docs: overhaul README for professional project presentation

Restructure README with compelling hero section, "Why Oikos?" philosophy
section, two-column feature grid, tablet screenshot gallery, and streamlined
quick start. Add GitHub Private Vulnerability Reporting link to SECURITY.md.
Include social preview HTML template for GitHub social card generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas
2026-03-31 17:34:23 +02:00
parent 9e8338c9b9
commit be8af0f154
3 changed files with 379 additions and 124 deletions
+185 -123
View File
@@ -1,5 +1,5 @@
<p align="center">
<!-- Replace with your logo: recommended 120160px, SVG or PNG, transparent background -->
<!-- Replace with your logo when ready: recommended 120160px, SVG or PNG, transparent background -->
<!-- <img src="docs/logo.svg" alt="Oikos" width="140"> -->
<img src="https://img.shields.io/badge/%F0%9F%8F%A0-Oikos-007AFF?style=for-the-badge&labelColor=F5F5F7" alt="Oikos" height="48">
</p>
@@ -7,44 +7,57 @@
<h1 align="center">Oikos</h1>
<p align="center">
<strong>Self-hosted family planner — tasks, calendars, shopping, meals, budget.</strong><br>
Your data stays on your server. No subscriptions. No tracking. No cloud lock-in.
<strong>The self-hosted family planner that respects your privacy.</strong><br>
Tasks, calendars, shopping, meals, budget, notes, contacts — <br>
all in one place, on your own server.
</p>
<p align="center">
<a href="https://github.com/ulsklyc/oikos/releases"><img src="https://img.shields.io/github/v/release/ulsklyc/oikos?style=flat-square&color=007AFF&label=release" alt="Latest Release"></a>
<a href="https://github.com/ulsklyc/oikos/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D22-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node.js ≥22"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A522-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node.js ≥22"></a>
<a href="https://www.docker.com"><img src="https://img.shields.io/badge/docker-ready-2496ED?style=flat-square&logo=docker&logoColor=white" alt="Docker Ready"></a>
<a href="https://www.zetetic.net/sqlcipher/"><img src="https://img.shields.io/badge/SQLCipher-AES--256-003B57?style=flat-square&logo=sqlite&logoColor=white" alt="SQLCipher Encrypted"></a>
<a href="https://web.dev/progressive-web-apps/"><img src="https://img.shields.io/badge/PWA-offline--capable-5A0FC8?style=flat-square&logo=pwa&logoColor=white" alt="PWA Offline"></a>
<a href="https://github.com/ulsklyc/oikos/stargazers"><img src="https://img.shields.io/github/stars/ulsklyc/oikos?style=flat-square&color=f5c542" alt="GitHub Stars"></a>
<a href="https://github.com/ulsklyc/oikos/commits/main"><img src="https://img.shields.io/github/last-commit/ulsklyc/oikos?style=flat-square" alt="Last Commit"></a>
</p>
<p align="center">
<a href="#features">Features</a> · <a href="#quick-start">Quick Start</a> · <a href="#configuration">Configuration</a> · <a href="#calendar-sync">Calendar Sync</a> · <a href="#security">Security</a>
<a href="#-screenshots">Screenshots</a>&ensp;·&ensp;
<a href="#-features">Features</a>&ensp;·&ensp;
<a href="#-quick-start">Quick Start</a>&ensp;·&ensp;
<a href="#-security">Security</a>&ensp;·&ensp;
<a href="#-contributing">Contributing</a>
</p>
---
Oikos is a self-hosted family organizer for 26 people. Tasks, calendars, shopping lists, meal plans, budget tracking, notes, and contacts — all running on your own server inside a single Docker container. No cloud dependency, no telemetry, no data leaves your network.
Built with Express.js, SQLite (optionally encrypted via SQLCipher), and vanilla JavaScript — no frontend framework, no build step. Works offline as a PWA on phones and tablets.
Oikos is **not** a SaaS product, not a team collaboration tool, and not designed for public multi-tenant use. It is a private tool for one family on one server.
---
## Screenshots
<br>
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/tablet-dark/tablet-dark-dashboard.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/tablet-light/tablet-light-dashboard.png">
<img src="docs/screenshots/tablet-light/tablet-light-dashboard.png" alt="Dashboard" width="720">
<img src="docs/screenshots/tablet-light/tablet-light-dashboard.png" alt="Oikos Dashboard" width="720">
</picture>
</p>
---
## Why Oikos?
Most family organizers are cloud apps with monthly subscriptions, data mining, and vendor lock-in. Oikos takes a different approach:
- **Your server, your data** — runs in a single Docker container on your own hardware. Nothing leaves your network.
- **No subscriptions** — free and open source, forever. MIT licensed.
- **No tracking** — zero telemetry, zero analytics, zero third-party scripts.
- **Offline-first** — works as a PWA on phones and tablets, even without connectivity.
- **Encrypted at rest** — optional AES-256 database encryption via SQLCipher.
- **Lightweight** — vanilla JavaScript frontend with no framework and no build step. Express + SQLite backend. Minimal resource footprint.
Oikos is designed for **one family on one server** — not a SaaS product, not a team tool, not multi-tenant. It's a private, self-contained household organizer for 26 people.
---
## 📸 Screenshots
<table>
<tr>
<td align="center" width="33%">
@@ -95,34 +108,113 @@ Oikos is **not** a SaaS product, not a team collaboration tool, and not designed
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/mobile-light/mobile-light-dashboard.png">
<img src="docs/screenshots/mobile-light/mobile-light-dashboard.png" alt="Dashboard Mobile" width="240">
</picture>
<br><strong>Dashboard — Mobile</strong>
<br><strong>Dashboard</strong>
</td>
</tr>
</table>
<details>
<summary>Tablet views</summary>
<br>
<table>
<tr>
<td align="center" width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/tablet-dark/tablet-dark-tasks.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/tablet-light/tablet-light-tasks.png">
<img src="docs/screenshots/tablet-light/tablet-light-tasks.png" alt="Tasks — Tablet" width="480">
</picture>
<br><strong>Tasks</strong>
</td>
<td align="center" width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/tablet-dark/tablet-dark-household.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/tablet-light/tablet-light-household.png">
<img src="docs/screenshots/tablet-light/tablet-light-household.png" alt="Shopping — Tablet" width="480">
</picture>
<br><strong>Shopping</strong>
</td>
</tr>
<tr>
<td align="center" width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/tablet-dark/tablet-dark-notes.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/tablet-light/tablet-light-notes.png">
<img src="docs/screenshots/tablet-light/tablet-light-notes.png" alt="Notes — Tablet" width="480">
</picture>
<br><strong>Notes</strong>
</td>
<td align="center" width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/tablet-dark/tablet-dark-budget.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/tablet-light/tablet-light-budget.png">
<img src="docs/screenshots/tablet-light/tablet-light-budget.png" alt="Budget — Tablet" width="480">
</picture>
<br><strong>Budget</strong>
</td>
</tr>
<tr>
<td align="center" width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/tablet-dark/tablet-dark-contacts.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/tablet-light/tablet-light-contacts.png">
<img src="docs/screenshots/tablet-light/tablet-light-contacts.png" alt="Contacts — Tablet" width="480">
</picture>
<br><strong>Contacts</strong>
</td>
<td></td>
</tr>
</table>
</details>
<p align="center">
<sub>Screenshots adapt to your GitHub theme — switch between light and dark mode to see both variants.</sub>
<sub>Screenshots adapt to your GitHub theme — toggle light/dark mode to see both variants.</sub>
</p>
---
## Features
## Features
| | Module | What it does | Highlights |
|---|---|---|---|
| 📋 | **Dashboard** | At-a-glance overview of your family's day | Weather widget · upcoming events · urgent tasks · today's meals · pinned notes |
| ✅ | **Tasks** | Shared to-do lists with accountability | List + Kanban views · subtasks · recurring tasks (RRULE) · swipe gestures · priority levels |
| 🛒 | **Shopping** | Collaborative grocery lists | Multiple lists · aisle-grouped categories · auto-import from meal plan |
| 🍽️ | **Meals** | Weekly meal planning with ingredients | Week view (MonSun) · ingredient management · one-click export to shopping list |
| 📅 | **Calendar** | Family calendar with external sync | Month/week/day/agenda views · Google Calendar & Apple iCloud two-way sync |
| 📌 | **Notes** | Shared family pinboard | Colored sticky notes · pinning · full-text search · lightweight Markdown (bold, italic, lists) |
| 👥 | **Contacts** | Important family contacts | Category filters · tap-to-call · tap-to-email · map links · vCard import/export |
| 💰 | **Budget** | Income & expense tracking | Category breakdown · month-over-month comparison · CSV export |
| ⚙️ | **Settings** | User & sync management | Password changes · calendar sync config · family member admin |
<table>
<tr>
<td width="50%">
**📋 Dashboard**
At-a-glance family overview — weather, upcoming events, urgent tasks, today's meals, pinned notes.
**✅ Tasks**
List + Kanban views, subtasks, recurring tasks (RRULE), swipe gestures, priority levels.
**🛒 Shopping**
Multiple lists, aisle-grouped categories, auto-import from meal plan, swipe to check off.
**🍽️ Meals**
Weekly planner (MonSun), drag & drop between slots, ingredients, one-click shopping list export.
</td>
<td width="50%">
**📅 Calendar**
Month / week / day / agenda views. Two-way sync with Google Calendar and Apple iCloud.
**📌 Notes**
Colored sticky notes, pinning, full-text search, Markdown formatting toolbar.
**👥 Contacts**
Category filters, tap-to-call/email, map links, vCard import & export.
**💰 Budget**
Income & expense tracking, recurring entries, month-over-month trends, CSV export.
</td>
</tr>
</table>
**And also:** dark mode with system detection · responsive design (mobile / tablet / desktop) · offline PWA with install prompt · per-module accent colors · accessibility (skip links, ARIA, reduced motion) · staggered animations · bottom sheet modals on mobile.
---
## Tech Stack
## 🛠 Tech Stack
<p>
<img src="https://img.shields.io/badge/Express-000000?style=flat-square&logo=express&logoColor=white" alt="Express">
@@ -133,78 +225,75 @@ Oikos is **not** a SaaS product, not a team collaboration tool, and not designed
</p>
| Layer | Technology |
|---|---|
|:---|:---|
| **Server** | Node.js ≥ 22 · Express · better-sqlite3 · bcrypt · Helmet |
| **Database** | SQLite with optional SQLCipher encryption (AES-256) |
| **Frontend** | Vanilla JavaScript ES modules — no framework, no build step. Web Components (`oikos-*`). Lucide Icons (self-hosted SVG sprite) |
| **Auth** | Session-based · httpOnly cookies · CSRF double-submit · express-session |
| **Database** | SQLite with optional [SQLCipher](https://www.zetetic.net/sqlcipher/) encryption (AES-256) |
| **Frontend** | Vanilla JavaScript ES modules — no framework, no build step. [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) (`oikos-*`). [Lucide Icons](https://lucide.dev) (self-hosted) |
| **Auth** | Session-based · httpOnly cookies · CSRF double-submit · [express-session](https://github.com/expressjs/session) |
| **Deployment** | Docker + Docker Compose · Nginx reverse proxy · Let's Encrypt SSL |
| **Integrations** | Google Calendar API v3 (OAuth 2.0) · Apple iCloud CalDAV (tsdav) · OpenWeatherMap |
| **Integrations** | [Google Calendar API v3](https://developers.google.com/calendar) (OAuth 2.0) · [Apple iCloud CalDAV](https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/) (tsdav) · [OpenWeatherMap](https://openweathermap.org/api) |
---
## Quick Start
## 🚀 Quick Start
**Prerequisites:** Docker + Docker Compose on a Linux server.
### 1. Clone
> **Prerequisites:** Docker and Docker Compose installed on a Linux server.
```bash
git clone https://github.com/ulsklyc/oikos.git
cd oikos
```
# 1. Clone the repository
git clone https://github.com/ulsklyc/oikos.git && cd oikos
### 2. Configure
```bash
# 2. Configure environment
cp .env.example .env
```
# Edit .env — set SESSION_SECRET (≥32 chars) and optionally DB_ENCRYPTION_KEY
Edit `.env` and set the two required variables:
```env
SESSION_SECRET=your-random-string-at-least-32-chars
DB_ENCRYPTION_KEY=your-sqlcipher-aes256-key
```
### 3. Start
```bash
# 3. Start the container
docker compose up -d
```
# First build takes 23 minutes (compiles SQLCipher)
First build takes 23 minutes (compiles SQLCipher against better-sqlite3).
### 4. Create admin account
```bash
# 4. Create your admin account
docker compose exec oikos node setup.js
# 5. Open http://localhost:3000
```
Interactive script — sets up username, display name, and password. This admin can create additional family members from the settings page.
The admin can create additional family members from **Settings → Family Members**.
### 5. Open
> **Production:** See [`nginx.conf.example`](nginx.conf.example) for a reverse proxy config with SSL. If you use [Nginx Proxy Manager](https://nginxproxymanager.com), paste the contents into the Advanced tab. Ensure `X-Forwarded-Proto` is set for session cookies to work correctly.
Navigate to `http://localhost:3000` — or your configured domain after Nginx setup.
---
> **Nginx:** See [`nginx.conf.example`](nginx.conf.example) for a production-ready config. If you use [Nginx Proxy Manager](https://nginxproxymanager.com), paste the contents into the "Advanced" tab. Make sure `X-Forwarded-Proto` is set so session cookies work correctly in production.
## 🔒 Security
Oikos is designed to run on a private server behind SSL. No public endpoints exist except the login page.
| Layer | Implementation |
|:---|:---|
| **Sessions** | `httpOnly`, `SameSite=Strict`, `Secure` in production, 7-day TTL |
| **CSRF** | Double-submit cookie pattern on all state-changing requests |
| **Passwords** | bcrypt with cost factor 12 |
| **Rate limiting** | 5 login attempts/min, 300 API requests/min per IP |
| **Headers** | Strict Content Security Policy via Helmet (`self`-only) |
| **Encryption** | Optional SQLCipher AES-256 database encryption at rest |
| **Access control** | No API endpoint accessible without session auth (except login) |
| **Registration** | Disabled — only admins can create user accounts |
---
<details>
<summary><h2>Configuration</h2></summary>
<summary><strong>Configuration</strong></summary>
### Required
| Variable | Description |
|---|---|
|:---|:---|
| `SESSION_SECRET` | Random string ≥ 32 characters for session signing |
| `DB_ENCRYPTION_KEY` | SQLCipher AES-256 key. Leave empty to disable encryption |
| `DB_ENCRYPTION_KEY` | SQLCipher AES-256 key — leave empty to disable encryption |
### Optional
| Variable | Default | Description |
|---|---|---|
|:---|:---|:---|
| `PORT` | `3000` | Server port |
| `NODE_ENV` | `development` | Set to `production` for deployment |
| `DB_PATH` | `./oikos.db` | Path to SQLite database file |
@@ -216,7 +305,7 @@ Navigate to `http://localhost:3000` — or your configured domain after Nginx se
Register a free API key at [openweathermap.org](https://openweathermap.org/api):
| Variable | Default | Description |
|---|---|---|
|:---|:---|:---|
| `OPENWEATHER_API_KEY` | — | Your API key |
| `OPENWEATHER_CITY` | `Berlin` | City name |
| `OPENWEATHER_UNITS` | `metric` | `metric` (°C) or `imperial` (°F) |
@@ -225,7 +314,7 @@ Register a free API key at [openweathermap.org](https://openweathermap.org/api):
### Integrations
| Variable | Description |
|---|---|
|:---|:---|
| `GOOGLE_CLIENT_ID` | Google OAuth 2.0 Client ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth 2.0 Client Secret |
| `GOOGLE_REDIRECT_URI` | `https://your-domain/api/v1/calendar/google/callback` |
@@ -237,10 +326,8 @@ Full template: [`.env.example`](.env.example)
</details>
---
<details>
<summary><h2>Calendar Sync</h2></summary>
<summary><strong>Calendar Sync</strong></summary>
Oikos syncs bidirectionally with Google Calendar and Apple iCloud. External events are visually distinguished in the UI. On conflict, the external source wins.
@@ -266,13 +353,13 @@ Oikos syncs bidirectionally with Google Calendar and Apple iCloud. External even
### Apple Calendar (iCloud CalDAV)
**Option A — via Settings UI (recommended, no restart required):**
**Option A — via Settings UI** (recommended, no restart required):
1. Go to [appleid.apple.com](https://appleid.apple.com) → Sign-In and Security → App-Specific Passwords
2. Generate a new password for "Oikos"
3. In Oikos: **Settings → Calendar Sync → Apple Calendar** → enter the CalDAV URL, Apple ID email and the app-specific password → click **Verbinden & testen**
3. In Oikos: **Settings → Calendar Sync → Apple Calendar** → enter CalDAV URL, Apple ID, and app-specific password → click **Verbinden & testen**
Credentials are stored in the database. No server restart required.
Credentials are stored in the database. No server restart needed.
**Option B — via `.env`:**
@@ -282,29 +369,12 @@ APPLE_USERNAME=your@apple-id.com
APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
```
Restart: `docker compose up -d`. The sync button appears automatically in Settings. `.env`-credentials are used as fallback when no UI-credentials are saved.
Restart: `docker compose up -d`. The sync button appears automatically in Settings. `.env` credentials are used as fallback when no UI credentials are saved.
</details>
---
## Security
Oikos is designed to run on a private server behind SSL. No public endpoints exist except the login page.
- **Sessions** — `httpOnly`, `SameSite=Strict`, `Secure` in production, 7-day TTL
- **CSRF** — Double-submit cookie pattern on all state-changing requests
- **Passwords** — bcrypt with cost factor 12
- **Rate limiting** — 5 login attempts/min, 300 API requests/min per IP
- **Headers** — Strict Content Security Policy via Helmet (`self`-only)
- **Encryption** — Optional SQLCipher AES-256 database encryption at rest
- **Access control** — No API endpoint accessible without session auth (except `/api/v1/auth/login`)
- **No public registration** — Only admins can create user accounts
---
<details>
<summary><h2>Development</h2></summary>
<summary><strong>Development</strong></summary>
### Local Setup
@@ -318,7 +388,7 @@ npm run dev # Starts server with --watch (auto-reload)
### Tests
```bash
npm test # 146 tests across 7 suites
npm test # 146+ tests across 9 suites
```
Tests use Node.js built-in test runner with `--experimental-sqlite` for in-memory SQLite. No running server required.
@@ -330,6 +400,7 @@ server/
index.js # Express entry, middleware, static serving
db.js # SQLite connection, migration runner
auth.js # Session auth + user management routes
middleware/ # CSRF, input validation
routes/ # One file per module
services/ # Calendar sync, recurrence engine
public/
@@ -339,19 +410,17 @@ public/
styles/ # Design tokens, reset, layout, per-module CSS
components/ # Web Components (oikos-* prefix)
pages/ # Page modules with render() export
sw.js # Service worker
sw.js # Service worker (app-shell caching)
```
**Request flow:** Client → Express static or `/api/v1/*` → session auth middleware → route handler → better-sqlite3 (sync) → JSON response.
**Request flow:** Browser → Express static (`public/`) or `/api/v1/*` → session auth middleware → route handler → better-sqlite3 (sync) → JSON response.
**Database migrations** run automatically on startup. Each migration is an idempotent SQL block in `server/db.js`. Append new migrations — never modify existing ones.
</details>
---
<details>
<summary><h2>Backup & Restore</h2></summary>
<summary><strong>Backup & Restore</strong></summary>
### Backup
@@ -373,16 +442,12 @@ docker run --rm \
docker compose up -d
```
Database migrations run automatically on startup. Data in the `oikos_data` volume is preserved across container rebuilds.
Migrations run automatically on startup. Data in the `oikos_data` volume is preserved across container rebuilds.
</details>
---
<details>
<summary><h2>Updates & Family Members</h2></summary>
### Updating Oikos
<summary><strong>Updates</strong></summary>
```bash
git pull
@@ -393,19 +458,18 @@ Migrations run automatically. Your data volume stays intact.
### Adding Family Members
Only admins can create new accounts — there is no public registration endpoint.
Only admins can create accounts — there is no public registration.
**In the browser:** Settings → Family Members → Add Member
**Via CLI:** `docker compose exec oikos node setup.js`
- **In the browser:** Settings → Family Members → Add Member
- **Via CLI:** `docker compose exec oikos node setup.js`
</details>
---
## Contributing
## 🤝 Contributing
Contributions are welcome. If you find a bug or have a feature idea, [open an issue](https://github.com/ulsklyc/oikos/issues). Pull requests are appreciated — please keep the vanilla JS constraint in mind (no frameworks, no build tools).
Contributions are welcome! If you find a bug or have a feature idea, [open an issue](https://github.com/ulsklyc/oikos/issues). Pull requests are appreciated — please keep the vanilla JS constraint in mind (no frameworks, no build tools).
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup instructions, code conventions, commit format, and workflow.
@@ -413,10 +477,8 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup instructions, code convention
## License
[MIT](LICENSE) © 2025 ulsklyc
---
[MIT](LICENSE) © 2026 ulsklyc
<p align="center">
Made with ☕ by <a href="https://github.com/ulsklyc">ulsklyc</a>
<sub>Made with care for families who value their privacy.</sub>
</p>
+1 -1
View File
@@ -4,7 +4,7 @@
If you discover a security vulnerability in Oikos, please report it responsibly. **Do not open a public issue.**
Instead, use [GitHub Private Vulnerability Reporting](https://github.com/ulsklyc/oikos/security/advisories/new) to submit your report. This creates a private advisory visible only to you and the maintainers.
Include:
+193
View File
@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Oikos — Social Preview</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1280px;
height: 640px;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #0a0f1a 0%, #141c2e 50%, #1a2540 100%);
display: flex;
color: #fff;
}
.left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding: 64px 56px;
z-index: 2;
}
.badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(0, 122, 255, 0.15);
border: 1px solid rgba(0, 122, 255, 0.3);
border-radius: 20px;
padding: 6px 16px;
font-size: 13px;
font-weight: 600;
color: #5ac8fa;
letter-spacing: 0.5px;
text-transform: uppercase;
margin-bottom: 24px;
width: fit-content;
}
.title {
font-size: 72px;
font-weight: 800;
letter-spacing: -2px;
line-height: 1;
margin-bottom: 16px;
background: linear-gradient(135deg, #ffffff 0%, #c8d6e5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.tagline {
font-size: 22px;
font-weight: 400;
color: #8899b0;
line-height: 1.5;
max-width: 440px;
margin-bottom: 32px;
}
.features {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.feature {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
padding: 8px 14px;
font-size: 13px;
font-weight: 500;
color: #a0b0c4;
}
.feature-icon {
font-size: 15px;
}
.right {
width: 560px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.screenshot-wrapper {
position: relative;
transform: perspective(1200px) rotateY(-8deg) rotateX(2deg);
border-radius: 12px;
overflow: hidden;
box-shadow:
0 24px 80px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.08);
}
.screenshot-wrapper img {
display: block;
width: 500px;
height: auto;
border-radius: 12px;
}
/* Gradient overlay on the screenshot edges */
.screenshot-wrapper::after {
content: '';
position: absolute;
inset: 0;
border-radius: 12px;
background: linear-gradient(90deg, rgba(10, 15, 26, 0.4) 0%, transparent 30%, transparent 100%);
pointer-events: none;
}
/* Subtle glow behind screenshot */
.right::before {
content: '';
position: absolute;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(0, 122, 255, 0.12) 0%, transparent 70%);
border-radius: 50%;
z-index: -1;
}
.footer {
position: absolute;
bottom: 28px;
left: 56px;
display: flex;
align-items: center;
gap: 16px;
font-size: 13px;
color: #556680;
}
.footer span {
display: inline-flex;
align-items: center;
gap: 5px;
}
.dot {
width: 3px;
height: 3px;
border-radius: 50%;
background: #556680;
}
</style>
</head>
<body>
<div class="left">
<div class="badge">Self-Hosted &middot; Open Source</div>
<div class="title">Oikos</div>
<p class="tagline">The family planner that respects your privacy. Tasks, calendars, shopping, meals, budget &mdash; on your own server.</p>
<div class="features">
<span class="feature"><span class="feature-icon">&#x2705;</span> Tasks</span>
<span class="feature"><span class="feature-icon">&#x1F4C5;</span> Calendar</span>
<span class="feature"><span class="feature-icon">&#x1F6D2;</span> Shopping</span>
<span class="feature"><span class="feature-icon">&#x1F35D;</span> Meals</span>
<span class="feature"><span class="feature-icon">&#x1F4B0;</span> Budget</span>
<span class="feature"><span class="feature-icon">&#x1F4CC;</span> Notes</span>
<span class="feature"><span class="feature-icon">&#x1F465;</span> Contacts</span>
<span class="feature"><span class="feature-icon">&#x1F512;</span> Encrypted</span>
</div>
</div>
<div class="right">
<!-- Replace with your actual tablet screenshot path -->
<div class="screenshot-wrapper">
<img src="../docs/screenshots/tablet-light/tablet-light-dashboard.png" alt="Dashboard">
</div>
</div>
<div class="footer">
<span>MIT License</span>
<div class="dot"></div>
<span>Docker &middot; Express &middot; SQLite &middot; Vanilla JS</span>
<div class="dot"></div>
<span>github.com/ulsklyc/oikos</span>
</div>
</body>
</html>