## 🚀 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 `` 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: '