Full Code of dani3l0/honey for AI

main 0ae1ea569beb cached
38 files
34.8 KB
11.2k tokens
48 symbols
1 requests
Download .txt
Repository: dani3l0/honey
Branch: main
Commit: 0ae1ea569beb
Files: 38
Total size: 34.8 KB

Directory structure:
gitextract_y0s9qw7f/

├── .github/
│   └── workflows/
│       └── docker-image.yml
├── .gitignore
├── Dockerfile
├── README.md
├── css/
│   ├── Background.css
│   ├── Flags/
│   │   ├── Dark.css
│   │   ├── Flags.css
│   │   └── Loaded.css
│   ├── Pages/
│   │   ├── Home.css
│   │   ├── More/
│   │   │   ├── More.css
│   │   │   ├── Overview.css
│   │   │   └── Settings.css
│   │   ├── Pages.css
│   │   └── Services.css
│   └── main.css
├── docker-compose.yaml
├── entrypoint.sh
├── fonts/
│   ├── MaterialSymbolsRounded/
│   │   └── MaterialSymbolsRounded.css
│   ├── Quicksand/
│   │   └── Quicksand.css
│   └── fonts.css
├── index.html
├── js/
│   ├── App.js
│   ├── UI/
│   │   ├── Drawer/
│   │   │   └── Drawer.js
│   │   ├── Home/
│   │   │   └── Home.js
│   │   ├── Main/
│   │   │   └── Main.js
│   │   └── More/
│   │       ├── More.js
│   │       ├── Overview/
│   │       │   ├── Overview.js
│   │       │   ├── analyzer.js
│   │       │   └── tiles.js
│   │       └── Settings/
│   │           ├── Settings.js
│   │           ├── events.js
│   │           └── tiles.js
│   ├── Utils/
│   │   ├── Config.js
│   │   ├── DOMUtils.js
│   │   ├── PingService.js
│   │   └── StringUtils.js
│   └── main.js
└── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/docker-image.yml
================================================
# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images


name: Create and publish a Docker image


# Build Docker image on new release
on:
  push:
    tags: ['v*']


# Set registry to GitHub Container Registry
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:

  build-and-push-image:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write

    # Build steps
    steps:

      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          provenance: false
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}


================================================
FILE: .gitignore
================================================
dist
node_modules
config


================================================
FILE: Dockerfile
================================================
FROM node:alpine

# Path to assets in container
WORKDIR /app
COPY . .

# Build honey
RUN npm install
RUN npm run build

# Run a built-in webserver
CMD ["/app/entrypoint.sh"]

# Expose port
EXPOSE 4173

# Health check
HEALTHCHECK CMD wget -nv --spider --tries=1 http://127.0.0.1:4173


================================================
FILE: README.md
================================================
# honey

_A sweet dashboard I use on my homeserver with some self-hosted stuff..._

honey is written in **pure** `HTML` `CSS` `JS` so dynamic backend or special webserver configuration is not required.
It works out-of-the-box as all operations are done client-side.

<font size="4">**[📺 Live demo](https://honeyy.vercel.app/)**</font>

<img src="screenshot.jpg" style="width: 720px">


## 🚀 Installation

### 🕸️ On existing webserver

1. Download latest prebuilt archive from **[Releases](https://github.com/dani3l0/honey/releases)**.

2. Extract downloaded archive to your webserver root.

3. You're done!


### 🐋 via Docker

```
docker run -p 4173:4173 -v /path/to/config:/app/dist/config ghcr.io/dani3l0/honey:latest
```

- `-p 4173:4173` - exposes HTTP port to your machine
- `-v /path/to/config:/app/dist/config` - mounts config directory to your local filesystem, missing config files will be created automatically

If you have custom icons or background images, you can freely put them in `config` dir.
Just remember to provide valid URLs (with `/config` prefix).

_alternatively, use a `docker-compose.yml` file_


## ⚙️ Configuration

Configuration file is located at `config/config.json`.


### 📱 Tweaking the user interface

The following keys are available under `ui` section.
Some of them are listed in _Settings_ page and can be customized by end-user.

| Key name				| Description																								| in Settings	 |
|-----------------------|-----------------------------------------------------------------------------------------------------------|----------------|
| `name`				| Name shown at the main screen and the tab title.															|		❌		|
| `desc`				| Short description shown under title at the main screen.													|		❌		|
| `icon`				| Icon shown at the main screen and as site's favicon.														|		❌		|
| `wallpaper`			| Background image visible when dark mode is disabled.														|		❌		|
| `wallpaper_dark`		| Background image visible when dark mode is enabled.														|		❌		|
| `dark_mode`			| Tells whether dark mode is enabled by default. (Available values: `Auto`,`Off`,`On`)						|		✅		|
| `open_new_tab`		| Tells whether clicking on a service will open it in new tab by default.									|		✅		|
| `ping_dots`			| Enables small dot before service name indicating whether is it available or not.							|		✅		|
| `blur`				| Tells whether card background blur is enabled by default.													|		✅		|
| `animations`			| Tells whether UI animations are enabled by default.														|		✅		|
| `trusted_domains`		| Array of domains (or IP addresses) to no longer be considered as 3rd-parties. RegExp is fully supported.	|		✅		|


### 🔗 Adding custom services

`services` section is an array containing objects. Object's structure looks like this:

| Key name			| Description																	|
|-------------------|-------------------------------------------------------------------------------|
| `name`			| Your service's name.															|
| `desc`			| Short description shown under service's name.									|
| `href`			| URL address of your service. It is directly passed to `<a>` tag.				|
| `icon`			| Path to an icon of your service.												|

Example:
```
...
{
	"name": "CalDav",
	"desc": "Simple CalDav server for calendar sync between various devices.",
	"href": "caldav",
	"icon": "img/preview/caldav.png"
},
...
```


## 🛠️ Development

honey is built on top of [Vite.js](https://vitejs.dev/). This tool allows faster development and offers various optimizations.

How to prepare a development environment:

```
# Download the source code
git clone https://github.com/dani3l0/honey && cd honey

# Install required modules
npm i
```


### 🗼 Live server

**For coding.** This will spin up a HTTP server on **[localhost:5173](http://localhost:5173/)**. Each time source file is saved, UI will automatically hot-reload so there is no need for `ALT+TAB` and `F5`.

```
npm run dev
```


### 🏗️ Build

**Prepare project for production.** This command will link and optimize project assets to take less space and require less bandwith. Prebuilt assets will be stored in `dist` directory and are ready to be put in a webserver root.

```
npm run build
```


## 🤝 Credits

Of course, some third-party resources are used in this project. I kanged them for self-hosting, easier development and to avoid compatibility issues.

- **[Material Icons](https://github.com/materialos/android-icon-pack/)**, for app icons at _Services_ page

- **[Google Fonts](https://fonts.google.com/)**, for material icons on buttons and Quicksand font

- **honey icon** - random icon found in DuckDuckGo Images

- **Wallpapers** - very nice background images kanged from [wallhaven](https://wallhaven.cc/)


================================================
FILE: css/Background.css
================================================
#background img {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	overflow: hidden;
	object-fit: cover;
	transition: transform .4s, opacity .4s !important;
}

#background.scaled img {
	transform: scale(var(--scale-factor));
}

body #background img {
	opacity: 1;
}

body:not(.dark) #background img:last-child,
body.dark #background img:first-child {
	opacity: 0;
}

body.dark #background:not(.scaled) img:first-child,
body:not(.dark) #background:not(.scaled) img:last-child {
	transform: scale(var(--scale-factor));
}

body.dark #background.scaled img:first-child,
body:not(.dark) #background.scaled img:last-child {
	transform: none;
}

#background .notloaded {
	transform: scale(1) !important;
	opacity: 0 !important;
}


