21 Commits

Author SHA1 Message Date
m
0b2b718410 fix: zensiebels.de — ZenSebels → ZenSiebels 2026-04-03 19:13:32 +02:00
m
68552cac15 fix: rename zensebels.de → zensiebels.de (Siebels not Sebels) 2026-04-02 14:55:14 +02:00
m
8af8d3d35f feat: add zensebels.de — Zen/meditation onepager, japanese aesthetics 2026-04-02 14:53:21 +02:00
m
596ccac889 fix: billableaua — 'nicht nur ein Job' 2026-04-02 10:57:42 +02:00
m
d3efd8231f fix: billableaua — lifestyle threshold at 7.96 (rounds to 8.0) 2026-04-02 10:56:46 +02:00
m
e6d397a77b fix: billableaua — >= 8h threshold for lifestyle verdict 2026-04-02 10:52:14 +02:00
m
c9f97eb43f fix: billableaua — center invoice, thresholds 6/7/8, new verdicts 2026-04-02 10:50:59 +02:00
m
8992f6775f refactor: billableaua — invoice-style layout, lower thresholds
Calculation now looks like an invoice with left-aligned labels and
right-aligned values. Verdict thresholds lowered: >8h lifestyle,
>6h warning, >4h "klingt machbar".
2026-04-02 10:47:32 +02:00
m
7702963902 fix: billableaua — 'Minuten mehr an jedem anderen Tag. Jeden.' 2026-04-02 10:45:17 +02:00
m
536e693b18 fix: billableaua — 'Jeden Tag.' 2026-04-02 10:44:54 +02:00
m
6596ac14fa fix: billableaua — show only delta minutes per missed day 2026-04-02 10:44:27 +02:00
m
cfa491c47e fix: billableaua — show missed day impact in minutes 2026-04-02 10:42:42 +02:00
m
f7b5439387 feat: billableaua calculator — show impact of missed days 2026-04-02 10:41:57 +02:00
m
327e1fcd43 fix: billableaua calculator default to 1800h 2026-04-02 10:41:05 +02:00
m
6aa3d79d20 feat: billableaua — add line about unbilled office hours 2026-04-02 10:35:56 +02:00
m
3d4dd8c49a fix: billableaua — sharper line about unbilled work 2026-04-02 10:34:24 +02:00
m
5173611b46 fix: billableaua — rephrase idea line, remove 'ohne Atmen' 2026-04-02 10:30:49 +02:00
m
069a2a3b4a refactor: billableaua critique — deeper, structural criticism
Remove Partnertrack + client-pays items. New focus: the systemic
misalignment between billable hours and actual value creation.
Inefficiency rewarded, creativity happens off-clock, 6-minute
consciousness, innovation as revenue threat.
2026-04-02 10:28:34 +02:00
m
882179d533 refactor: billableaua — remove concrete numbers from intro, move calculator down
Intro now focuses on billable hours as the sole KPI metric rather than
specific numbers. Calculator moved below the critique section. Removed
the "8h work = 6.5 billable" item (too specific for the new tone).
2026-04-02 10:23:55 +02:00
m
b4d2ef7991 fix: billableaua calculator — step-by-step breakdown, correct labels
Show the full calculation as a written equation:
365 - 52x2=104 Wochenendtage - Urlaub - Feiertage = Arbeitstage.
Fixes "104 Wochenenden" → "52 x 2 = 104 Wochenendtage".
2026-04-02 10:22:39 +02:00
m
9a92d9651f feat: billableaua.de — add billable hours calculator
Interactive calculator: enter annual hours target + vacation days,
shows actual billable hours per working day after subtracting weekends,
vacation, and public holidays. Makes the absurdity of 2400h targets visible.
2026-04-02 10:20:38 +02:00
31 changed files with 931 additions and 9 deletions

79
.claude/CLAUDE.md Normal file
View File