================================================
FILE: css/Flags/Dark.css
================================================
body.dark {
	--color: #EEE;
	--color2: #EEE6;
	--background: #1118;
	--bg2: #0008;
	--hover: #FFF1;
}

body.dark #theme-switcher i::before {
	--hidden: 1;
}

body.dark #theme-switcher i::after {
	--hidden: 0;
}


================================================
FILE: css/Flags/Flags.css
================================================
@import url(Loaded.css);
@import url(Dark.css);

body {
	--color: #222;
	--color2: #2229;
	--background: #EEE8;
	--bg2: #FFF8;
	--hover: #0001;
	--scale-factor: 1.15;
	--blur: blur(16px);
}

body.noblur {
	--blur: 0;
}

body.noanim * {
	transition: none !important;
}


================================================
FILE: css/Flags/Loaded.css
================================================
body {
	transition: opacity .3s;
}

body:not(.loaded) {
	opacity: 0;
	transition: none;
}

body:not(.loaded) * {
	transition: none !important;
}

body:not(.loaded) .page .wrapper {
	transform: scale(.8) translateY(50%);
}


================================================
FILE: css/Pages/Home.css
================================================
.main {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
}

.appicon {
	width: 192px;
	height: 192px;
	object-fit: cover;
	transition: transform .4s, opacity .4s;
}

.appicon.notloaded {
	transform: scale(.8);
	opacity: 0;
}

#theme-switcher i {
	position: relative;
	overflow: hidden;
}

#theme-switcher i::before, #theme-switcher i::after {
	position: absolute;
	top: 50%;
	left: 50%;
	--hidden: 1;
	opacity: calc(1 - var(--hidden));
	transform: translate(-50%, -50%) rotateZ(calc(var(--hidden) * 360deg)) scale(calc(1 - var(--hidden) / 2));
	transition: transform .3s, opacity .3s;
}

#theme-switcher i::before {
	--hidden: 0;
	--dark: 0;
	content: "light_mode";
}

#theme-switcher i::after {
	--dark: 1;
	content: "dark_mode";
}

.home.page {
	top: 50%;
	left: 50%;
	width: 100%;
	height: auto;
	overflow: hidden;
	transform: translate(-50%, -50%);
}

.home.page:not(.current) {
	top: calc(50% - 64px);
}

.home .wrapper {
	box-shadow: none;
	background: transparent;
	backdrop-filter: none;
}

.appname {
	font-size: 48px;
}

.appdesc {
	opacity: .6;
	margin: 2px 12px;
}

.buttons {
	box-shadow: 2px 2px 8px #0002;
	display: flex;
	margin: 16px auto 0;
	backdrop-filter: var(--blur);
	border-radius: 24px;
	max-width: 480px;
	background: var(--background);
	padding: 2px;
	justify-content: space-between;
	transition: background .3s;
}

.buttons > div {
	padding: 16px;
	margin: 2px;
	cursor: pointer;
	border-radius: 20px;
	width: 100%;
	transition: background .2s;
}

.buttons > div:hover {
	background: var(--hover);
}

.buttons .text {
	margin-top: -2px;
}


================================================
FILE: css/Pages/More/More.css
================================================
@import url(Overview.css);
@import url(Settings.css);

.subpages {
	position: relative;
	transform: translateX(calc(var(--id) * -100%));
	transition: transform .4s, height .4s;
}

.subpages > div {
	position: absolute;
	left: calc(var(--n) * 100%);
	width: 100%;
}


================================================
FILE: css/Pages/More/Overview.css
================================================
.overview {
	display: flex;
	align-items: center;
	justify-content: space-between;
	max-width: 640px;
	padding: 0 20px;
	margin: 128px auto 88px;
	text-align: right;
}

.overview > i {
	font-size: 80px;
}

@property --value {
	syntax: '<integer>';
	initial-value: 0;
	inherits: false;
}

.overview .big {
	font-size: 64px;
	margin-bottom: -4px;
	counter-reset: value var(--value);
	transition: --value 1.2s;
}

.page:not(.current) .overview .big {
	--value: 0 !important;
	transition: --value 0s .5s;
}

.overview .big::after {
	content: counter(value);
}

.overview .small {
	opacity: .5;
}

.privacy-boxes {
	display: flex;
	text-align: left;
	flex-wrap: wrap;
	margin-bottom: 10px;
}

.privacy-boxes > div {
	display: flex;
	width: 50%;
	min-width: 256px;
	flex: 1;
	padding: 8px;
	align-items: center;
}

.privacy-boxes i {
	color: var(--color);
	text-shadow: 0 0 48px var(--color);
	padding: 16px;
	font-size: 28px;
	border-radius: 32px;
}

.privacy-boxes .title {
	font-size: 16px;
}

.privacy-boxes .subtitle {
	opacity: .5;
}


================================================
FILE: css/Pages/More/Settings.css
================================================
#settings {
	margin: 32px auto;
	padding: 0 16px;
}

.setting {
	background: var(--bg2);
	margin: 8px;
	padding: 20px;
	display: flex;
	border-radius: 16px;
	align-items: center;
	text-align: left;
	transition: background .3s;
}
.setting.pointer {
	cursor: pointer;
}

.setting i {
	margin-right: 14px;
	font-size: 28px;
}

.setting .name {
	font-size: 16px;
}

.setting .desc {
	opacity: .6;
	margin-right: 16px;
}

.setting .switch {
	position: relative;
	width: 44px;
	min-width: 44px;
	height: 24px;
	background: #8886;
	border-radius: 100px;
	margin: 0 4px 0 auto;
	transition: border .4s, background .3s;
}

.setting .switch:after {
	content: "";
	position: absolute;
	width: 16px;
	height: 16px;
	background: var(--color);
	left: 4px;
	top: 50%;
	border-radius: 10px;
	transform: translateY(-50%);
	transition: left .2s, background .3s;
}

.setting.checked .switch {
	background-color: #68F;
	border-color: #68F;
}

.setting.checked .switch:after {
	left: calc(100% - 20px);
}

#no-cookies {
	margin: 24px 0 -8px;
	color: #F60;
}

#no-cookies.hidden {
	display: none;
}

.options {
	position: relative;
	margin-left: auto;
	background: #8883;
	border-radius: 16px;
	border: 4px solid transparent;
	display: flex;
	align-items: center;
	text-align: center;
	overflow: hidden;
}

.options::before {
	content: " ";
	position: absolute;
	width: calc(100% / var(--items));
	left: calc(100% / var(--items) * var(--item));
	height: 100%;
	border-radius: 12px;
	background: #68F;
	transition: left .3s;
}

.options div {
	padding: 12px 0;
	width: 64px;
	flex: 1;
	cursor: pointer;
	z-index: 1;
}


================================================
FILE: css/Pages/Pages.css
================================================
@import url(Home.css);
@import url(Services.css);
@import url(More/More.css);

.page {
	position: fixed;
	top: 0;
	left: 50%;
	width: 100%;
	max-width: 920px;
	height: 100vh;
	transform: translateX(-50%);
	overflow-y: scroll;
	transition: top .4s, opacity .4s, visibility .4s, color .3s;
}

.page:not(.current) {
	top: 240px;
	visibility: hidden;
	opacity: 0;
}

.wrapper {
	box-shadow: 2px 2px 8px #0003;
	background: var(--background);
	padding: 3px;
	backdrop-filter: var(--blur);
	border-radius: 24px;
	margin: -4px 12px 16px;
	text-align: center;
	overflow: hidden;
	min-width: 340px;
	transition: background .2s, transform .4s, backdrop-filter .6s;
}

.back {
	box-shadow: 2px 2px 8px #0002;
	position: relative;
	width: 64px;
	height: 64px;
	border-radius: 24px;
	background: var(--background);
	margin: 20px;
	backdrop-filter: var(--blur);
	transition: background .2s;
}

.back i {
	margin: 4px;
	width: 56px;
	height: 56px;
	cursor: pointer;
	border-radius: 20px;
	transition: background .2s;
}

.back i:hover {
	background: var(--hover);
}

.back i:after {
	content: "chevron_left";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
}

.header {
	display: flex;
	align-items: center;
	margin: 20px 20px 16px;
}

.header i {
	margin-top: 1px;
	margin-right: 10px;
}

.header .text {
	font-size: 26px;
}

.subswitch {
	display: flex;
	background: var(--bg2);
	transition: background .2s;
	margin: 16px 24px 0;
	z-index: 1;
	padding: 4px;
	border-radius: 16px;
	position: relative;
	overflow: hidden;
	--id: inherit;
}

.subswitch::before {
	content: " ";
	z-index: -1;
	position: absolute;
	top: 4px;
	left: calc(var(--id) / var(--switches) * 100% + 4px - 4px * var(--id));
	width: calc(100% / var(--switches) - 4px);
	height: calc(100% - 8px);
	opacity: .25;
	background: var(--color2);
	border-radius: 12px;
	transition: left .3s, background .3s;
}

.subswitch div {
	padding: 12px;
	width: 50%;
	cursor: pointer;
}


================================================
FILE: css/Pages/Services.css
================================================
.boxes {
	display: flex;
	flex: 1 1 0;
	flex-wrap: wrap;
}

.box {
	min-width: 292px;
	flex: 1;
	margin: 2px;
	border-radius: 20px;
	padding: 8px;
	display: flex;
	align-items: center;
	text-align: left;
	text-decoration: none;
	color: inherit;
	transition: background .2s;
}

.box:hover {
	background: var(--hover);
}

.box i {
	font-size: 24px;
	padding: 20px;
	background: hsl(var(--color), 100%, 89%);
	color: hsl(var(--color), 100%, 35%);
	border-radius: 100px;
	margin: 2px 12px 2px 2px;
}

a.box {
	cursor: pointer;
	padding: 14px;
}

.box img {
	width: 64px;
	height: 64px;
	object-fit: cover;
	margin-right: 12px;
}

.box .name {
	position: relative;
	font-size: 18px;
}

.box.pingdot .name {
	padding-left: 16px;
}

.box.pingdot .name::before {
	content: " ";
	position: absolute;
	width: 6px;
	height: 6px;
	border-radius: 6px;
	top: 50%;
	left: 0;
	transform: translateY(-50%);
	background: #888;
	transition: background .2s, box-shadow .8s;
}
.box.pingdot.up .name::before {
	background: #0F8;
	box-shadow: 0 0 12px #0F8;
}
.box.pingdot.down .name::before {
	background: #F35;
	box-shadow: 0 0 12px #F35;
}
.box.pingdot.error .name::before {
	background: #F82;
	box-shadow: 0 0 12px #F82;
}

.box .desc {
	opacity: .6;
}


================================================
FILE: css/main.css
================================================
@import url(../fonts/fonts.css);

@import url(Flags/Flags.css);
@import url(Background.css);
@import url(Pages/Pages.css);


body {
	background: #000;
	color: var(--color);
	margin: 0;
	font-family: Quicksand;
	font-weight: bold;
	user-select: none;
	font-size: 14px;
	-webkit-tap-highlight-color: transparent;
}

* {
	scrollbar-width: none;
}

::-webkit-scrollbar {
	display: none;
}

================================================
FILE: docker-compose.yaml
================================================
services:
  honey:
    image: ghcr.io/dani3l0/honey:latest
    container_name: honey
    restart: unless-stopped
    volumes:
      - ./config:/app/dist/config
    ports:
      - "4173:4173"