@@ -0,0 +1,79 @@
# onepager Project Instructions
## Project Overview
Mono-repo for 57+ vanity domain onepager sites. Single Caddy container with bash template system and host-based routing. Most domains are creative AI/KI wordplay (kAInco, kIlemma, orAKIl, etc.).
**Deploy:** Push to main -> Dokploy auto-deploys. All domains must be configured in Dokploy.
## Architecture
```
sites/<domain>/ # One folder per domain
site.yaml # Config: domain, template, vars
index.html # Content (rendered or hand-crafted for custom)
assets/ # Optional images, fonts
templates/ # 6 HTML templates + base.html
base.html # Shared skeleton (CSS includes, meta tags)
person-dark.html # Professional profile, dark theme
person-light.html # Professional profile, light/cream theme
product-dark.html # Product/service landing, dark
editorial.html # Long-form manifesto/editorial
fun.html # Playful/personal pages
minimal.html # Bare-bones single section
shared/
css/ # variables.css, responsive.css, animations.css, noise.css
fonts.html # Google Fonts includes
impressum.js # Shared impressum overlay
build/ # Generated output (gitignored)
Caddyfile # Generated by generate-caddyfile.sh (committed)
```
### Build Pipeline
1. `generate-caddyfile.sh` reads all `sites/*/site.yaml` -> generates Caddyfile with host matchers
2. `build.sh` orchestrates: generates Caddyfile, renders all sites, copies shared assets to `build/`
3. `render.sh` takes `site.yaml` + template -> outputs rendered HTML (bash/yq/awk templating)
4. Docker: Alpine builder runs `build.sh`, then Caddy serves from `/srv/`
### Template System
Templates use `{{placeholder}}` markers. `render.sh` reads vars from `site.yaml` and substitutes. Templates define CSS between `{{template_css_start}}`/`{{template_css_end}}` and body between `{{template_body_start}}`/`{{template_body_end}}`. Base template assembles shared CSS + template CSS + body.
Available vars: `title`, `description`, `lang`, `name`, `role`, `initials`, `tagline`, `accent`, `accent_light`, `font_primary`, `font_secondary`, `emoji`, `cta_text`, `cta_href`, `tags_html`, `sections_html`, `content_html`, `domain`, `year`.
### Custom Sites
Sites with `template: custom` skip rendering entirely -- their `index.html` is copied as-is. Many sites use custom for complex interactive content (e.g., orakil.de oracle, ichbinotto.de agent profile).
## Code Style & Conventions
- **Shell scripts**: bash, `set -euo pipefail`, use yq for YAML parsing
- **HTML/CSS**: Inline styles in templates. Shared CSS via variables.css. Dark themes predominant.
- **Commit messages**: `feat:` for new sites, `fix:` for corrections, `refactor:` for restructuring
- **Site naming**: domain name = folder name under `sites/`
- **Language**: Default `de` (German). Sites are primarily German-language.
## Adding a New Site
```bash
# Templated
./add-site.sh example.de --template person-dark --name "Max Mustermann"
# Custom
./add-site.sh example.de --template custom
# Then edit sites/example.de/index.html
```
After adding: build locally with `./build.sh` to verify, commit, push.
## Deploy Trigger
A `.deploy-trigger` file exists -- changing its content forces Dokploy rebuild even without code changes.
## Git Strategy
- **main** = production, auto-deploys via Dokploy
- Feature branches for multi-site changes or infrastructure work
- Direct commits to main OK for single-site additions/fixes (this is a content repo)
- Gitea repo: m/onepager

14
.claude/agents/coder.md Normal file
View File

@@ -0,0 +1,14 @@
# Coder Agent
Implementation-focused agent for writing and refactoring code.
## Instructions
- Follow existing patterns in the codebase
- Write minimal, focused code
- Run tests after changes
- Commit incrementally with descriptive messages
## Tools
All tools available.

View File

@@ -0,0 +1,14 @@
# Researcher Agent
Exploration and information gathering agent.
## Instructions
- Search broadly, then narrow down
- Document findings in structured format
- Cite sources and file paths
- Summarize key insights, don't dump raw data
## Tools
Read-only tools preferred. Use Bash only for non-destructive commands.

View File

@@ -0,0 +1,14 @@
# Reviewer Agent
Code review agent for checking quality and correctness.
## Instructions
- Check for bugs, security issues, and style violations
- Verify test coverage for changes
- Suggest improvements concisely
- Focus on correctness over style preferences
## Tools
Read-only tools. No file modifications.

1
.claude/skills/mai-clone Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-clone

1
.claude/skills/mai-coder Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-coder

1
.claude/skills/mai-commit Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-commit

View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-consultant

1
.claude/skills/mai-daily Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-daily

1
.claude/skills/mai-debrief Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-debrief

1
.claude/skills/mai-enemy Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-enemy

View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-excalidraw

1
.claude/skills/mai-fixer Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-fixer

1
.claude/skills/mai-gitster Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-gitster

1
.claude/skills/mai-head Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-head

1
.claude/skills/mai-init Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-init

1
.claude/skills/mai-inventor Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-inventor

1
.claude/skills/mai-lead Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-lead

1
.claude/skills/mai-maister Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-maister

1
.claude/skills/mai-member Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-member

View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-researcher

1
.claude/skills/mai-think Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-think

1
.claude/skills/mai-web Symbolic link
View File

@@ -0,0 +1 @@
/home/m/.mai/skills/mai-web

4
.m/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
workers.json
spawn.lock
session.yaml
config.reference.yaml

171
.m/config.yaml Normal file
View File

@@ -0,0 +1,171 @@
# Project-specific mai configuration
# Auto-generated by 'mai init' — run 'mai setup' to customize
provider: claude
providers:
claude:
api_key: ""
model: claude-sonnet-4-20250514
base_url: https://api.anthropic.com/v1
ollama:
host: http://localhost:11434
model: llama3.2
memory:
enabled: true
backend: ""
path: ""
url: postgres://mai_memory.your-tenant-id:maiMem6034supa@100.99.98.201:6543/postgres?sslmode=disable
group_id: onepager
cache_ttl: 5m0s
auto_load: true
embedding_url: ""
embedding_model: ""
gitea:
url: https://mgit.msbls.de
repo: m/onepager
token: ""
sync:
enabled: false
interval: 0s
repos: []
auto_queue: false
api:
api_key: ""
basic_auth:
username: ""
password: ""
public_endpoints:
- /api/health
ui:
theme: default
show_sidebar: true
animation: true
persona: true
avatar_pack: ""
worker:
names: []
name_scheme: role
default_level: standard
auto_discard: false
max_workers: 5
persistent: true
head:
name: ""
max_loops: 50
infinity_mode: false
capacity:
global:
max_workers: 5
max_heads: 3
per_worker:
max_tasks_lifetime: 0
max_concurrent: 1
max_context_tokens: 0
per_head:
max_workers: 10
resources:
max_memory_mb: 0
max_cpu_percent: 0
queue:
max_pending: 100
stale_task_days: 30
workforce:
timeouts:
task_default: 0s
task_max: 0s
idle_before_warn: 10m0s
idle_before_kill: 30m0s
quality_check: 2m0s
context:
max_tokens_per_worker: 0
max_tokens_global: 0
warn_threshold: 0.8
truncate_strategy: oldest
delegation:
strategy: skill_match
preferred_role: coder
auto_delegate: false
max_depth: 3
allowed_roles:
- coder
- researcher
- fixer
peppy:
enabled: false
style: calm
interval: 5m0s
emoji: false
nudges: true
nudge_main: false
custom_prompt: ""
stall_threshold: 0s
restart_enabled: false
max_shifts: 0
quality_gates:
enabled: false
checks: []
preflight:
enabled: false
type: ""
root: ""
checks: []
guardrails:
enabled: false
use_defaults: true
output:
coder_checks: []
researcher_checks: []
fixer_checks: []
custom_checks: {}
global_checks: []
tools:
role_rules: {}
deny_patterns: []
allow_patterns: []
schemas:
report_schemas: {}
deliverable_schemas: {}
modes:
yolo: false
self_improvement: true
autonomous: false
verbose: false
improve_interval: 0s
predict_interval: 0s
layouts:
head: ""
worker: ""
roles: {}
dog:
name: buddy
supabase:
url: ""
role_key: ""
anon_key: ""
schema: mai
storage:
backend: ""
postgres:
url: ""
max_conns: 0
min_conns: 0
max_conn_lifetime: 0s
idle:
behavior: wait
auto_hire: false
prompt: ""
git:
worktrees:
enabled: true
delete_branch: false
dir: .worktrees
phase:
enabled: false
current: ""
allowed_roles: {}
goal: "Mono-repo for 57+ vanity domain onepager sites — bash template system, Caddy routing, Dokploy deploy. Creative AI/KI-themed domains."
skills: {}
editor: nvim
log_level: info
project_detection: true
tone: professional