================================================
FILE: entrypoint.sh
================================================
#!/bin/sh
cp -rnv /app/public/config/* /app/dist/config
npm run preview
exit $?


================================================
FILE: fonts/MaterialSymbolsRounded/MaterialSymbolsRounded.css
================================================
@font-face {
	font-family: 'Material Symbols Rounded';
	font-style: normal;
	font-weight: 500;
	src: url(MaterialSymbolsRounded.woff2) format('woff2');
}

i {
	font-family: 'Material Symbols Rounded';
	font-weight: normal;
	font-style: normal;
	font-size: 24px;
	line-height: 1;
	letter-spacing: normal;
	text-transform: none;
	display: inline-block;
	white-space: nowrap;
	word-wrap: normal;
	direction: ltr;
	-webkit-font-feature-settings: 'liga';
	-webkit-font-smoothing: antialiased;
}

i {
	display: inline-block;
}


================================================
FILE: fonts/Quicksand/Quicksand.css
================================================
/* quicksand-regular-latin */
@font-face {
	font-family: Quicksand;
	font-style: normal;
	font-weight: 400;
	src: local("Quicksand Regular"), local("Quicksand-Regular"), url(quicksand-regular-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2212,U+2215;
}
/* quicksand-regular-latin-ext */
@font-face {
	font-family: Quicksand;
	font-style: normal;
	font-weight: 400;
	src: local("Quicksand Regular"), local("Quicksand-Regular"), url(quicksand-regular-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-024F,U+0259,U+1E00-1EFF,U+20A0-20CF,U+2C60-2C7F,U+A720-A7FF;
}
/* quicksand-bold-latin */
@font-face {
	font-family: Quicksand;
	font-style: normal;
	font-weight: 700;
	src: local("Quicksand Bold"), local("Quicksand-Bold"), url(quicksand-bold-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2212,U+2215;
}
/* quicksand-bold-latin-ext */
@font-face {
	font-family: Quicksand;
	font-style: normal;
	font-weight: 700;
	src: local("Quicksand Bold"), local("Quicksand-Bold"), url(quicksand-bold-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-024F,U+0259,U+1E00-1EFF,U+20A0-20CF,U+2C60-2C7F,U+A720-A7FF;
}



================================================
FILE: fonts/fonts.css
================================================
@import url(Quicksand/Quicksand.css);
@import url(MaterialSymbolsRounded/MaterialSymbolsRounded.css);


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
	<link rel="icon" id="favicon">
	<link rel="apple-touch-icon" id="apple-touch-icon">
	<link rel="stylesheet" type="text/css" href="css/main.css">
	<script src="js/main.js" type="module"></script>
</head>
<body>
	<div id="background"></div>
	<div class="main">

		<!-- Home page -->
		<div class="page home" p="home">
			<div class="wrapper">
				<div class="home">
					<img class="appicon">
					<div class="appname"></div>
					<div class="appdesc"></div>
				</div>
				<div class="buttons">
					<div id="theme-switcher">
						<i>&nbsp;</i>
						<div class="text">Theme</div>
					</div>
					<div t="services">
						<i>apps</i>
						<div class="text">Services</div>
					</div>
					<div t="more">
						<i>more</i>
						<div class="text">More</div>
					</div>
				</div>
			</div>
		</div>

		<!-- App list -->
		<div class="page" p="services">
			<div class="back"><i></i></div>
			<div class="wrapper">
				<div class="header">
					<i>apps</i>
					<div class="text">Services</div>
				</div>
				<div class="boxes" id="app-list"></div>
			</div>
		</div>

		<!-- More -->
		<div class="page" p="more">
			<div class="back"><i></i></div>
			<div class="wrapper">
				<div class="subswitch">
					<div>Overview</div>
					<div>Settings</div>
				</div>
				<div class="subpages">

					<!-- Overview -->
					<div>
						<div class="overview">
							<i>rocket_launch</i>
							<div>
								<div class="big"></div>
								<div class="small"></div>
							</div>
						</div>
						<div class="privacy-boxes"></div>
					</div>

					<!-- Settings -->
					<div>
						<div id="no-cookies">WARNING: due to blocked cookies, all settings will be lost after page reload</div>
						<div id="settings"></div>
					</div>
				</div>
			</div>
		</div>
	</div>
</body>
</html>


================================================
FILE: js/App.js
================================================
import Drawer from "./UI/Drawer/Drawer"
import Home from "./UI/Home/Home"
import Main from "./UI/Main/Main"
import More from "./UI/More/More"
import Config from "./Utils/Config"
import { showPage } from "./Utils/DOMUtils"


export default class App {
	static instance

	constructor(config) {
		if (App.instance) return App.instance
		App.instance = this
		this.config = new Config(config)
		this.init()
	}

	init() {
		this.main = new Main()
		this.home = new Home()
		this.drawer = new Drawer()
		this.more = new More()

		showPage("home")
		setTimeout(() => {
			document.body.classList.add("loaded")
		}, 100)
	}
}


================================================
FILE: js/UI/Drawer/Drawer.js
================================================
import App from "../../App";
import PingService from "../../Utils/PingService";

export default class Drawer {
	constructor() {
		this.app = new App()
		this.config = this.app.config
		this.init()
	}

	init() {
		this.importApps()
	}

	importApps() {
		let apps = this.config.getServices()
		let enablePingDots = this.config.get("ping_dots")
		let openNewTab = this.config.get("open_new_tab")
		let applist = document.querySelector("#app-list")
		applist.innerHTML = ""
		for (let app of apps) {
			let a = document.createElement("a")
			a.classList.add("box")
			a.href = app.href
			if (openNewTab) a.setAttribute("target", "_blank")
			a.innerHTML = `
				<img src="${app.icon}">
				<div>
					<div class="name">${app.name}</div>
					<div class="desc">${app.desc}</div>
				</div>`

			if (enablePingDots) {
				a.classList.add("pingdot")
				PingService(app.href, status => {
					if (!status) return
					let resp = "down"
					if (status >= 200 && status < 400) resp = "up"
					else if (status >= 400) resp = "error"
					a.classList.add(resp)
				})
			}

			applist.appendChild(a)
		}
	}
}


================================================
FILE: js/UI/Home/Home.js
================================================
import App from "../../App"
import { showPage } from "../../Utils/DOMUtils"


export default class Home {
	constructor() {
		this.app = new App()
		this.config = this.app.config
		this.init()
	}

	init() {
		this.initButtons()
		this.initHomeUI()
		this.initBackButtons()
	}

	initButtons() {
		let buttons = document.querySelector(".buttons").children
		for (let button of buttons) {
			let target = button.getAttribute("t")
			if (target) {
				button.addEventListener("click", () => {
					showPage(target)
				})
			}
		}
	}

	initBackButtons() {
		let backButtons = document.querySelectorAll(".back")
		for (let button of backButtons) {
			button.addEventListener("click", () => {
				showPage("home")
			})
		}
	}

	initHomeUI() {
		let logo = document.querySelector(".appicon")
		logo.src = this.config.get("icon")
		logo.classList.add("notloaded")
		logo.addEventListener("load", () => {
			logo.classList.remove("notloaded")
		})

		let name = document.querySelector(".appname")
		name.innerText = this.config.get("name")

		let desc = document.querySelector(".appdesc")
		desc.innerText = this.config.get("desc")
	}
}

================================================
FILE: js/UI/Main/Main.js
================================================
import App from "../../App"

export default class Main {
	constructor() {
		this.app = new App()
		this.config = this.app.config
		this.init()
	}

	init() {
		document.title = this.config.get("name")
		document.querySelector("#favicon").href = this.config.get("icon")
		document.querySelector("#apple-touch-icon").href = this.config.get("icon")
		this.initBackgrounds()
	}

	initBackgrounds() {
		this.backgrounds = document.querySelector("#background")
		for (let i = 0; i < 2; i++) {
			let img = document.createElement("img")
			img.classList.add("notloaded")
			img.addEventListener("load", () => {
				img.classList.remove("notloaded")
			})
			this.backgrounds.appendChild(img)
		}

		this.backgrounds = this.backgrounds.children
		this.backgrounds[0].src = this.config.get("wallpaper")
		this.backgrounds[1].src = this.config.get("wallpaper_dark")
	}
}


================================================
FILE: js/UI/More/More.js
================================================
import App from "../../App";
import Overview from "./Overview/Overview";
import Settings from "./Settings/Settings";


export default class More {
	constructor() {
		this.app = new App()
		this.config = this.app.config
		this.overview = new Overview()
		this.settings = new Settings()
		this.init()
	}

	init() {
		this.overview.init()
		this.settings.init()
		this.initPager()
	}

	initPager() {
		let switcher = document.querySelector(".subswitch")
		let buttons = switcher.children
		let subsettings = document.querySelector(".subpages")

		for (let i = 0; i < buttons.length; i++) {
			let button = buttons[i]
			subsettings.children[i].setAttribute("style", `--n: ${i}`)

			button.addEventListener("click", () => {
				let calculatedHeight = subsettings.children[i].offsetHeight
				subsettings.style.height = `${calculatedHeight}px`
				subsettings.parentNode.setAttribute("style", `--id: ${i}`)
				switcher.setAttribute("style", `--switches: ${buttons.length}`)
			})
		}

		buttons[0].click()
	}
}


================================================
FILE: js/UI/More/Overview/Overview.js
================================================
import App from "../../../App";
import { analyzeService } from "./analyzer";
import { privacyBox } from "./tiles";
import { s, isare } from "../../../Utils/StringUtils";


export default class Overview {
	constructor() {
		this.app = new App()
		this.config = this.app.config
		this.div = document.querySelector(".overview").parentNode
	}

	init() {
		this.initPrivacyBoxes()
	}

	initPrivacyBoxes() {
		let stats = {
			total: 0,
			secure: 0,
			thirdParties: 0
		}
		for (let service of this.config.getServices()) {
			let analysis = analyzeService(service.href, this.config.get("trusted_domains"))
			stats.total++
			stats.secure += analysis.isSecure
			stats.thirdParties += analysis.isThirdParty
		}
		this.div.querySelector(".big").setAttribute("style", `--value: ${stats.total}`)
		this.div.querySelector(".small").innerText = `Available service${s(stats.total)}`

		let encryption_t, encryption_d
		if (stats.secure == stats.total) {
			encryption_t = "Full encryption"
			encryption_d = "All services use secure connections (HTTPS)."
		}
		else if (stats.secure == 0) {
			encryption_t = "No encryption"
			encryption_d = "It seems server does not support HTTPS."

		}
		else {
			let insecure = stats.total - stats.secure
			encryption_t = "Partial encryption"
			encryption_d = `${insecure} service${s(insecure)} do not use secure connections.`

		}

		let indepencence_t, indepencence_d
		if (stats.thirdParties == 0) {
			indepencence_t = "Independence"
			indepencence_d = "This server is free of 3rd party services."
		}
		else if (stats.thirdParties == stats.total) {
			indepencence_t = "Something is wrong..."
			indepencence_d = "It seems only 3rd-party services are listed."
		}
		else {
			indepencence_t = "Partial independence"
			indepencence_d = `${stats.thirdParties} service${s(stats.thirdParties)} ${isare(stats.thirdParties)} provided by 3rd-parties.`
		}

		privacyBox("lock", "#0D6", encryption_t, encryption_d, stats.secure / stats.total)
		privacyBox("home", "#68F", indepencence_t, indepencence_d, 1 - stats.thirdParties / stats.total)
	}
}


================================================
FILE: js/UI/More/Overview/analyzer.js
================================================
export function analyzeService(url, whitelist) {
	let isSiteSecure = (
		window.location.protocol == "https:" ||
		window.location.hostname == "localhost"
	)

	let isSecure = false
	if (url.startsWith("https://")) {
		isSecure = true
	}
	else if (!["http", "https"].includes(url.split("://")[0])) {
		isSecure = isSiteSecure
	}

	let isThirdParty = true
	let domain_base = window.location.hostname
	let domain = url.split("://")
	if (domain.length > 1) {
		domain = domain[1]
		domain = domain.split("/")[0]
		isThirdParty = !domain.includes(domain_base)
		if (isThirdParty) {
			for (let entry of whitelist) {
				let re = RegExp(entry)
				if (re.exec(domain)) {
					isThirdParty = false
					break
				}
			}
		}
	}
	else {
		isThirdParty = false
	}

	return {isSecure, isThirdParty}
}

================================================
FILE: js/UI/More/Overview/tiles.js
================================================
export function privacyBox(icon, color, name, desc, pp) {
	if (pp == 1) {}
	else if (pp > 0.7) color = "#EA0"
	else if (pp > 0.25) color = "#F72"
	else color = "#F33"

	let item = document.createElement("div")
	item.setAttribute("style", `--color: ${color}`)

	item.innerHTML = `<i>${icon}</i>
		<div>
			<div class="title">${name}</div>
			<div class="subtitle">${desc}</div>
		</div>`

	document.querySelector(".privacy-boxes").appendChild(item)
	return item
}


================================================
FILE: js/UI/More/Settings/Settings.js
================================================
import App from "../../../App";
import { addOnOffTile, addOptionsTile } from "./tiles";
import * as EVENTS from "./events"


export default class Settings {
	constructor() {
		this.app = new App()
		this.config = this.app.config
	}

	init() {
		this.checkLocalStorage()
		this.initSettings()
	}

	initSettings() {
		let darkMode = addOptionsTile(this.config,
			"dark_mode", "Dark mode",
			"Make the colors more appropriate for low-light environments",
			"dark_mode", EVENTS.onThemeChange
		)

		addOnOffTile(this.config,
			"open_in_new", "Open in new tab",
			"Clicking on application will open it in a new browser tab",
			"open_new_tab", EVENTS.onNewTabChange
		)

		addOnOffTile(this.config,
			"sensors", "Ping dots",
			"Shows small dots before titles indicating whether service is up or not",
			"ping_dots", EVENTS.onPingDotsChange
		)

		addOnOffTile(this.config,
			"blur_on", "Enable blur",
			"Improves UI sweetness but may have a huge impact on performance",
			"blur", EVENTS.onBlurChange
		)

		addOnOffTile(this.config,
			"animation", "Animations",
			"Show nice and fancy page transitions for improved experience",
			"animations", EVENTS.onAnimationChange
		)

		document.querySelector("#theme-switcher").addEventListener("click", () => {
			let targetButtons = darkMode.querySelector(".options").children
			let storedValue = this.config.get("dark_mode")
			let target;
			if (storedValue == "Auto") {
				let isSystemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
				target = 2 - isSystemDark
			}
			else {
				let isEnforcedDark = storedValue == "On"
				target = !isEnforcedDark + 1
			}
			targetButtons[target].click()
		})
	}

	checkLocalStorage() {
		let warn = document.querySelector("#no-cookies").classList
		if (this.config.storageAvailable) warn.add("hidden")
	}
}

================================================
FILE: js/UI/More/Settings/events.js
================================================
import App from "../../../App";

const CL = document.body.classList

// Switch between light & dark themes
var onThemeChange_SystemListener = false;
export function onThemeChange(config) {
	let value = config.get("dark_mode")
	let mm = window.matchMedia('(prefers-color-scheme: dark)')
	let isDark = value == "Auto" ? mm.matches : value == "On"
	isDark ? CL.add("dark") : CL.remove("dark")

	// Listen for system theme changes
	if (!onThemeChange_SystemListener) {
		onThemeChange_SystemListener = true
		mm.addEventListener('change', event => {
			if (config.get("dark_mode") == "Auto") {
				let isDark = event.matches;
				isDark ? CL.add("dark") : CL.remove("dark")
			}
		});
	}
}

// Open apps in new tab
export function onNewTabChange(config) {
	let openNewTab = config.get("open_new_tab")
	let appList = document.querySelector("#app-list").children

	for (let app of appList) {
		if (openNewTab) app.setAttribute("target", "_blank")
		else app.removeAttribute("target") 
	}
}

// Enable/disable ping dots
export function onPingDotsChange(config) {
	let app = new App()
	app.drawer.importApps()
}

// Enable/disable background blur
export function onBlurChange(config) {
	let blur = config.get("blur")
	blur ? CL.remove("noblur") : CL.add("noblur")
}

// Enable/disable animations
export function onAnimationChange(config) {
	let animations = config.get("animations")
	animations ? CL.remove("noanim") : CL.add("noanim")
}


================================================
FILE: js/UI/More/Settings/tiles.js
================================================
export function addOnOffTile(conf, icon, name, desc, key, func) {
	let item = document.createElement("div")
	item.classList.add("setting")
	item.classList.add("pointer")
	item.innerHTML = `
		<i>${icon}</i>
		<div class="text">
			<div class="name">${name}</div>
			<div class="desc">${desc}</div>
		</div>
		<div class="switch"></div>`

	let handleState = () => {
		let c = item.classList
		if (conf.get(key)) c.add("checked")
		else c.remove("checked")
	}

	let write = () => {
		let target_value = !conf.get(key)
		conf.set(key, target_value)
	}

	let f = () => {func(conf)}

	item.addEventListener("click", write)
	item.addEventListener("click", handleState)
	if (func) item.addEventListener("click", f)

	handleState()
	if (func) f()

	document.querySelector("#settings").appendChild(item)
	return item
}

export function addOptionsTile(conf, icon, name, desc, key, func) {
	let options = ["Auto", "Off", "On"]
	let optionsHtml = document.createElement("div")
	optionsHtml.classList.add("options")

	let handleState = () => {
		let c = optionsHtml
		let value = conf.get(key)
		let n = options.indexOf(value)
		for (let i = 0; i < options.length; i++) {
			let cl = c.children[i].classList
			if (i == n) cl.add("active")
			else cl.remove("active")
		}
		c.setAttribute("style", `--item: ${n}; --items: ${options.length}`)
	}

	let write = (val) => {
		conf.set(key, val)
	}

	let f = () => {func(conf)}

	options.forEach(e => {
		let node = document.createElement("div")
		node.innerText = e
		node.addEventListener("click", () => {
			write(e)
			handleState()
			if (func) f()
		})
		optionsHtml.appendChild(node)
	})

	handleState()
	if (func) f()

	let item = document.createElement("div")
	item.classList.add("setting")
	item.innerHTML = `
		<i>${icon}</i>
		<div class="text">
			<div class="name">${name}</div>
			<div class="desc">${desc}</div>
		</div>`
	item.appendChild(optionsHtml)

	document.querySelector("#settings").appendChild(item)
	return item
}


================================================
FILE: js/Utils/Config.js
================================================
export default class Config {

	// Initialization & localStorage availability check
	constructor(config) {
		this.config = config
		try {
			window.localStorage
			this.storageAvailable = true
		}
		catch (e) {
			this.storageAvailable = false
		}
	}

	// Get value from config or localStorage (if set)
	get(key) {
		let value = this.config["ui"][key]

		if (this.storageAvailable) {
			let type = typeof(value)
			let stored_value = window.localStorage.getItem(key)

			if (stored_value != null) {
				value = stored_value
				if (type == "number") value = Number(value)
				else if (type == "boolean") value = value == "true"
			}
		}

		return value
	}

	// Save value to localStorage
	set(key, value) {
		key = key.toLowerCase()
		this.config["ui"][key] = value

		if (this.storageAvailable) {
			window.localStorage.setItem(key, value)
		}
	}

	// Get services from config.json file
	getServices() {
		return this.config["services"]
	}
}


================================================
FILE: js/Utils/DOMUtils.js
================================================
export function showPage(target) {
	let bg = document.querySelector("#background").classList
	if (target == "home") bg.add("scaled")
	else bg.remove("scaled")
	let pages = document.querySelectorAll(".page")

	for (let page of pages) {
		let p = page.getAttribute("p")
		if (p == target) page.classList.add("current")
		else page.classList.remove("current")
	}

}


================================================
FILE: js/Utils/PingService.js
================================================
export default function PingService(uri, callback) {
    let xhr = new XMLHttpRequest()
    xhr.open("GET", uri)
    xhr.onreadystatechange = function() {
        if (this.readyState < 4) return
        let code = this.status
        setTimeout(() => callback(code), 3000)
    }
    xhr.send()
}


================================================
FILE: js/Utils/StringUtils.js
================================================
export function s(number) {
	if (number == 1) return ""
	return "s"
}

export function isare(number) {
	if (number == 1) return "is"
	return "are"
}


================================================
FILE: js/main.js
================================================
import App from "./App"

window.addEventListener("DOMContentLoaded", () => {
	let xhr = new XMLHttpRequest()
	xhr.open("GET", "config/config.json")
	xhr.onload = function() {
		let config = JSON.parse(this.responseText)
		window.app = new App(config)
	}
	xhr.send()
})


================================================
FILE: package.json
================================================
{
  "name": "honey",
  "private": true,
  "version": "2",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --host"
  },
  "devDependencies": {
    "vite": "^5.4.9"
  }
}
Download .txt
gitextract_y0s9qw7f/

├── .github/
│   └── workflows/
│       └── docker-image.yml
├── .gitignore
├── Dockerfile
├── README.md
├── css/
│   ├── Background.css
│   ├── Flags/
│   │   ├── Dark.css
│   │   ├── Flags.css
│   │   └── Loaded.css
│   ├── Pages/
│   │   ├── Home.css
│   │   ├── More/
│   │   │   ├── More.css
│   │   │   ├── Overview.css
│   │   │   └── Settings.css
│   │   ├── Pages.css
│   │   └── Services.css
│   └── main.css
├── docker-compose.yaml
├── entrypoint.sh
├── fonts/
│   ├── MaterialSymbolsRounded/
│   │   └── MaterialSymbolsRounded.css
│   ├── Quicksand/
│   │   └── Quicksand.css
│   └── fonts.css
├── index.html
├── js/
│   ├── App.js
│   ├── UI/
│   │   ├── Drawer/
│   │   │   └── Drawer.js
│   │   ├── Home/
│   │   │   └── Home.js
│   │   ├── Main/
│   │   │   └── Main.js
│   │   └── More/
│   │       ├── More.js
│   │       ├── Overview/
│   │       │   ├── Overview.js
│   │       │   ├── analyzer.js
│   │       │   └── tiles.js
│   │       └── Settings/
│   │           ├── Settings.js
│   │           ├── events.js
│   │           └── tiles.js
│   ├── Utils/
│   │   ├── Config.js
│   │   ├── DOMUtils.js
│   │   ├── PingService.js
│   │   └── StringUtils.js
│   └── main.js
└── package.json
Download .txt
SYMBOL INDEX (48 symbols across 15 files)

FILE: js/App.js
  class App (line 9) | class App {
    method constructor (line 12) | constructor(config) {
    method init (line 19) | init() {

FILE: js/UI/Drawer/Drawer.js
  class Drawer (line 4) | class Drawer {
    method constructor (line 5) | constructor() {
    method init (line 11) | init() {
    method importApps (line 15) | importApps() {

FILE: js/UI/Home/Home.js
  class Home (line 5) | class Home {
    method constructor (line 6) | constructor() {
    method init (line 12) | init() {
    method initButtons (line 18) | initButtons() {
    method initBackButtons (line 30) | initBackButtons() {
    method initHomeUI (line 39) | initHomeUI() {

FILE: js/UI/Main/Main.js
  class Main (line 3) | class Main {
    method constructor (line 4) | constructor() {
    method init (line 10) | init() {
    method initBackgrounds (line 17) | initBackgrounds() {

FILE: js/UI/More/More.js
  class More (line 6) | class More {
    method constructor (line 7) | constructor() {
    method init (line 15) | init() {
    method initPager (line 21) | initPager() {

FILE: js/UI/More/Overview/Overview.js
  class Overview (line 7) | class Overview {
    method constructor (line 8) | constructor() {
    method init (line 14) | init() {
    method initPrivacyBoxes (line 18) | initPrivacyBoxes() {

FILE: js/UI/More/Overview/analyzer.js
  function analyzeService (line 1) | function analyzeService(url, whitelist) {

FILE: js/UI/More/Overview/tiles.js
  function privacyBox (line 1) | function privacyBox(icon, color, name, desc, pp) {

FILE: js/UI/More/Settings/Settings.js
  class Settings (line 6) | class Settings {
    method constructor (line 7) | constructor() {
    method init (line 12) | init() {
    method initSettings (line 17) | initSettings() {
    method checkLocalStorage (line 64) | checkLocalStorage() {

FILE: js/UI/More/Settings/events.js
  function onThemeChange (line 7) | function onThemeChange(config) {
  function onNewTabChange (line 26) | function onNewTabChange(config) {
  function onPingDotsChange (line 37) | function onPingDotsChange(config) {
  function onBlurChange (line 43) | function onBlurChange(config) {
  function onAnimationChange (line 49) | function onAnimationChange(config) {

FILE: js/UI/More/Settings/tiles.js
  function addOnOffTile (line 1) | function addOnOffTile(conf, icon, name, desc, key, func) {
  function addOptionsTile (line 37) | function addOptionsTile(conf, icon, name, desc, key, func) {

FILE: js/Utils/Config.js
  class Config (line 1) | class Config {
    method constructor (line 4) | constructor(config) {
    method get (line 16) | get(key) {
    method set (line 34) | set(key, value) {
    method getServices (line 44) | getServices() {

FILE: js/Utils/DOMUtils.js
  function showPage (line 1) | function showPage(target) {

FILE: js/Utils/PingService.js
  function PingService (line 1) | function PingService(uri, callback) {

FILE: js/Utils/StringUtils.js
  function s (line 1) | function s(number) {
  function isare (line 6) | function isare(number) {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (42K chars).
[
  {
    "path": ".github/workflows/docker-image.yml",
    "chars": 1397,
    "preview": "# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images\n\n\nname: Create and publish a Docker im"
  },
  {
    "path": ".gitignore",
    "chars": 25,
    "preview": "dist\nnode_modules\nconfig\n"
  },
  {
    "path": "Dockerfile",
    "chars": 283,
    "preview": "FROM node:alpine\n\n# Path to assets in container\nWORKDIR /app\nCOPY . .\n\n# Build honey\nRUN npm install\nRUN npm run build\n\n"
  },
  {
    "path": "README.md",
    "chars": 4760,
    "preview": "# honey\n\n_A sweet dashboard I use on my homeserver with some self-hosted stuff..._\n\nhoney is written in **pure** `HTML` "
  },
  {
    "path": "css/Background.css",
    "chars": 741,
    "preview": "#background img {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\toverflow: hidden;\n\tobject-fit: cove"
  },
  {
    "path": "css/Flags/Dark.css",
    "chars": 211,
    "preview": "body.dark {\n\t--color: #EEE;\n\t--color2: #EEE6;\n\t--background: #1118;\n\t--bg2: #0008;\n\t--hover: #FFF1;\n}\n\nbody.dark #theme-"
  },
  {
    "path": "css/Flags/Flags.css",
    "chars": 268,
    "preview": "@import url(Loaded.css);\n@import url(Dark.css);\n\nbody {\n\t--color: #222;\n\t--color2: #2229;\n\t--background: #EEE8;\n\t--bg2: "
  },
  {
    "path": "css/Flags/Loaded.css",
    "chars": 222,
    "preview": "body {\n\ttransition: opacity .3s;\n}\n\nbody:not(.loaded) {\n\topacity: 0;\n\ttransition: none;\n}\n\nbody:not(.loaded) * {\n\ttransi"
  },
  {
    "path": "css/Pages/Home.css",
    "chars": 1584,
    "preview": ".main {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.appicon {\n\twidth: 192px;\n\theight: 192px;\n\t"
  },
  {
    "path": "css/Pages/More/More.css",
    "chars": 265,
    "preview": "@import url(Overview.css);\n@import url(Settings.css);\n\n.subpages {\n\tposition: relative;\n\ttransform: translateX(calc(var("
  },
  {
    "path": "css/Pages/More/Overview.css",
    "chars": 1034,
    "preview": ".overview {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmax-width: 640px;\n\tpadding: 0 20px;\n"
  },
  {
    "path": "css/Pages/More/Settings.css",
    "chars": 1595,
    "preview": "#settings {\n\tmargin: 32px auto;\n\tpadding: 0 16px;\n}\n\n.setting {\n\tbackground: var(--bg2);\n\tmargin: 8px;\n\tpadding: 20px;\n\t"
  },
  {
    "path": "css/Pages/Pages.css",
    "chars": 1955,
    "preview": "@import url(Home.css);\n@import url(Services.css);\n@import url(More/More.css);\n\n.page {\n\tposition: fixed;\n\ttop: 0;\n\tleft:"
  },
  {
    "path": "css/Pages/Services.css",
    "chars": 1234,
    "preview": ".boxes {\n\tdisplay: flex;\n\tflex: 1 1 0;\n\tflex-wrap: wrap;\n}\n\n.box {\n\tmin-width: 292px;\n\tflex: 1;\n\tmargin: 2px;\n\tborder-ra"
  },
  {
    "path": "css/main.css",
    "chars": 384,
    "preview": "@import url(../fonts/fonts.css);\n\n@import url(Flags/Flags.css);\n@import url(Background.css);\n@import url(Pages/Pages.css"
  },
  {
    "path": "docker-compose.yaml",
    "chars": 191,
    "preview": "services:\n  honey:\n    image: ghcr.io/dani3l0/honey:latest\n    container_name: honey\n    restart: unless-stopped\n    vol"
  },
  {
    "path": "entrypoint.sh",
    "chars": 80,
    "preview": "#!/bin/sh\ncp -rnv /app/public/config/* /app/dist/config\nnpm run preview\nexit $?\n"
  },
  {
    "path": "fonts/MaterialSymbolsRounded/MaterialSymbolsRounded.css",
    "chars": 521,
    "preview": "@font-face {\n\tfont-family: 'Material Symbols Rounded';\n\tfont-style: normal;\n\tfont-weight: 500;\n\tsrc: url(MaterialSymbols"
  },
  {
    "path": "fonts/Quicksand/Quicksand.css",
    "chars": 1313,
    "preview": "/* quicksand-regular-latin */\n@font-face {\n\tfont-family: Quicksand;\n\tfont-style: normal;\n\tfont-weight: 400;\n\tsrc: local("
  },
  {
    "path": "fonts/fonts.css",
    "chars": 102,
    "preview": "@import url(Quicksand/Quicksand.css);\n@import url(MaterialSymbolsRounded/MaterialSymbolsRounded.css);\n"
  },
  {
    "path": "index.html",
    "chars": 1946,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale="
  },
  {
    "path": "js/App.js",
    "chars": 618,
    "preview": "import Drawer from \"./UI/Drawer/Drawer\"\nimport Home from \"./UI/Home/Home\"\nimport Main from \"./UI/Main/Main\"\nimport More "
  },
  {
    "path": "js/UI/Drawer/Drawer.js",
    "chars": 1101,
    "preview": "import App from \"../../App\";\nimport PingService from \"../../Utils/PingService\";\n\nexport default class Drawer {\n\tconstruc"
  },
  {
    "path": "js/UI/Home/Home.js",
    "chars": 1127,
    "preview": "import App from \"../../App\"\nimport { showPage } from \"../../Utils/DOMUtils\"\n\n\nexport default class Home {\n\tconstructor()"
  },
  {
    "path": "js/UI/Main/Main.js",
    "chars": 860,
    "preview": "import App from \"../../App\"\n\nexport default class Main {\n\tconstructor() {\n\t\tthis.app = new App()\n\t\tthis.config = this.ap"
  },
  {
    "path": "js/UI/More/More.js",
    "chars": 1008,
    "preview": "import App from \"../../App\";\nimport Overview from \"./Overview/Overview\";\nimport Settings from \"./Settings/Settings\";\n\n\ne"
  },
  {
    "path": "js/UI/More/Overview/Overview.js",
    "chars": 2077,
    "preview": "import App from \"../../../App\";\nimport { analyzeService } from \"./analyzer\";\nimport { privacyBox } from \"./tiles\";\nimpor"
  },
  {
    "path": "js/UI/More/Overview/analyzer.js",
    "chars": 789,
    "preview": "export function analyzeService(url, whitelist) {\n\tlet isSiteSecure = (\n\t\twindow.location.protocol == \"https:\" ||\n\t\twindo"
  },
  {
    "path": "js/UI/More/Overview/tiles.js",
    "chars": 463,
    "preview": "export function privacyBox(icon, color, name, desc, pp) {\n\tif (pp == 1) {}\n\telse if (pp > 0.7) color = \"#EA0\"\n\telse if ("
  },
  {
    "path": "js/UI/More/Settings/Settings.js",
    "chars": 1819,
    "preview": "import App from \"../../../App\";\nimport { addOnOffTile, addOptionsTile } from \"./tiles\";\nimport * as EVENTS from \"./event"
  },
  {
    "path": "js/UI/More/Settings/events.js",
    "chars": 1430,
    "preview": "import App from \"../../../App\";\n\nconst CL = document.body.classList\n\n// Switch between light & dark themes\nvar onThemeCh"
  },
  {
    "path": "js/UI/More/Settings/tiles.js",
    "chars": 1972,
    "preview": "export function addOnOffTile(conf, icon, name, desc, key, func) {\n\tlet item = document.createElement(\"div\")\n\titem.classL"
  },
  {
    "path": "js/Utils/Config.js",
    "chars": 943,
    "preview": "export default class Config {\n\n\t// Initialization & localStorage availability check\n\tconstructor(config) {\n\t\tthis.config"
  },
  {
    "path": "js/Utils/DOMUtils.js",
    "chars": 363,
    "preview": "export function showPage(target) {\n\tlet bg = document.querySelector(\"#background\").classList\n\tif (target == \"home\") bg.a"
  },
  {
    "path": "js/Utils/PingService.js",
    "chars": 296,
    "preview": "export default function PingService(uri, callback) {\n    let xhr = new XMLHttpRequest()\n    xhr.open(\"GET\", uri)\n    xhr"
  },
  {
    "path": "js/Utils/StringUtils.js",
    "chars": 149,
    "preview": "export function s(number) {\n\tif (number == 1) return \"\"\n\treturn \"s\"\n}\n\nexport function isare(number) {\n\tif (number == 1)"
  },
  {
    "path": "js/main.js",
    "chars": 269,
    "preview": "import App from \"./App\"\n\nwindow.addEventListener(\"DOMContentLoaded\", () => {\n\tlet xhr = new XMLHttpRequest()\n\txhr.open(\""
  },
  {
    "path": "package.json",
    "chars": 231,
    "preview": "{\n  \"name\": \"honey\",\n  \"private\": true,\n  \"version\": \"2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"bui"
  }
]

About this extraction

This page contains the full source code of the dani3l0/honey GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (34.8 KB), approximately 11.2k tokens, and a symbol index with 48 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!