22
.mcp.json Normal file
View File

@@ -0,0 +1,22 @@
{
"mcpServers": {
"mai": {
"type": "http",
"url": "http://100.99.98.201:8000/mcp",
"headers": {
"Authorization": "Basic ${SUPABASE_AUTH}"
}
},
"mai-memory": {
"command": "mai",
"args": [
"mcp",
"memory"
],
"env": {
"MAI_MEMORY_EMBEDDING_MODEL": "nomic-embed-text",
"MAI_MEMORY_EMBEDDING_URL": "https://llm.x.msbls.de"
}
}
}
}

1
.worktrees/knuth Submodule

Submodule .worktrees/knuth added at b49f1ae83e

1
AGENTS.md Symbolic link
View File

@@ -0,0 +1 @@
.claude/CLAUDE.md

View File

@@ -157,6 +157,91 @@
font-weight: 300;
}
/* Rechner */
.rechner {
padding: 120px 0;
}
.rechner h2 {
font-size: clamp(2.5rem, 7vw, 4rem);
font-weight: 900;
letter-spacing: -0.02em;
text-transform: uppercase;
margin-bottom: 48px;
}
.rechner h2 .dot { color: var(--red); }
.rechner-grid {
display: flex;
gap: 32px;
margin-bottom: 48px;
}
.rechner-input {
flex: 1;
}
.rechner-input label {
display: block;
font-size: 0.9rem;
color: var(--text-dim);
font-weight: 300;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 8px;
}
.rechner-input input {
width: 100%;
background: transparent;
border: 1px solid var(--text-muted);
color: var(--text);
font-family: inherit;
font-size: 2.4rem;
font-weight: 700;
padding: 12px 16px;
text-align: center;
outline: none;
transition: border-color 0.3s;
-moz-appearance: textfield;
}
.rechner-input input::-webkit-inner-spin-button,
.rechner-input input::-webkit-outer-spin-button {
-webkit-appearance: none;
}
.rechner-input input:focus {
border-color: var(--red);
}
.rechner-result {
font-size: clamp(1.3rem, 3vw, 1.8rem);
font-weight: 300;
color: var(--text-dim);
line-height: 2;
}
.rechner-result .num {
color: var(--red);
font-weight: 700;
font-size: 1.15em;
}
.rechner-result .verdict {
display: block;
margin-top: 24px;
font-size: clamp(1.5rem, 4vw, 2.2rem);
font-weight: 600;
color: var(--text);
}
@media (max-width: 640px) {
.rechner { padding: 80px 0; }
.rechner-grid { flex-direction: column; gap: 24px; }
}
/* Silence */
.silence {
padding: 160px 0;
@@ -171,6 +256,34 @@
text-transform: uppercase;
}
/* Rechnung */
.rechnung {
max-width: 400px;
margin: 0 auto;
}
.rechnung-row {
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: clamp(1.1rem, 2.5vw, 1.5rem);
font-weight: 300;
color: var(--text-dim);
padding: 4px 0;
}
.rechnung-row .label { text-align: left; }
.rechnung-row .value { text-align: right; font-weight: 700; color: var(--red); }
.rechnung-divider {
border: none;
border-top: 1px solid var(--text-muted);
margin: 12px 0;
}
.rechnung-total .label { font-weight: 600; color: var(--text); }
.rechnung-total .value { font-size: 1.15em; }
/* Footer */
footer {
padding: 40px 0;
@@ -217,8 +330,9 @@
<section class="numbers">
<div class="wrap">
<p class="reveal">
<span class="num">2.400</span> Stunden pro Jahr.<br>
<span class="num">6,5</span> Stunden pro Tag, die du &bdquo;verkaufst&ldquo;.<br>
Eine Zahl bestimmt deinen Wert.<br>
Nicht dein Ergebnis. Nicht dein Wissen.<br>
<span class="num">Billable Hours</span> &mdash; der einzige KPI, der z&auml;hlt.<br>
Jede Minute dokumentiert.<br>
Jede Pause ein Verlust.
</p>
@@ -230,26 +344,53 @@
<section class="kritik">
<div class="wrap">
<div class="kritik-item reveal">
<p>Du wirst nach Zeit bezahlt, <span class="dim">nicht nach Ergebnis.</span></p>
</div>
<div class="kritik-item reveal">
<p>Effizienz wird bestraft. <span class="dim">Wer schneller arbeitet, verdient weniger.</span></p>
</div>
<div class="kritik-item reveal">
<p>Der Mandant zahlt <span class="dim">für deine Lernkurve.</span></p>
<p>Ineffizienz wird belohnt. <span class="dim">L&auml;nger brauchen hei&szlig;t mehr verdienen.</span></p>
</div>
<div class="kritik-item reveal">
<p><span class="num">8</span> Stunden Arbeit = <span class="num">6,5</span> billable. <span class="dim">Die restlichen 1,5? Dein Problem.</span></p>
<p>Verkauft wird nur, was auf der Rechnung steht. <span class="dim">Der Rest deiner Arbeit existiert nicht.</span></p>
</div>
<div class="kritik-item reveal">
<p>Partnertrack heißt: <span class="dim"><span class="num">10</span> Jahre beweisen, dass du leidensfähig bist.</span></p>
<p>Und die anderen Stunden im B&uuml;ro? <span class="dim">Egal.</span></p>
</div>
<div class="kritik-item reveal">
<p>Die besten Ideen kommen nicht, <span class="dim">w&auml;hrend die Stoppuhr l&auml;uft.</span></p>
</div>
<div class="kritik-item reveal">
<p>Ein Leben im Sechs-Minuten-Takt <span class="dim">macht etwas mit deinem Bewusstsein.</span></p>
</div>
<div class="kritik-item reveal">
<p>Innovation ist ein Risiko. <span class="dim">Jedes Tool, das Zeit spart, bedroht den Umsatz.</span></p>
</div>
</div>
</section>
<div class="line"></div>
<section class="rechner">
<div class="wrap">
<h2 class="reveal">Rechne selbst<span class="dot">.</span></h2>
<div class="rechner-grid reveal">
<div class="rechner-input">
<label>Billable-Stunden / Jahr</label>
<input type="number" id="hours" value="1800" min="0" max="5000">
</div>
<div class="rechner-input">
<label>Urlaubstage</label>
<input type="number" id="vacation" value="25" min="0" max="60">
</div>
</div>
<div class="rechner-result reveal" id="result"></div>
</div>
</section>
@@ -277,6 +418,60 @@
}, { threshold: 0.15 });
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
// Rechner
function calculate() {
const hours = parseInt(document.getElementById('hours').value) || 0;
const vacation = parseInt(document.getElementById('vacation').value) || 0;
const weekends = 104;
const feiertage = 10;
const workingDays = 365 - weekends - vacation - feiertage;
const perDay = workingDays > 0 ? (hours / workingDays) : 0;
const perDayStr = perDay.toFixed(1).replace('.', ',');
let verdict = '';
if (perDay >= 7.96) {
verdict = '<span class="verdict">Das ist nicht nur ein Job. <span class="dim">Das ist ein Lifestyle.</span></span>';
} else if (perDay > 7) {
verdict = '<span class="verdict">Das ist die Theorie. <span class="dim">In der Praxis bist du l&auml;nger da.</span></span>';
} else if (perDay > 6) {
verdict = '<span class="verdict">Das sind nur die billable hours. <span class="dim">Dazu kommen Admin, Meetings, Mails &mdash; unbezahlt.</span></span>';
}
var r = function(l, v) {
return '<div class="rechnung-row"><span class="label">' + l + '</span><span class="value">' + v + '</span></div>';
};
document.getElementById('result').innerHTML =
'<div class="rechnung">' +
r('Tage im Jahr', '365') +
r('Wochenendtage (52 &times; 2)', '&minus; 104') +
r('Urlaubstage', '&minus; ' + vacation) +
r('Feiertage', '&minus; ' + feiertage) +
'<hr class="rechnung-divider">' +
'<div class="rechnung-row rechnung-total">' +
'<span class="label">Arbeitstage</span><span class="value">' + workingDays + '</span></div>' +
'</div><br>' +
'<span class="num">' + hours.toLocaleString('de-DE') + '</span> Stunden &divide; <span class="num">' + workingDays + '</span> Tage = ' +
'<span class="num">' + perDayStr + '</span> Stunden billable pro Tag.' +
verdict +
'<span class="verdict" style="margin-top:32px;font-size:0.85em;font-weight:300;color:var(--text-dim);display:block;">' +
(function() {
var base = hours / workingDays * 60;
var d1 = workingDays > 1 ? Math.round(hours / (workingDays - 1) * 60 - base) : 0;
var d2 = workingDays > 2 ? Math.round(hours / (workingDays - 2) * 60 - base) : 0;
var d5 = workingDays > 5 ? Math.round(hours / (workingDays - 5) * 60 - base) : 0;
return 'Ein Tag krank? +<span class="num">' + d1 + '</span> Minuten mehr an jedem anderen Tag. Jeden.' +
' Zwei Tage? +<span class="num">' + d2 + '</span>.' +
' Eine Woche? +<span class="num">' + d5 + '</span>.' +
'<br>Jeder verpasste Tag erh&ouml;ht den Druck auf alle anderen.';
})() +
'</span>';
}
document.getElementById('hours').addEventListener('input', calculate);
document.getElementById('vacation').addEventListener('input', calculate);
calculate();
</script>
</body>

View File

@@ -0,0 +1,384 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ZenSiebels — 静けさ</title>
<meta name="description" content="ZenSiebels — Stille. Klarheit. Praxis.">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪷</text></svg>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+JP:wght@200;300;400;500;600&family=Inter:wght@200;300;400&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--stone: #1c1b1a;
--stone-soft: #3a3835;
--stone-faint: #6b6660;
--stone-ghost: #a09a92;
--washi: #f7f3ed;
--washi-warm: #efe9df;
--washi-dark: #e8e0d4;
--sumi: #2c2a28;
--matcha: #7a9a6a;
--matcha-glow: rgba(122, 154, 106, 0.1);
--sand: #c4b69c;
--sand-glow: rgba(196, 182, 156, 0.12);
}
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', -apple-system, sans-serif;
background: var(--washi);
color: var(--stone);
line-height: 1.7;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
.jp { font-family: 'Noto Serif JP', serif; }
/* === HERO === */
.hero {
min-height: 100vh;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
text-align: center;
padding: 80px 32px;
position: relative;
}
.hero::before {
content: '';
position: absolute; inset: 0;
background: radial-gradient(ellipse at 50% 35%, var(--sand-glow) 0%, transparent 55%);
pointer-events: none;
}
.enso {
width: 180px; height: 180px;
margin-bottom: 48px;
position: relative;
opacity: 0;
animation: fadeIn 2s ease-out 0.3s forwards;
}
.enso svg {
width: 100%; height: 100%;
}
.enso-circle {
fill: none;
stroke: var(--stone);
stroke-width: 2.5;
stroke-linecap: round;
stroke-dasharray: 580;
stroke-dashoffset: 580;
animation: drawEnso 3s ease-in-out 0.5s forwards;
}
@keyframes drawEnso {
to { stroke-dashoffset: 45; }
}
.kanji {
font-family: 'Noto Serif JP', serif;
font-size: clamp(2.5rem, 6vw, 4rem);
font-weight: 200;
color: var(--stone-ghost);
letter-spacing: 0.4em;
margin-bottom: 32px;
opacity: 0;
animation: fadeIn 1.5s ease-out 1.5s forwards;
}
h1 {
font-family: 'Noto Serif JP', serif;
font-size: clamp(2rem, 5vw, 3.2rem);
font-weight: 400;
letter-spacing: 0.08em;
line-height: 1.2;
margin-bottom: 24px;
opacity: 0;
animation: fadeIn 1.5s ease-out 2s forwards;
}
.subtitle {
font-size: 0.95rem;
color: var(--stone-faint);
font-weight: 300;
letter-spacing: 0.12em;
text-transform: uppercase;
opacity: 0;
animation: fadeIn 1.5s ease-out 2.5s forwards;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* === DIVIDER === */
.breath {
padding: 80px 0;
text-align: center;
}
.breath-dot {
width: 6px; height: 6px;
background: var(--sand);
border-radius: 50%;
margin: 0 auto;
animation: breathe 6s ease-in-out infinite;
}
@keyframes breathe {
0%, 100% { transform: scale(1); opacity: 0.4; }
50% { transform: scale(2.5); opacity: 1; }
}
/* === WISDOM === */
.wisdom {
max-width: 640px;
margin: 0 auto;
padding: 0 32px 120px;
}
.wisdom-item {
padding: 64px 0;
border-top: 1px solid var(--washi-dark);
text-align: center;
opacity: 0;
transform: translateY(16px);
transition: opacity 1s ease, transform 1s ease;
}
.wisdom-item.visible {
opacity: 1;
transform: translateY(0);
}
.wisdom-jp {
font-family: 'Noto Serif JP', serif;
font-size: 1.6rem;
font-weight: 300;
color: var(--stone-ghost);
margin-bottom: 16px;
letter-spacing: 0.15em;
}
.wisdom-text {
font-family: 'Noto Serif JP', serif;
font-size: clamp(1.2rem, 3vw, 1.6rem);
font-weight: 400;
color: var(--stone);
line-height: 1.8;
margin-bottom: 12px;
}
.wisdom-source {
font-size: 0.8rem;
color: var(--stone-faint);
font-weight: 300;
letter-spacing: 0.08em;
}
/* === PRACTICE === */
.practice {
background: var(--washi-warm);
padding: 100px 32px;
text-align: center;
}
.practice h2 {
font-family: 'Noto Serif JP', serif;
font-size: clamp(1.5rem, 4vw, 2.2rem);
font-weight: 400;
margin-bottom: 48px;
letter-spacing: 0.05em;
}
.practice-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 40px;
max-width: 720px;
margin: 0 auto;
}
.practice-card {
text-align: center;
}
.practice-icon {
font-size: 2rem;
margin-bottom: 16px;
opacity: 0.7;
}
.practice-name {
font-family: 'Noto Serif JP', serif;
font-size: 1rem;
font-weight: 500;
margin-bottom: 8px;
letter-spacing: 0.05em;
}
.practice-desc {
font-size: 0.82rem;
color: var(--stone-faint);
font-weight: 300;
line-height: 1.6;
}
/* === BREATHING === */
.breathing {
padding: 120px 32px;
text-align: center;
}
.breathing-label {
font-size: 0.75rem;
color: var(--stone-ghost);
letter-spacing: 0.2em;
text-transform: uppercase;
margin-bottom: 32px;
}
.breathing-circle {
width: 100px; height: 100px;
border: 1.5px solid var(--sand);
border-radius: 50%;
margin: 0 auto 32px;
animation: breatheCircle 8s ease-in-out infinite;
}
@keyframes breatheCircle {
0%, 100% { transform: scale(0.6); opacity: 0.3; }
30% { transform: scale(1.2); opacity: 0.8; }
50% { transform: scale(1.2); opacity: 0.8; }
80% { transform: scale(0.6); opacity: 0.3; }
}
.breathing-text {
font-family: 'Noto Serif JP', serif;
font-size: 0.9rem;
color: var(--stone-faint);
font-weight: 300;
animation: breatheText 8s ease-in-out infinite;
}
@keyframes breatheText {
0%, 100% { opacity: 0; }
5% { opacity: 1; }
25% { opacity: 1; }
30% { opacity: 0; }
50% { opacity: 0; }
55% { opacity: 1; }
75% { opacity: 1; }
80% { opacity: 0; }
}
/* === FOOTER === */
footer {
padding: 48px 32px;
text-align: center;
border-top: 1px solid var(--washi-dark);
}
footer p {
font-size: 0.75rem;
color: var(--stone-ghost);
font-weight: 300;
letter-spacing: 0.1em;
}
/* === RESPONSIVE === */
@media (max-width: 640px) {
.practice-grid { grid-template-columns: 1fr; gap: 32px; }
.wisdom-item { padding: 48px 0; }
.enso { width: 140px; height: 140px; }
}
</style>
</head>
<body>
<section class="hero">
<div class="enso">
<svg viewBox="0 0 200 200">
<circle class="enso-circle" cx="100" cy="100" r="88" />
</svg>
</div>
<div class="kanji"></div>
<h1>ZenSiebels</h1>
<p class="subtitle">Stille · Klarheit · Praxis</p>
</section>
<div class="breath">
<div class="breath-dot"></div>
</div>
<section class="wisdom">
<div class="wisdom-item">
<p class="wisdom-jp">初心</p>
<p class="wisdom-text">Im Geist des Anfängers gibt es viele Möglichkeiten. Im Geist des Experten nur wenige.</p>
<p class="wisdom-source">Shunryū Suzuki</p>
</div>
<div class="wisdom-item">
<p class="wisdom-jp">一期一会</p>
<p class="wisdom-text">Jede Begegnung ist einmalig und kann nicht wiederholt werden.</p>
<p class="wisdom-source">Teezeremonie-Weisheit</p>
</div>
<div class="wisdom-item">
<p class="wisdom-jp">無為</p>
<p class="wisdom-text">Nicht Nichtstun. Sondern tun, ohne zu erzwingen.</p>
<p class="wisdom-source">Laozi</p>
</div>
</section>
<section class="practice">
<h2 class="jp">三つの柱</h2>
<div class="practice-grid">
<div class="practice-card">
<div class="practice-icon">🧘</div>
<p class="practice-name">Zazen</p>
<p class="practice-desc">Sitzen. Atmen. Loslassen. Die einfachste und schwierigste Übung zugleich.</p>
</div>
<div class="practice-card">
<div class="practice-icon">🍵</div>
<p class="practice-name">Chadō</p>
<p class="practice-desc">Der Weg des Tees. Jede Bewegung bewusst. Jeder Moment vollständig.</p>
</div>
<div class="practice-card">
<div class="practice-icon">✍️</div>
<p class="practice-name">Shodō</p>
<p class="practice-desc">Der Weg der Schrift. Ein Strich, unwiderruflich. Kontrolle durch Loslassen.</p>
</div>
</div>
</section>
<section class="breathing">
<p class="breathing-label">Atme mit</p>
<div class="breathing-circle"></div>
<p class="breathing-text jp">吸う — 吐く</p>
</section>
<footer>
<p>zensiebels.de — 2026</p>
</footer>
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) entry.target.classList.add('visible');
});
}, { threshold: 0.2 });
document.querySelectorAll('.wisdom-item').forEach(el => observer.observe(el));
</script>
</body>
</html>

View File

@@ -0,0 +1,4 @@
domain: zensiebels.de
template: custom
title: "ZenSebels"
description: "Stille. Klarheit. Praxis."