70 Commits

Author SHA1 Message Date
mAi
59833b5d6d mAi: #4 - Impressum-Overlay theme-aware + frame-safe
- Trigger: <a>-Link mit identischer CSS-Hülle wie die inline-minimal-
  Variante (text-align:center; font-size:0.75rem; opacity:0.6;
  padding:12px 0; margin-top:4px). Kein Footer-Layout-Shift mehr,
  Höhe und Breite identisch zu vorher.
- Karte: nutzt --bg-card / --text / --accent / --border / --radius /
  --font-primary vom Host (shared/css/variables.css) — Dark-Fallbacks
  neutral. Auf hellen Themes greifen die CSS-Vars, auf Sites ohne
  Variablen bleibt es dezent dunkel.
- max-width: min(420px, calc(100vw - 32px)) + box-sizing:border-box
  + overflow-wrap:break-word — Karte kann Viewport nie sprengen,
  auch nicht bei langen unbreakable strings.
- Body-scroll-lock während Overlay offen ist; vorheriger overflow-
  Wert wird beim Schließen restauriert.
2026-04-26 10:34:33 +02:00
mAi
098a7ad99a mAi: #4 - Impressum als Klick-Overlay statt inline (full variant)
- variant=full: kleiner Footer-Trigger ("Impressum"), Klick öffnet Overlay
  mit § 5 TMG-Block. Schließen via × / ESC / Backdrop-Klick. Fade-in.
- variant=minimal: bleibt unveränderter Inline-Einzeiler.
- API unverändert: data-owner / data-variant am Script-Tag.
- Alles inline (kein neues Stylesheet, keine Abhängigkeiten).
2026-04-26 01:36:21 +02:00
mAi
7d731e8bcf chore(mcp): rename mai → supabase in .mcp.json
Project-scope `mai` server collided with the user-scope maimcp
orchestration binary registered in ~/.claude.json — silently rebinding
mcp__mai__* from list_tasks/update_task/send_message to Supabase SQL
tools. Renaming to `supabase` keeps the namespaces disjoint.

Refs: m/mAi#154
2026-04-26 00:37:35 +02:00
mAi
e4797aa532 Merge branch 'mai/hermes/issue-3-impressum': Impressum consolidation (#3)
- shared/impressum.js als Single-Source-of-Truth
- 3 Owner (msbls/flexsiebels/martinsiebels) × 2 Varianten (minimal/full)
- Name-Bug 'Matthias Flexsiebels' → 'Matthias Siebels' korrigiert
- 14 Sites final konfiguriert

Commits: a4e3773 + a5777ff + f85f4b4
2026-04-23 00:33:40 +02:00
mAi
f85f4b4e7f mAi: #3 - heygoldi/martinsiebels/zensiebels Impressum
- heygoldi.de: hardcoded "ein Projekt von msbls.de" Zeile entfernt, shared (msbls minimal) eingebunden
- martinsiebels.de: shared (martinsiebels full) — volles § 5 TMG
- zensiebels.de: shared (flexsiebels minimal)
- matthiasbreier.de: bewusst kein Impressum (m-Entscheidung)

Satire-Sites (kainco/kili*/kino* etc.) bleiben weiter ohne Impressum-Link.

Refs: #3
2026-04-23 00:26:18 +02:00
mAi
a5777ff65d mAi: #3 - mai-otto/lexsiebels Impressum + clemensplassmann Redirect
- mai-otto.de: shared Impressum (flexsiebels full)
- lexsiebels.de: shared Impressum (flexsiebels full)
- clemensplassmann.de: offline genommen, leitet per meta refresh + JS zur
  Hogan-Lovells-Profilseite weiter (https://www.hoganlovells.com/en/clemens-plassmann).
  Fallback-Link sichtbar, falls Redirect blockiert.

Refs: #3
2026-04-23 00:03:21 +02:00
mAi
a4e37735f8 mAi: #3 - Impressum-Konsistenz: shared als Single-Source-of-Truth
- shared/impressum.js: Name korrigiert (msbls/flexsiebels = Matthias Siebels, nicht Martin),
  echte Adresse aus youpc.org übernommen (c/o Online-Impressum.de #5892, Sankt Augustin).
  Neuer Owner 'flexsiebels', neuer Owner 'martinsiebels' (für Martin als separate Person
  mit eigener Adresse in Osnabrück). data-variant als offizielles Attribut, data-style
  weiterhin als Legacy-Alias. Rendert in #impressum falls vorhanden, sonst footer.

- paragraphenraiter.de: hardcoded "Ein Projekt von Matthias Flexsiebels" → shared (msbls minimal)
- patentonkel.de: hardcoded "Matthias Flexsiebels" + kaputter window.__impressum() → shared (msbls minimal)
- smartin3.de: inline Impressum-Text → shared (martinsiebels full) in #impressum Div
- ichbinotto.de: shared (flexsiebels full) mit echter § 5 TMG-Angabe

Caddyfile: Regenerierung hat fehlende Einträge für 6034.de, traihard.de, zensiebels.de ergänzt.

Refs: #3
2026-04-22 23:39:41 +02:00
m
dadfa0df7c feat: smartin3.de — mouse interaction covers full hero section width and depth 2026-04-18 21:20:56 +02:00
m
1caf12d801 fix: smartin3.de — remove noise overlay from body 2026-04-18 21:19:45 +02:00
m
f6c9eddfe4 fix: smartin3.de — tighten hero gradient, smaller radius to avoid page-wide glow 2026-04-18 21:19:31 +02:00
m
4315f4c2c0 fix: smartin3.de — larger mouse interaction area, gradient not clipped by container 2026-04-18 21:18:29 +02:00
m
8f615fd3d1 feat: smartin3.de — interactive 3D text, follows mouse/touch 2026-04-18 21:14:38 +02:00
m
6f2f150cb6 fix: smartin3.de — sweep -45° to 45° 2026-04-18 21:13:29 +02:00
m
3d457d6b42 feat: smartin3.de gallery — add Sonnenblendenhalter photo 2026-04-18 21:13:08 +02:00
m
d8e145a49d feat: smartin3.de — slow sweep 45° to 165°, no full rotation 2026-04-18 21:12:36 +02:00
m
8fc605c851 fix: smartin3.de — replace all fake umlauts with real ä ö ü ß 2026-04-18 21:12:11 +02:00
m
66166d979c feat: smartin3.de — 3D text gradient from orange to blue 2026-04-18 21:10:26 +02:00
m
b12b8e9068 feat: smartin3.de — 30 depth layers at 1px spacing for solid 3D extrusion 2026-04-18 21:08:50 +02:00
m
5a2416f3a6 feat: smartin3.de — solid text-shadow extrusion, no gaps between layers 2026-04-18 21:07:20 +02:00
m
5454f0fc27 feat: smartin3.de — lightbox on gallery click with caption 2026-04-18 21:06:39 +02:00
m
ab4003fb15 feat: smartin3.de — solid 3D block letters, remove duplicate h1 2026-04-18 21:05:32 +02:00
m
7d56e6a2fd feat: smartin3.de — full 360 rotation from diagonal angle, more depth 2026-04-18 21:03:42 +02:00
m
866d30476d fix: smartin3.de — missing closing brace on @keyframes rotateText 2026-04-18 21:02:39 +02:00
m
5240244a14 fix: smartin3.de — rename Schaltschrank Abdeckungshalter title 2026-04-18 21:01:53 +02:00
m
057fbf5434 feat: smartin3.de — replace rotating cube with 3D "smart" text animation 2026-04-18 21:01:29 +02:00
m
52758c94a8 fix: smartin3.de — prevent horizontal overflow from hero gradient on mobile 2026-04-18 20:57:53 +02:00
m
e2fc469957 fix: smartin3.de — remove container div from nav, flex directly on nav element 2026-04-18 20:53:38 +02:00
m
4a7aa31dc1 feat: smartin3.de gallery — add Schaltanlage Panelhalter photo 2026-04-18 20:52:23 +02:00
m
3c324d4103 fix: smartin3.de — nav container full-width, no more 860px constraint 2026-04-18 20:51:16 +02:00
m
9d6debecbd fix: smartin3.de — nav uses full viewport width, hamburger aligned properly 2026-04-18 20:48:42 +02:00
m
21f22dc90d fix: smartin3.de — remove max-width on gallery description text 2026-04-18 20:47:40 +02:00
m
98fd237298 fix: smartin3.de — remove stock photos, fix umlauts, Flachkabelhalter 2026-04-18 20:46:42 +02:00
m
ffeed4998c feat: smartin3.de gallery — add Muenzhalter photo 2026-04-18 20:36:52 +02:00
m
fc4986b805 feat: smartin3.de gallery — add Flachkabel Halter photo 2026-04-18 20:29:25 +02:00
m
1576ac892b fix: smartin3.de — hamburger menu on mobile, nav no longer overflows 2026-04-18 20:22:50 +02:00
m
08739c6294 feat: smartin3.de — add Etsy shop link to nav and contact section 2026-04-18 20:19:52 +02:00
m
075be054e9 fix: smartin3.de — responsive nav, prevent overflow on mobile 2026-04-18 20:17:53 +02:00
m
6480e72472 feat: smartin3.de — add Instagram link to nav and contact section 2026-04-18 20:03:49 +02:00
m
6dc338ddc2 feat: smartin3.de gallery — add VW Golf MK1 cover photo with caption
Add real product photo (VW Golf MK1 trunk cover) to gallery with
description. Extend gallery CSS to support figcaption elements.
2026-04-18 19:59:38 +02:00
m
2c4b693ac8 fix: 6034.de — minimal links only, no description 2026-04-16 16:32:11 +02:00
m
fc50219740 feat: add 6034.de — personal project space with mGreen link 2026-04-16 15:11:28 +02:00
m
89a0f992e0 feat: smartin3.de — add gallery section with Unsplash placeholders 2026-04-09 19:18:33 +02:00
m
5fe3132e34 feat: traihard.de — universal tryhard manifesto (cycling, comunio, fitness, productivity) 2026-04-08 13:21:12 +02:00
m
4dfae76862 fix: remove comunio link from traihard.de footer 2026-04-08 13:15:30 +02:00
m
2ffb1b4192 fix: remove 'powered by' from footers 2026-04-08 13:13:59 +02:00
m
40f07304de feat: traihard.de — tryhard cycling manifesto + comunio guide 2026-04-08 13:12:30 +02:00
m
d022ef209e feat: add comunio AI assistant guide at traihard.de/comunio 2026-04-08 13:11:03 +02:00
m
208004f450 feat: add traihard.de — AI cycling routes onepager 2026-04-08 12:54:16 +02:00
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
m
883904318e feat: i18n pilot — shared JS snippet + ichbinotto.de translation
Shared i18n.js (data-de/data-en attributes, navigator.language detection,
localStorage persistence, footer toggle button). Piloted on ichbinotto.de
with full de/en translation of all visible text.

Closes pilot for #1.
2026-04-01 12:35:36 +02:00
52 changed files with 2469 additions and 214 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": {
"supabase": {
"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

@@ -9,6 +9,11 @@
root * /srv
file_server
}
@6034_de host 6034.de
handle @6034_de {
root * /srv/6034.de
file_server
}
@allainallain_de host allainallain.de
handle @allainallain_de {
root * /srv/allainallain.de
@@ -279,6 +284,11 @@
root * /srv/sorgenfrai.de
file_server
}
@traihard_de host traihard.de
handle @traihard_de {
root * /srv/traihard.de
file_server
}
@vonschraitter_de host vonschraitter.de
handle @vonschraitter_de {
root * /srv/vonschraitter.de
@@ -289,6 +299,11 @@
root * /srv/wartebitte.de
file_server
}
@zensiebels_de host zensiebels.de
handle @zensiebels_de {
root * /srv/zensiebels.de
file_server
}
handle {
respond "Not Found" 444
}

View File

@@ -1,45 +1,248 @@
/**
* Modulares Impressum für Onepager-Sites.
* Modulares Impressum — Single Source of Truth für alle Onepager-Sites.
*
* Einbinden: <script src="/shared/impressum.js"></script>
* Einbinden:
* <script src="/shared/impressum.js"></script>
* <script src="/shared/impressum.js" data-owner="flexsiebels"></script>
* <script src="/shared/impressum.js" data-owner="flexsiebels" data-variant="full"></script>
*
* Konfiguration via data-Attribute am Script-Tag:
* data-owner="msbls" (default) — Kurzform, msbls.de Satire-Impressum
* data-owner="martinsiebels" — Volles Impressum Martin Siebels
* data-style="minimal" (default) — Einzeiler
* data-style="full" — Komplettes Impressum mit Adresse etc.
* data-owner="msbls" (default) — Kurzverweis auf msbls.de
* data-owner="flexsiebels" Kurzverweis auf flexsiebels.de
* data-owner="martinsiebels" — volles Impressum Martin Siebels (separate Person, Osnabrück)
* data-variant="minimal" (default) — Einzeiler im Footer (inline)
* data-variant="full" — kleiner Text-Trigger im Footer, voller § 5 TMG-Block im Overlay
*
* Legacy-Alias: data-style (gleiche Werte wie data-variant).
*
* Render-Ziel: Element mit id="impressum" falls vorhanden, sonst <footer>, sonst body.
*
* Theme: das Overlay liest --bg-card / --text / --accent / --border vom Host
* (siehe shared/css/variables.css), mit neutralen Dark-Fallbacks für Sites
* ohne diese Variablen.
*/
(function () {
const script = document.currentScript;
const owner = script?.getAttribute('data-owner') || 'msbls';
const style = script?.getAttribute('data-style') || 'minimal';
const variant = script?.getAttribute('data-variant')
|| script?.getAttribute('data-style')
|| 'minimal';
// Gemeinsamer Block für Matthias Siebels (m) — beide Domains, gleiche Anschrift.
const matthiasAddress = 'Matthias Siebels<br>'
+ 'c/o Online-Impressum.de #5892<br>'
+ 'Europaring 90<br>'
+ '53757 Sankt Augustin';
const owners = {
msbls: {
minimal: 'Ein Projekt von <a href="https://msbls.de" target="_blank" rel="noopener">msbls.de</a>',
full: '<strong>Angaben gemäß § 5 TMG:</strong><br>msbls.de — Martin Siebels<br><a href="https://msbls.de/impressum" target="_blank" rel="noopener">Vollständiges Impressum</a>',
full: '<strong>Angaben gemäß § 5 TMG:</strong><br>'
+ matthiasAddress + '<br>'
+ 'E-Mail: <a href="mailto:mail@msbls.de">mail@msbls.de</a>',
},
flexsiebels: {
minimal: 'Ein Projekt von <a href="https://flexsiebels.de" target="_blank" rel="noopener">flexsiebels.de</a>',
full: '<strong>Angaben gemäß § 5 TMG:</strong><br>'
+ matthiasAddress + '<br>'
+ 'E-Mail: <a href="mailto:mail@flexsiebels.de">mail@flexsiebels.de</a>',
},
martinsiebels: {
minimal: 'Ein Projekt von <a href="https://martinsiebels.de" target="_blank" rel="noopener">Martin Siebels</a>',
full: '<strong>Angaben gemäß § 5 TMG:</strong><br>'
+ 'Martin Siebels<br>'
+ 'Leyer Str. 38<br>'
+ '49076 Osnabrück<br>'
+ 'E-Mail: <a href="mailto:Martin_Siebels@web.de">Martin_Siebels@web.de</a>',
},
};
const config = owners[owner] || owners.msbls;
const html = config[style] || config.minimal;
// Impressum-Element erstellen
const el = document.createElement('div');
el.className = 'onepager-impressum';
el.innerHTML = html;
const target = document.getElementById('impressum')
|| document.querySelector('footer .container')
|| document.querySelector('footer')
|| document.body;
// Styling — erbt Farben vom footer/body, bleibt dezent
el.style.cssText = 'text-align:center;font-size:0.7rem;opacity:0.5;padding:8px 0;margin-top:4px;';
el.querySelector('a')?.style && Object.assign(el.querySelector('a').style, {
color: 'inherit', textDecoration: 'none'
});
// Einfügen: in <footer> falls vorhanden, sonst ans body-Ende
const footer = document.querySelector('footer .container, footer');
if (footer) {
footer.appendChild(el);
if (variant === 'full') {
renderOverlay(config.full, target);
} else {
document.body.appendChild(el);
renderInline(config.minimal, target);
}
function renderInline(html, target) {
const el = document.createElement('div');
el.className = 'onepager-impressum';
el.innerHTML = html;
el.style.cssText = 'text-align:center;font-size:0.75rem;opacity:0.6;padding:12px 0;margin-top:4px;line-height:1.7;';
el.querySelectorAll('a').forEach(a => {
a.style.color = 'inherit';
a.style.textDecoration = 'underline';
a.style.textDecorationThickness = '1px';
a.style.textUnderlineOffset = '2px';
});
target.appendChild(el);
}
function renderOverlay(html, target) {
// Trigger: kleiner Text-Link, layout-äquivalent zur inline-minimal-Variante,
// damit Footer-Höhe sich nicht ändert.
const wrap = document.createElement('div');
wrap.className = 'onepager-impressum';
wrap.style.cssText = 'text-align:center;font-size:0.75rem;opacity:0.6;padding:12px 0;margin-top:4px;line-height:1.7;';
const trigger = document.createElement('a');
trigger.href = '#impressum';
trigger.className = 'onepager-impressum-trigger';
trigger.textContent = 'Impressum';
trigger.style.cssText = [
'color:inherit',
'text-decoration:underline',
'text-decoration-thickness:1px',
'text-underline-offset:2px',
'cursor:pointer',
].join(';');
wrap.appendChild(trigger);
target.appendChild(wrap);
let backdrop = null;
let lastFocus = null;
let prevBodyOverflow = '';
trigger.addEventListener('click', (e) => {
e.preventDefault();
open();
});
function open() {
if (backdrop) return;
lastFocus = document.activeElement;
backdrop = document.createElement('div');
backdrop.className = 'onepager-impressum-backdrop';
backdrop.style.cssText = [
'position:fixed',
'inset:0',
'background:rgba(0,0,0,0.6)',
'display:flex',
'align-items:center',
'justify-content:center',
'padding:16px',
'box-sizing:border-box',
'overflow:auto',
'z-index:99999',
'opacity:0',
'transition:opacity 0.18s ease',
].join(';');
const card = document.createElement('div');
card.className = 'onepager-impressum-card';
card.setAttribute('role', 'dialog');
card.setAttribute('aria-modal', 'true');
card.setAttribute('aria-label', 'Impressum');
// Theme-aware: nutzt CSS-Variablen vom Host, mit neutralen Dark-Fallbacks.
// max-width via min(...) verhindert Frame-Sprengen auf schmalen Viewports.
card.style.cssText = [
'position:relative',
'box-sizing:border-box',
'max-width:min(420px, calc(100vw - 32px))',
'width:100%',
'background:var(--bg-card, #16161b)',
'color:var(--text, #e8e8ed)',
'padding:32px 24px 24px',
'border:1px solid var(--border, rgba(255,255,255,0.08))',
'border-radius:var(--radius, 8px)',
'box-shadow:0 20px 60px rgba(0,0,0,0.5)',
'font-family:var(--font-primary, Inter, -apple-system, BlinkMacSystemFont, sans-serif)',
'font-size:0.9rem',
'line-height:1.6',
'overflow-wrap:break-word',
'word-wrap:break-word',
'transform:translateY(8px)',
'transition:transform 0.18s ease',
].join(';');
const close = document.createElement('button');
close.type = 'button';
close.setAttribute('aria-label', 'Schließen');
close.innerHTML = '&times;';
close.style.cssText = [
'position:absolute',
'top:4px',
'right:8px',
'background:none',
'border:none',
'font:inherit',
'font-size:1.5rem',
'line-height:1',
'color:inherit',
'opacity:0.5',
'cursor:pointer',
'padding:6px 10px',
'border-radius:4px',
'transition:opacity 0.15s ease',
].join(';');
close.addEventListener('mouseenter', () => { close.style.opacity = '1'; });
close.addEventListener('mouseleave', () => { close.style.opacity = '0.5'; });
close.addEventListener('focus', () => { close.style.opacity = '1'; });
close.addEventListener('blur', () => { close.style.opacity = '0.5'; });
close.addEventListener('click', dismiss);
const content = document.createElement('div');
content.innerHTML = html;
content.style.cssText = 'margin-top:4px;';
content.querySelectorAll('a').forEach(a => {
a.style.color = 'var(--accent, #c9a84c)';
a.style.textDecoration = 'underline';
});
content.querySelectorAll('strong').forEach(s => {
s.style.display = 'block';
s.style.marginBottom = '10px';
s.style.fontWeight = '600';
});
card.appendChild(close);
card.appendChild(content);
backdrop.appendChild(card);
document.body.appendChild(backdrop);
// Scroll-Lock auf body, damit Hintergrund nicht mitscrollt.
prevBodyOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
requestAnimationFrame(() => {
backdrop.style.opacity = '1';
card.style.transform = 'translateY(0)';
});
backdrop.addEventListener('click', (e) => {
if (e.target === backdrop) dismiss();
});
document.addEventListener('keydown', onKey);
close.focus();
}
function dismiss() {
if (!backdrop) return;
document.removeEventListener('keydown', onKey);
const node = backdrop;
backdrop = null;
node.style.opacity = '0';
document.body.style.overflow = prevBodyOverflow;
setTimeout(() => {
if (node.parentNode) node.parentNode.removeChild(node);
if (lastFocus && typeof lastFocus.focus === 'function') {
lastFocus.focus();
}
}, 180);
}
function onKey(e) {
if (e.key === 'Escape') {
e.preventDefault();
dismiss();
}
}
}
})();

110
sites/6034.de/index.html Normal file
View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#0f1210">
<title>6034.de</title>
<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=Inter:wght@400;500;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0f1210;
--bg-card: #1a1f1c;
--border: #2d3630;
--text: #e8ede9;
--text-dim: #8a9b8e;
--green: #4ade80;
--green-dim: #22c55e;
--font: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
body::before {
content: "";
position: fixed;
inset: 0;
background-image: radial-gradient(circle at 20% 10%, rgba(74,222,128,0.06), transparent 40%),
radial-gradient(circle at 80% 90%, rgba(74,222,128,0.04), transparent 40%);
pointer-events: none;
z-index: 0;
}
main {
position: relative;
z-index: 1;
max-width: 480px;
margin: 0 auto;
padding: 4rem 1.5rem;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
}
.brand {
font-family: var(--font-mono);
font-size: 4rem;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--green);
line-height: 1;
margin-bottom: 2.5rem;
}
.brand-dot { color: var(--text-dim); }
.links {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.link {
padding: 1rem 1.25rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
text-decoration: none;
color: var(--text);
font-weight: 500;
display: flex;
align-items: center;
justify-content: space-between;
transition: border-color 0.15s ease, transform 0.15s ease;
}
.link:hover {
border-color: var(--green-dim);
transform: translateY(-1px);
}
.link-arrow {
color: var(--green);
font-family: var(--font-mono);
opacity: 0.6;
transition: opacity 0.15s ease, transform 0.15s ease;
}
.link:hover .link-arrow {
opacity: 1;
transform: translateX(2px);
}
@media (max-width: 480px) {
.brand { font-size: 3rem; }
main { padding: 3rem 1rem; }
}
</style>
</head>
<body>
<main>
<div class="brand">6034<span class="brand-dot">.de</span></div>
<div class="links">
<a class="link" href="https://grn.msbls.de" target="_blank" rel="noopener">
<span>🌱 mGreen</span>
<span class="link-arrow"></span>
</a>
</div>
</main>
</body>
</html>

4
sites/6034.de/site.yaml Normal file
View File

@@ -0,0 +1,4 @@
domain: 6034.de
template: custom
title: "6034 — Personal Project Space"
description: "Eine Sammlung persönlicher Projekte von m."

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

@@ -3,129 +3,34 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clemens Plassmann</title>
<title>Clemens Plassmann — Hogan Lovells</title>
<meta name="description" content="Clemens Plassmann — Patent Litigation, Hogan Lovells.">
<meta http-equiv="refresh" content="0; url=https://www.hoganlovells.com/en/clemens-plassmann">
<link rel="canonical" href="https://www.hoganlovells.com/en/clemens-plassmann">
<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>">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Georgia', 'Times New Roman', serif;
font-family: Georgia, 'Times New Roman', serif;
background: #0a0a0a;
color: #e8e6e3;
color: #c9a96e;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
text-align: center;
padding: 2rem;
max-width: 700px;
}
.name {
font-size: clamp(2.5rem, 6vw, 4rem);
font-weight: 400;
letter-spacing: 0.08em;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #c9a96e 0%, #f0d89d 50%, #c9a96e 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.divider {
width: 60px;
height: 1px;
background: #c9a96e;
margin: 1.5rem auto;
opacity: 0.6;
}
.title {
font-size: 1.1rem;
font-weight: 400;
color: #8a8580;
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.firm {
font-size: 1rem;
color: #6a6560;
letter-spacing: 0.1em;
}
.links {
margin-top: 3rem;
display: flex;
gap: 2rem;
justify-content: center;
flex-wrap: wrap;
}
.links a {
color: #c9a96e;
text-decoration: none;
font-size: 0.85rem;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 0.6rem 1.5rem;
border: 1px solid rgba(201, 169, 110, 0.3);
transition: all 0.3s ease;
}
.links a:hover {
border-color: #c9a96e;
background: rgba(201, 169, 110, 0.08);
}
.footer {
margin-top: 4rem;
font-size: 0.75rem;
color: #3a3530;
letter-spacing: 0.1em;
}
.footer a {
color: #3a3530;
text-decoration: none;
}
.footer a:hover {
color: #6a6560;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.container > * {
animation: fadeIn 0.8s ease forwards;
opacity: 0;
}
.container > *:nth-child(1) { animation-delay: 0.1s; }
.container > *:nth-child(2) { animation-delay: 0.3s; }
.container > *:nth-child(3) { animation-delay: 0.5s; }
.container > *:nth-child(4) { animation-delay: 0.7s; }
.container > *:nth-child(5) { animation-delay: 0.9s; }
.container > *:nth-child(6) { animation-delay: 1.1s; }
a { color: #c9a96e; text-decoration: none; border-bottom: 1px solid rgba(201,169,110,0.4); }
a:hover { border-bottom-color: #c9a96e; }
p { letter-spacing: 0.06em; line-height: 1.8; }
</style>
<script>window.location.replace('https://www.hoganlovells.com/en/clemens-plassmann');</script>
</head>
<body>
<div class="container">
<h1 class="name">Clemens Plassmann</h1>
<div class="divider"></div>
<p class="title">Patent Litigation</p>
<p class="firm">Hogan Lovells</p>
<div class="links">
<a href="https://www.hoganlovells.com/en/clemens-plassmann" target="_blank">Profile</a>
<a href="https://www.linkedin.com/in/clemens-plassmann/" target="_blank">LinkedIn</a>
</div>
<p class="footer">Built by <a href="https://flexsiebels.de">Otto</a></p>
<div>
<p>Clemens Plassmann — Patent Litigation, Hogan Lovells.</p>
<p>Weiter zu <a href="https://www.hoganlovells.com/en/clemens-plassmann">hoganlovells.com</a>.</p>
</div>
</body>
</html>

View File

@@ -576,8 +576,9 @@
<footer>
<div class="footer-bear">🧸</div>
<p class="footer-text">Hey Goldi! — Dein freundlicher KI-Begleiter.</p>
<p class="footer-copy">&copy; 2026 heygoldi.de — ein Projekt von <a href="https://msbls.de">msbls.de</a></p>
<p class="footer-copy">&copy; 2026 heygoldi.de</p>
</footer>
<script src="/shared/impressum.js"></script>
</body>
</html>

View File

@@ -440,5 +440,6 @@
</footer>
<script src="/shared/i18n.js"></script>
<script src="/shared/impressum.js" data-owner="flexsiebels" data-variant="full"></script>
</body>
</html>

View File

@@ -550,5 +550,6 @@
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
</script>
<script src="/shared/impressum.js" data-owner="flexsiebels" data-variant="full"></script>
</body>
</html>

View File

@@ -436,5 +436,6 @@
</div>
</footer>
<script src="/shared/impressum.js" data-owner="flexsiebels" data-variant="full"></script>
</body>
</html>

View File

@@ -392,5 +392,6 @@
</div>
</footer>
<script src="/shared/impressum.js" data-owner="martinsiebels" data-variant="full"></script>
</body>
</html>

View File

@@ -724,9 +724,10 @@
<!-- FOOTER -->
<footer>
<p class="footer-brand">Paragraphen<span>rAI</span>ter</p>
<p class="footer-credit">Ein Projekt von Matthias Flexsiebels</p>
<p class="footer-legal">&copy; 2026 &middot; Recht. Aber schlauer.</p>
</footer>
<script src="/shared/impressum.js"></script>
</body>
</html>

View File

@@ -650,7 +650,7 @@
<footer>
<p class="footer-brand">Patentonkel</p>
<p class="footer-tagline">Familiär. Kompetent. KI.</p>
<p class="footer-legal">&copy; 2026 &middot; Ein Projekt von Matthias Flexsiebels &middot; <a href="#" onclick="window.__impressum&&window.__impressum();return false;">Impressum</a></p>
<p class="footer-legal">&copy; 2026 &middot; Familienkompatibel ausgedacht.</p>
</footer>
<script src="/shared/impressum.js"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sMARTin 3D — Smart in 3D</title>
<meta name="description" content="sMARTin 3D — Dein 3D-Druck-Shop. Individuelle Teile, Prototypen, Kleinserien. Smart gedacht, praezise gedruckt.">
<meta name="description" content="sMARTin 3D — Dein 3D-Druck-Shop. Individuelle Teile, Prototypen, Kleinserien. Smart gedacht, präzise gedruckt.">
<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>">
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500&display=swap');
@@ -26,7 +26,7 @@
--blue-glow: rgba(56, 189, 248, 0.15);
}
html { scroll-behavior: smooth; }
html { scroll-behavior: smooth; overflow-x: hidden; }
body {
font-family: 'Inter', sans-serif;
@@ -37,39 +37,41 @@
overflow-x: hidden;
}
body::before {
content: '';
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 9999;
}
.container { max-width: 860px; margin: 0 auto; padding: 0 24px; }
/* Nav */
nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
padding: 20px 0;
padding: 20px 24px;
background: rgba(10, 10, 12, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center;
max-width: 100vw; box-sizing: border-box;
}
nav .container { display: flex; justify-content: space-between; align-items: center; }
.logo { font-family: 'Space Grotesk', sans-serif; font-size: 1.1rem; font-weight: 600; }
.logo { font-family: 'Space Grotesk', sans-serif; font-size: 1.1rem; font-weight: 600; white-space: nowrap; }
.logo .s { color: var(--text-dim); font-weight: 300; text-transform: lowercase; }
.logo .martin { color: var(--orange); }
.logo .three { color: var(--blue); font-weight: 700; }
.logo .d { color: var(--text); }
nav a { color: var(--text-dim); text-decoration: none; font-size: 0.85rem; transition: color 0.2s; }
.nav-links { display: flex; gap: 16px; align-items: center; }
nav a { color: var(--text-dim); text-decoration: none; font-size: 0.85rem; transition: color 0.2s; white-space: nowrap; }
nav a:hover { color: var(--text); }
.nav-toggle { display: none; background: none; border: none; color: var(--text-dim); font-size: 1.4rem; cursor: pointer; padding: 4px; }
@media (max-width: 600px) {
.nav-links { display: none; position: absolute; top: 100%; right: 24px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 0 0 12px 12px; flex-direction: column; padding: 16px 24px; gap: 14px; min-width: 160px; }
.nav-links.open { display: flex; }
.nav-toggle { display: block; }
nav a { font-size: 0.9rem; }
}
/* Hero */
.hero {
padding: 180px 0 120px;
text-align: center;
position: relative;
overflow: visible;
}
.hero::before {
@@ -77,46 +79,80 @@
position: absolute;
top: 60px; left: 50%;
transform: translateX(-50%);
width: 700px; height: 500px;
width: 100vw; height: 400px;
background:
radial-gradient(ellipse at 40% 50%, var(--orange-glow) 0%, transparent 60%),
radial-gradient(ellipse at 60% 40%, var(--blue-glow) 0%, transparent 60%);
radial-gradient(ellipse at 40% 50%, var(--orange-glow) 0%, transparent 50%),
radial-gradient(ellipse at 60% 40%, var(--blue-glow) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 3D cube animation */
.cube-wrapper {
perspective: 600px;
width: 80px; height: 80px;
/* 3D text animation */
.text3d-wrapper {
perspective: 800px;
margin: 0 auto 40px;
display: flex; justify-content: center;
padding: 60px 40px;
}
.cube {
width: 80px; height: 80px;
position: relative;
.text3d {
transform-style: preserve-3d;
animation: rotateCube 12s linear infinite;
animation: rotateText 10s ease-in-out infinite;
transition: transform 0.1s ease-out;
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(3rem, 10vw, 5rem);
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
position: relative;
display: inline-block;
}
@keyframes rotateCube {
from { transform: rotateX(-20deg) rotateY(0deg); }
to { transform: rotateX(-20deg) rotateY(360deg); }
.text3d-face {
display: block;
color: var(--orange);
}
.cube-face {
position: absolute;
width: 80px; height: 80px;
border: 1px solid var(--orange);
background: rgba(249, 115, 22, 0.04);
opacity: 0.7;
.text3d-depth {
position: absolute; top: 0; left: 0;
}
.cube-face:nth-child(1) { transform: translateZ(40px); }
.cube-face:nth-child(2) { transform: rotateY(180deg) translateZ(40px); }
.cube-face:nth-child(3) { transform: rotateY(90deg) translateZ(40px); }
.cube-face:nth-child(4) { transform: rotateY(-90deg) translateZ(40px); }
.cube-face:nth-child(5) { transform: rotateX(90deg) translateZ(40px); }
.cube-face:nth-child(6) { transform: rotateX(-90deg) translateZ(40px); }
.text3d-depth:nth-child(2) { transform: translateZ(-1px); color: rgb(249,115,22); }
.text3d-depth:nth-child(3) { transform: translateZ(-2px); color: rgb(242,117,29); }
.text3d-depth:nth-child(4) { transform: translateZ(-3px); color: rgb(235,120,37); }
.text3d-depth:nth-child(5) { transform: translateZ(-4px); color: rgb(229,122,45); }
.text3d-depth:nth-child(6) { transform: translateZ(-5px); color: rgb(222,125,53); }
.text3d-depth:nth-child(7) { transform: translateZ(-6px); color: rgb(215,127,60); }
.text3d-depth:nth-child(8) { transform: translateZ(-7px); color: rgb(209,130,68); }
.text3d-depth:nth-child(9) { transform: translateZ(-8px); color: rgb(202,132,76); }
.text3d-depth:nth-child(10) { transform: translateZ(-9px); color: rgb(195,135,84); }
.text3d-depth:nth-child(11) { transform: translateZ(-10px); color: rgb(189,137,92); }
.text3d-depth:nth-child(12) { transform: translateZ(-11px); color: rgb(182,140,99); }
.text3d-depth:nth-child(13) { transform: translateZ(-12px); color: rgb(175,143,107); }
.text3d-depth:nth-child(14) { transform: translateZ(-13px); color: rgb(169,145,115); }
.text3d-depth:nth-child(15) { transform: translateZ(-14px); color: rgb(162,148,123); }
.text3d-depth:nth-child(16) { transform: translateZ(-15px); color: rgb(155,150,131); }
.text3d-depth:nth-child(17) { transform: translateZ(-16px); color: rgb(149,153,138); }
.text3d-depth:nth-child(18) { transform: translateZ(-17px); color: rgb(142,155,146); }
.text3d-depth:nth-child(19) { transform: translateZ(-18px); color: rgb(135,158,154); }
.text3d-depth:nth-child(20) { transform: translateZ(-19px); color: rgb(129,160,162); }
.text3d-depth:nth-child(21) { transform: translateZ(-20px); color: rgb(122,163,170); }
.text3d-depth:nth-child(22) { transform: translateZ(-21px); color: rgb(115,166,177); }
.text3d-depth:nth-child(23) { transform: translateZ(-22px); color: rgb(109,168,185); }
.text3d-depth:nth-child(24) { transform: translateZ(-23px); color: rgb(102,171,193); }
.text3d-depth:nth-child(25) { transform: translateZ(-24px); color: rgb(95,173,201); }
.text3d-depth:nth-child(26) { transform: translateZ(-25px); color: rgb(89,176,209); }
.text3d-depth:nth-child(27) { transform: translateZ(-26px); color: rgb(82,178,216); }
.text3d-depth:nth-child(28) { transform: translateZ(-27px); color: rgb(75,181,224); }
.text3d-depth:nth-child(29) { transform: translateZ(-28px); color: rgb(69,183,232); }
.text3d-depth:nth-child(30) { transform: translateZ(-29px); color: rgb(62,186,240); }
.text3d-depth:nth-child(31) { transform: translateZ(-30px); color: rgb(56,189,248); }
@keyframes rotateText {
0% { transform: rotateX(-20deg) rotateY(-45deg); }
50% { transform: rotateX(-20deg) rotateY(45deg); }
100% { transform: rotateX(-20deg) rotateY(-45deg); }
}
h1 {
font-family: 'Space Grotesk', sans-serif;
@@ -201,6 +237,92 @@
.service-card h3 { font-family: 'Space Grotesk', sans-serif; font-size: 0.95rem; font-weight: 600; margin-bottom: 8px; }
.service-card p { color: var(--text-dim); font-size: 0.84rem; line-height: 1.6; font-weight: 300; }
/* Gallery */
.gallery-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.gallery-item {
border-radius: 12px;
overflow: hidden;
aspect-ratio: 4/3;
border: 1px solid var(--border);
transition: border-color 0.3s, transform 0.3s;
cursor: pointer;
}
.gallery-item:hover {
border-color: var(--orange);
transform: scale(1.02);
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.gallery-item figcaption {
padding: 12px 14px;
font-size: 0.78rem;
color: var(--text-dim);
line-height: 1.5;
font-weight: 300;
background: var(--bg-card);
}
.gallery-item figcaption strong {
color: var(--text);
font-weight: 500;
display: block;
margin-bottom: 4px;
font-family: 'Space Grotesk', sans-serif;
font-size: 0.82rem;
}
.gallery-item figure {
margin: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.gallery-item figure img {
flex: 1;
min-height: 0;
}
.gallery-item.has-caption {
aspect-ratio: auto;
}
@media (max-width: 640px) {
.gallery-grid { grid-template-columns: repeat(2, 1fr); }
}
/* Lightbox */
.lightbox {
display: none; position: fixed; inset: 0; z-index: 200;
background: rgba(0,0,0,0.9); backdrop-filter: blur(10px);
justify-content: center; align-items: center;
cursor: pointer;
}
.lightbox.active { display: flex; }
.lightbox img {
max-width: 90vw; max-height: 85vh;
object-fit: contain; border-radius: 8px;
}
.lightbox-caption {
position: fixed; bottom: 24px; left: 50%;
transform: translateX(-50%);
color: var(--text-dim); font-size: 0.9rem;
font-family: 'Space Grotesk', sans-serif;
text-align: center; max-width: 80vw;
}
/* Process */
.process-steps {
display: grid;
@@ -266,31 +388,58 @@
<body>
<nav>
<div class="container">
<div class="logo"><span class="s">s</span><span class="martin">MARTIN</span> <span class="three">3</span><span class="d">D</span></div>
<a href="#kontakt">Anfrage</a>
<div class="logo"><span class="s">s</span><span class="martin">MARTIN</span> <span class="three">3</span><span class="d">D</span></div>
<button class="nav-toggle" onclick="document.querySelector('.nav-links').classList.toggle('open')" aria-label="Menu"></button>
<div class="nav-links">
<a href="https://www.instagram.com/smartin3d/" target="_blank" rel="noopener">Instagram</a>
<a href="https://www.etsy.com/shop/Smartin3de" target="_blank" rel="noopener">Etsy Shop</a>
<a href="#kontakt" onclick="document.querySelector('.nav-links').classList.remove('open')">Anfrage</a>
</div>
</nav>
<section class="hero">
<div class="container">
<div class="cube-wrapper">
<div class="cube">
<div class="cube-face"></div>
<div class="cube-face"></div>
<div class="cube-face"></div>
<div class="cube-face"></div>
<div class="cube-face"></div>
<div class="cube-face"></div>
<div class="text3d-wrapper">
<div class="text3d">
<span class="text3d-face">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
<span class="text3d-depth" aria-hidden="true">smart</span>
</div>
</div>
<h1><span class="s">s</span><span class="martin">MARTIN</span> <span class="three">3</span>D</h1>
<div class="tagline">Smart in 3D</div>
<p class="hero-desc">
<strong>3D-Druck</strong> fuer Ideen, die nicht warten koennen.
Vom Prototyp bis zur Kleinserie — praezise, schnell und genau so,
<strong>3D-Druck</strong> für Ideen, die nicht warten können.
Vom Prototyp bis zur Kleinserie — präzise, schnell und genau so,
wie du es brauchst.
</p>
@@ -308,8 +457,8 @@
<div class="section-label">Leistungen</div>
<h2>Was wir drucken.</h2>
<div class="section-desc">
Egal ob Einzelstueck oder Serie — jedes Teil wird individuell
fuer deinen Anwendungsfall optimiert.
Egal ob Einzelstück oder Serie — jedes Teil wird individuell
für deinen Anwendungsfall optimiert.
</div>
<div class="services-grid">
@@ -322,13 +471,13 @@
<div class="service-card">
<div class="service-icon">📦</div>
<h3>Kleinserien</h3>
<p>10 bis 500 Stueck. Ohne Werkzeugkosten, ohne Mindestmengen, ohne Wartezeit.</p>
<p>10 bis 500 Stück. Ohne Werkzeugkosten, ohne Mindestmengen, ohne Wartezeit.</p>
</div>
<div class="service-card">
<div class="service-icon">⚙️</div>
<h3>Funktionsteile</h3>
<p>Mechanisch belastbar, massgenau, einsatzbereit. Fuer Maschinen, Geraete, Anlagen.</p>
<p>Mechanisch belastbar, maßgenau, einsatzbereit. Für Maschinen, Geräte, Anlagen.</p>
</div>
<div class="service-card">
@@ -354,13 +503,69 @@
<div class="divider"></div>
<section id="galerie">
<div class="container">
<div class="section-label">Galerie</div>
<h2>Unsere Drucke.</h2>
<p style="color: var(--text-dim); font-size: 1rem; margin-bottom: 56px; font-weight: 300; line-height: 1.7;">
Vom Prototyp bis zum fertigen Teil — ein paar Beispiele aus unserer Werkstatt.
</p>
<div class="gallery-grid">
<div class="gallery-item has-caption">
<figure>
<img src="img/vw-golf-mk1-abdeckung.jpg" alt="VW Golf MK1 Kofferraumklappe Abdeckung">
<figcaption>
<strong>VW Golf MK1 Kofferraumklappe</strong>
Abdeckung für die offenen Löcher in der Kofferraumklappe des alten Golfs. Auf Wunsch auch mit Kunstleder oder anderen Folien lieferbar.
</figcaption>
</figure>
</div>
<div class="gallery-item has-caption">
<figure>
<img src="img/flachkabel-halter.jpg" alt="Flachkabelhalter für Wandmontage">
<figcaption>
<strong>Flachkabelhalter für die Wandmontage</strong>
</figcaption>
</figure>
</div>
<div class="gallery-item has-caption">
<figure>
<img src="img/muenzhalter.jpg" alt="Münzhalter zum Reinigen von Sammlermünzen">
<figcaption>
<strong>Münzhalter zum Reinigen von Sammlermünzen</strong>
</figcaption>
</figure>
</div>
<div class="gallery-item has-caption">
<figure>
<img src="img/schaltanlage-panelhalter.jpg" alt="Schaltanlage Panelhalter Abdeckungen">
<figcaption>
<strong>Schaltschrank Abdeckungshalter für alte Schaltschränke in der Industrie</strong>
</figcaption>
</figure>
</div>
<div class="gallery-item has-caption">
<figure>
<img src="img/sonnenblendenhalter.jpg" alt="Sonnenblendenhalter für alte Opel Astra Modelle">
<figcaption>
<strong>Sonnenblendenhalter für alte Opel Astra Modelle</strong>
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<div class="divider"></div>
<section>
<div class="container">
<div class="section-label">Prozess</div>
<h2>In 4 Schritten zum Teil.</h2>
<div class="section-desc">
Kein komplizierter Bestellprozess. Schick uns dein Modell oder deine Idee —
wir kuemmern uns um den Rest.
wir kümmern uns um den Rest.
</div>
<div class="process-steps">
@@ -377,7 +582,7 @@
<div class="process-step">
<div class="process-num">03</div>
<h3>Druck</h3>
<p>Produktion auf professionellen Druckern. Qualitaetskontrolle bei jedem Teil.</p>
<p>Produktion auf professionellen Druckern. Qualitätskontrolle bei jedem Teil.</p>
</div>
<div class="process-step">
<div class="process-num">04</div>
@@ -395,7 +600,7 @@
<div class="section-label">Materialien</div>
<h2>Was wir verarbeiten.</h2>
<div class="section-desc">
Verschiedene Materialien fuer verschiedene Anforderungen.
Verschiedene Materialien für verschiedene Anforderungen.
</div>
<div class="material-tags">
@@ -404,7 +609,7 @@
<span class="material-tag">ABS</span>
<span class="material-tag">TPU (flexibel)</span>
<span class="material-tag">Nylon</span>
<span class="material-tag">ASA (UV-bestaendig)</span>
<span class="material-tag">ASA (UV-beständig)</span>
<span class="material-tag">Carbon-Verbund</span>
<span class="material-tag">Holz-Filament</span>
</div>
@@ -422,27 +627,73 @@
Wir melden uns innerhalb von 24 Stunden.
</div>
<a href="mailto:smartin3d@msbls.de" class="btn btn-primary">Projekt anfragen</a>
<a href="https://www.etsy.com/shop/Smartin3de" target="_blank" rel="noopener" class="btn btn-ghost">🛒 Etsy Shop</a>
<a href="https://www.instagram.com/smartin3d/" target="_blank" rel="noopener" class="btn btn-ghost">📸 Instagram</a>
</div>
</section>
<footer>
<div class="container">
<p><span style="color:var(--text-dim)">s</span><span style="color:var(--orange)">MARTIN</span> <span style="color:var(--blue)">3</span>D — smartin3.de</p>
<p style="margin-top: 24px; font-size: 0.65rem; color: var(--text-muted); line-height: 1.8;"><a href="#impressum" style="color: var(--text-muted); text-decoration: none;">Impressum</a></p>
<p style="margin-top: 24px; font-size: 0.65rem; color: var(--text-muted); line-height: 1.8;"><a href="#impressum-section" style="color: var(--text-muted); text-decoration: none;">Impressum</a></p>
</div>
</footer>
<section id="impressum" style="padding: 60px 0 80px; border-top: 1px solid var(--border);">
<section id="impressum-section" style="padding: 60px 0 80px; border-top: 1px solid var(--border);">
<div class="container" style="max-width: 540px;">
<h2 style="font-size: 1.2rem; margin-bottom: 24px;">Impressum</h2>
<p style="font-size: 0.85rem; color: var(--text-dim); line-height: 1.8; font-weight: 300;">
Martin Siebels<br>
Leyer Str. 38<br>
49076 Osnabrück<br><br>
E-Mail: <a href="mailto:Martin_Siebels@web.de" style="color: var(--orange); text-decoration: none;">Martin_Siebels@web.de</a>
</p>
<div id="impressum" style="font-size: 0.85rem; color: var(--text-dim); line-height: 1.8; font-weight: 300;"></div>
</div>
</section>
<script src="/shared/impressum.js" data-owner="martinsiebels" data-variant="full"></script>
<div class="lightbox" id="lightbox" onclick="this.classList.remove('active')">
<img id="lightbox-img" src="" alt="">
<div class="lightbox-caption" id="lightbox-cap"></div>
</div>
<script>
document.querySelectorAll('.gallery-item').forEach(function(item) {
item.addEventListener('click', function() {
var img = item.querySelector('img');
var cap = item.querySelector('figcaption strong');
document.getElementById('lightbox-img').src = img.src;
document.getElementById('lightbox-cap').textContent = cap ? cap.textContent : '';
document.getElementById('lightbox').classList.add('active');
});
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') document.getElementById('lightbox').classList.remove('active');
});
// Interactive 3D text
(function() {
var hero = document.querySelector('.hero');
var text = document.querySelector('.text3d');
if (!hero || !text) return;
function handleMove(x, y) {
var w = window.innerWidth;
var cx = x / w - 0.5;
var cy = y / (hero.getBoundingClientRect().bottom + 100) - 0.4;
var rotY = cx * 90;
var rotX = -cy * 30;
text.style.animation = 'none';
text.style.transform = 'rotateX(' + rotX + 'deg) rotateY(' + rotY + 'deg)';
}
hero.addEventListener('mousemove', function(e) { handleMove(e.clientX, e.clientY); });
hero.addEventListener('touchmove', function(e) {
e.preventDefault();
handleMove(e.touches[0].clientX, e.touches[0].clientY);
}, {passive: false});
hero.addEventListener('mouseleave', function() {
text.style.animation = '';
text.style.transform = '';
});
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,399 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Comunio AI Assistant — Anleitung</title>
<meta name="description" content="Schritt-für-Schritt-Anleitung: KI-Assistent für Comunio Fantasy Bundesliga mit Claude Code + Playwright">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0a0b0d;
--surface: #12141a;
--surface-2: #1a1c24;
--border: #1e2028;
--text: #e8e8ec;
--muted: #8888a0;
--accent: #22c55e;
--accent-dim: rgba(34, 197, 94, 0.12);
--orange: #f97316;
--orange-dim: rgba(249, 115, 22, 0.12);
--blue: #3b82f6;
}
html { scroll-behavior: smooth; }
body {
font-family: 'Space Grotesk', system-ui, sans-serif;
background: var(--bg);
color: var(--text);
-webkit-font-smoothing: antialiased;
line-height: 1.7;
}
.grain {
position: fixed; inset: 0; z-index: 9999; pointer-events: none; opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.container { max-width: 760px; margin: 0 auto; padding: 0 24px; }
/* Header */
header {
padding: 60px 0 40px;
text-align: center;
border-bottom: 1px solid var(--border);
}
header .badge {
display: inline-block;
background: var(--accent-dim);
color: var(--accent);
padding: 6px 14px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
margin-bottom: 16px;
}
h1 {
font-size: clamp(1.8rem, 5vw, 2.8rem);
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 12px;
}
header p {
color: var(--muted);
font-size: 1.05rem;
max-width: 500px;
margin: 0 auto;
}
/* Content */
.content { padding: 48px 0 80px; }
h2 {
font-size: 1.4rem;
font-weight: 700;
margin-top: 48px;
margin-bottom: 16px;
padding-top: 32px;
border-top: 1px solid var(--border);
}
h2:first-child { border-top: none; padding-top: 0; margin-top: 0; }
h3 {
font-size: 1.1rem;
font-weight: 600;
margin-top: 32px;
margin-bottom: 12px;
color: var(--accent);
}
p { margin-bottom: 16px; color: var(--text); }
ul, ol {
margin-bottom: 16px;
padding-left: 24px;
}
li {
margin-bottom: 8px;
color: var(--text);
}
strong { color: var(--text); font-weight: 600; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* Code */
code {
font-family: 'JetBrains Mono', monospace;
background: var(--surface);
padding: 2px 8px;
border-radius: 6px;
font-size: 0.85rem;
color: var(--orange);
}
pre {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
overflow-x: auto;
}
pre code {
background: none;
padding: 0;
color: var(--muted);
font-size: 0.85rem;
line-height: 1.8;
}
.cmd { color: var(--text); }
.comment { color: #555; }
.highlight { color: var(--accent); }
/* Architecture diagram */
.arch {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 28px;
margin: 24px 0;
text-align: center;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
line-height: 2;
color: var(--muted);
}
.arch .you { color: var(--accent); font-weight: 600; }
.arch .claude { color: var(--orange); font-weight: 600; }
.arch .pw { color: var(--blue); font-weight: 600; }
.arch .arrow { color: #444; }
/* Table */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 24px;
}
th {
text-align: left;
padding: 12px 16px;
background: var(--surface);
border: 1px solid var(--border);
font-weight: 600;
font-size: 0.85rem;
color: var(--accent);
}
td {
padding: 12px 16px;
border: 1px solid var(--border);
font-size: 0.9rem;
}
/* Cards */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 24px;
margin-bottom: 16px;
}
.card h4 {
font-size: 1rem;
margin-bottom: 8px;
}
.card p { color: var(--muted); font-size: 0.9rem; margin-bottom: 0; }
/* Warning/Info boxes */
.info {
background: var(--accent-dim);
border-left: 3px solid var(--accent);
padding: 16px 20px;
border-radius: 0 12px 12px 0;
margin: 20px 0;
font-size: 0.9rem;
}
.warning {
background: var(--orange-dim);
border-left: 3px solid var(--orange);
padding: 16px 20px;
border-radius: 0 12px 12px 0;
margin: 20px 0;
font-size: 0.9rem;
}
/* Footer */
footer {
text-align: center;
padding: 40px 0;
color: var(--muted);
font-size: 0.8rem;
border-top: 1px solid var(--border);
}
@media (max-width: 640px) {
header { padding: 40px 0 30px; }
.content { padding: 32px 0 60px; }
pre { padding: 14px; }
table { font-size: 0.8rem; }
th, td { padding: 8px 10px; }
}
</style>
</head>
<body>
<div class="grain"></div>
<header>
<div class="container">
<div class="badge">Anfänger-Guide</div>
<h1>Comunio AI Assistant</h1>
<p>Dein KI-Manager für die Fantasy Bundesliga. Claude Code + Playwright steuern dein Team im Browser.</p>
</div>
</header>
<div class="content">
<div class="container">
<h2>Was ist das?</h2>
<p><a href="https://comunio.de">Comunio</a> ist Deutschlands ältester Fantasy-Fußball-Manager (seit 2000, 600.000+ Nutzer). Du managst ein virtuelles Bundesliga-Team — kaufst und verkaufst Spieler auf dem Transfermarkt, stellst dein Team für jeden Spieltag auf und sammelst Punkte.</p>
<p>Diese Anleitung zeigt dir, wie du einen <strong>KI-Assistenten</strong> einrichtest, der dein Comunio-Team über den Browser steuert — automatisch, per Sprachbefehl.</p>
<h2>Was kann der Assistent?</h2>
<ul>
<li><strong>Aufstellung setzen</strong> vor jedem Spieltag</li>
<li><strong>Gebote abgeben</strong> auf dem Transfermarkt (Deadline: täglich 3:00 Uhr)</li>
<li><strong>Spielerwerte beobachten</strong> und Markttrends erkennen</li>
<li><strong>Ergebnisse checken</strong> nach dem Spieltag</li>
<li><strong>Kader analysieren</strong> und Schwachstellen finden</li>
<li><strong>Tauschangebote</strong> annehmen oder ablehnen</li>
</ul>
<h2>So funktioniert's</h2>
<div class="arch">
<span class="you">Du:</span> "Stell mein Team für den nächsten Spieltag auf"<br>
<span class="arrow"></span><br>
<span class="claude">Claude Code</span> (versteht was du willst, plant die Aktionen)<br>
<span class="arrow"></span><br>
<span class="pw">Playwright</span> (steuert den Browser — klickt, tippt, liest)<br>
<span class="arrow"></span><br>
comunio.de
</div>
<h2>Einrichtung (10 Minuten)</h2>
<h3>1. Claude Code installieren</h3>
<pre><code><span class="comment"># Im Terminal eingeben:</span>
<span class="cmd">npm install -g @anthropic-ai/claude-code</span></code></pre>
<p>Du brauchst einen <a href="https://claude.ai">Anthropic-Account</a>. Beim ersten Start wirst du zum Login aufgefordert.</p>
<h3>2. Projektordner erstellen</h3>
<pre><code><span class="cmd">mkdir ~/comunio-assistant</span>
<span class="cmd">cd ~/comunio-assistant</span></code></pre>
<h3>3. Playwright aktivieren</h3>
<pre><code><span class="comment"># Claude Code starten:</span>
<span class="cmd">claude</span>
<span class="comment"># Dann in Claude tippen:</span>
<span class="cmd">/mcp</span>
<span class="comment"># → "playwright" auswählen → Enable</span></code></pre>
<p>Damit kann Claude einen Browser öffnen und Webseiten bedienen — genau wie du es manuell tun würdest.</p>
<h3>4. CLAUDE.md erstellen</h3>
<p>Erstelle eine Datei namens <code>CLAUDE.md</code> in deinem Projektordner. Das ist die "Jobbeschreibung" für deinen Assistenten:</p>
<pre><code><span class="highlight"># Comunio Assistant</span>
Du managst mein Comunio Fantasy-Fußball-Team via Browser (Playwright).
<span class="highlight">## Zugang</span>
- URL: https://classic.comunio.de (einfacher zu automatisieren)
- Login: Frag mich nach den Zugangsdaten wenn nötig
<span class="highlight">## Punktesystem</span>
- Note 1.0 = 12 Pkt, 1.5 = 10, 2.0 = 8, 2.5 = 6, 3.0 = 4
- Tor-Bonus: TW +6, ABW +5, MF +4, ST +3
- Gelb-Rot: -3, Rot: -6, Leerer Platz: -4 pro Spieltag
<span class="highlight">## Transfers</span>
- Verdeckte Gebote — höchstes Gebot gewinnt
- Berechnung täglich um 3:00 Uhr
- IMMER fragen bevor du bietest!
<span class="highlight">## Regeln</span>
- Vor jeder Aktion die Budget kosten zeigen
- Screenshots machen bei wichtigen Aktionen
- Nie ohne meine Bestätigung bieten oder tauschen</code></pre>
<h2>Benutzung</h2>
<p>Starte Claude Code in deinem Projektordner:</p>
<pre><code><span class="cmd">cd ~/comunio-assistant</span>
<span class="cmd">claude</span></code></pre>
<p>Dann sprich einfach mit Claude:</p>
<table>
<tr><th>Du sagst</th><th>Claude macht</th></tr>
<tr><td>"Log mich ein"</td><td>Öffnet Browser, navigiert zum Login, fragt nach Zugangsdaten</td></tr>
<tr><td>"Zeig mir mein Team"</td><td>Geht zur Kaderseite, listet Spieler mit Werten</td></tr>
<tr><td>"Stell das beste Team auf"</td><td>Prüft Verletzungen/Sperren, stellt optimale Formation auf</td></tr>
<tr><td>"Was gibt's auf dem Transfermarkt?"</td><td>Listet verfügbare Spieler, zeigt gute Deals</td></tr>
<tr><td>"Biete 3M auf Wirtz"</td><td>Gibt Gebot ab (nach deiner Bestätigung)</td></tr>
<tr><td>"Wie lief der Spieltag?"</td><td>Zeigt deine Punkte, Einzelbewertungen, Ranking</td></tr>
</table>
<h2>Tipps</h2>
<div class="card">
<h4>Nutze classic.comunio.de</h4>
<p>Die Classic-Version ist einfacheres HTML (PHP-gerendert). Viel leichter für den Assistenten zu lesen als die moderne React-App.</p>
</div>
<div class="card">
<h4>Transfer-Deadline beachten</h4>
<p>Täglich um 3:00 Uhr werden Gebote ausgewertet. Frag abends: "Zeig mir Spieler unter 2M die gut bewertet wurden" — dann bieten.</p>
</div>
<div class="card">
<h4>Spieltag-Routine</h4>
<p>Vor dem Spieltag: "Ist jemand verletzt oder gesperrt?" → "Stell mein Team auf". Nach dem Spieltag: "Wie lief's?"</p>
</div>
<div class="card">
<h4>Screenshots</h4>
<p>Lass Claude regelmäßig Screenshots machen. So siehst du immer, was gerade passiert — und hast einen Nachweis deiner Aktionen.</p>
</div>
<div class="warning">
<strong>Warum kein API?</strong> Comunio hat keine offizielle API. Alte Python-Wrapper sind längst kaputt. Browser-Automatisierung via Playwright ist der einzige zuverlässige Weg — und funktioniert mit jeder Comunio-Version.
</div>
<h2>Häufige Fragen</h2>
<div class="card">
<h4>Brauche ich Programmierkenntnisse?</h4>
<p>Nein. Du musst nur ein Terminal öffnen und drei Befehle eintippen können. Den Rest macht Claude.</p>
</div>
<div class="card">
<h4>Kostet das was?</h4>
<p>Claude Code braucht einen Anthropic-Account (API-Kosten je nach Nutzung, ca. ein paar Euro/Monat bei normalem Gebrauch). Comunio selbst hat eine kostenlose Basic-Version.</p>
</div>
<div class="card">
<h4>Ist das erlaubt?</h4>
<p>Comunio hat keine öffentliche Policy gegen Browser-Automatisierung. Sei trotzdem fair: nicht im Sekundentakt Anfragen schicken, und spiel sportlich.</p>
</div>
<div class="card">
<h4>Was wenn was schiefgeht?</h4>
<p>Claude fragt vor jeder wichtigen Aktion (Gebote, Tausch, Aufstellung speichern). Du kannst jederzeit "Stop" sagen.</p>
</div>
</div>
</div>
<footer>
<div class="container">
<p><a href="https://traihard.de">traihard.de</a></p>
</div>
</footer>
</body>
</html>

View File

@@ -0,0 +1,435 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>traihard.de — Marginal Gains. Maximum Effort.</title>
<meta name="description" content="Trai Hard — Optimierungswahn in allen Lebensbereichen. Rennrad, Fantasy Football, Productivity, Fitness. Marginal Gains everywhere.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=Orbitron:wght@700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #080808;
--surface: #111114;
--border: #1c1c22;
--text: #f0f0f2;
--muted: #777790;
--accent: #ff3e00;
--accent-glow: rgba(255, 62, 0, 0.12);
}
html { scroll-behavior: smooth; }
body {
font-family: 'Space Grotesk', system-ui, sans-serif;
background: var(--bg);
color: var(--text);
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
.grain {
position: fixed; inset: 0; z-index: 9999; pointer-events: none; opacity: 0.04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.container { max-width: 960px; margin: 0 auto; padding: 0 24px; }
/* Hero */
.hero {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
position: relative;
}
.hero::before {
content: '';
position: absolute;
top: -10%;
left: 50%;
transform: translateX(-50%);
width: 700px;
height: 700px;
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 65%);
pointer-events: none;
}
.watts {
font-family: 'Orbitron', sans-serif;
font-size: 0.75rem;
letter-spacing: 0.3em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 24px;
opacity: 0.8;
}
.logo {
font-size: clamp(3.5rem, 12vw, 8rem);
font-weight: 700;
letter-spacing: -0.04em;
line-height: 0.9;
margin-bottom: 20px;
}
.logo .ai { color: var(--accent); }
.tagline {
font-size: clamp(1.1rem, 2.5vw, 1.4rem);
color: var(--muted);
margin-bottom: 48px;
max-width: 600px;
line-height: 1.6;
}
.hr-line {
width: 60px;
height: 2px;
background: var(--accent);
margin: 0 auto 48px;
opacity: 0.6;
}
/* Manifesto */
.manifesto {
padding: 80px 0;
}
.manifesto-text {
max-width: 640px;
margin: 0 auto;
font-size: 1.15rem;
line-height: 1.9;
color: var(--muted);
text-align: center;
}
.manifesto-text strong { color: var(--text); font-weight: 600; }
.manifesto-text em { color: var(--accent); font-style: normal; }
/* Stats */
.stats {
padding: 40px 0 80px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.stat {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 28px 20px;
text-align: center;
}
.stat-value {
font-family: 'Orbitron', sans-serif;
font-size: 1.8rem;
font-weight: 700;
color: var(--accent);
margin-bottom: 6px;
}
.stat-label {
font-size: 0.8rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
/* Domains */
.domains {
padding: 60px 0 80px;
}
.domains h2 {
text-align: center;
font-size: 1.6rem;
margin-bottom: 12px;
font-weight: 700;
}
.domains .subtitle {
text-align: center;
color: var(--muted);
margin-bottom: 40px;
font-size: 1rem;
}
.domains-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.domain-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 28px;
transition: border-color 0.2s, transform 0.2s;
}
.domain-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.domain-icon {
font-size: 2rem;
margin-bottom: 14px;
}
.domain-card h3 {
font-size: 1.05rem;
font-weight: 600;
margin-bottom: 8px;
}
.domain-card p {
font-size: 0.85rem;
color: var(--muted);
line-height: 1.6;
}
.domain-card a {
display: inline-block;
margin-top: 12px;
color: var(--accent);
font-size: 0.85rem;
text-decoration: none;
font-weight: 600;
}
.domain-card a:hover { text-decoration: underline; }
/* Gains */
.gains {
padding: 60px 0 80px;
}
.gains h2 {
text-align: center;
font-size: 1.6rem;
margin-bottom: 40px;
font-weight: 700;
}
.gains-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.gain-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 28px;
transition: border-color 0.2s, transform 0.2s;
}
.gain-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.gain-num {
font-family: 'Orbitron', sans-serif;
font-size: 0.7rem;
color: var(--accent);
letter-spacing: 0.2em;
margin-bottom: 12px;
}
.gain-card h3 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 8px;
}
.gain-card p {
font-size: 0.85rem;
color: var(--muted);
line-height: 1.6;
}
/* Quote */
.quote-section {
padding: 80px 0;
text-align: center;
}
.quote {
font-size: clamp(1.2rem, 3vw, 1.8rem);
font-weight: 600;
line-height: 1.5;
max-width: 640px;
margin: 0 auto 16px;
}
.quote-attr {
color: var(--muted);
font-size: 0.9rem;
}
/* Footer */
footer {
text-align: center;
padding: 40px 0;
color: var(--muted);
font-size: 0.75rem;
border-top: 1px solid var(--border);
}
@media (max-width: 768px) {
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.gains-grid { grid-template-columns: 1fr; }
.domains-grid { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
.hero { min-height: 80vh; }
}
</style>
</head>
<body>
<div class="grain"></div>
<section class="hero">
<div class="watts">OPTIMIZE EVERYTHING</div>
<div class="logo">tr<span class="ai">ai</span>hard</div>
<p class="tagline">Marginal Gains. Maximum Effort.<br>In allem. Immer. Ohne Kompromisse.</p>
</section>
<section class="manifesto">
<div class="container">
<div class="hr-line"></div>
<div class="manifesto-text">
Du wiegst dein <strong>Frühstück in Gramm ab</strong>, weil Haferflocken-Optimierung echte Gains bringt.
Du fährst bei <em>3°C im Regen</em>, weil Ruhetage für Schwache sind.
Du hast einen <strong>KI-Assistenten</strong>, der dein Fantasy-Team managed, weil manuelles Scouting ineffizient ist.
Dein Strava-Profil ist sorgfältiger kuratiert als dein LinkedIn.
Du trackst deinen <em>Schlaf</em>, dein <em>Wasser</em>, deine <em>Schritte</em> — und findest immer noch eine Metrik, die du optimieren kannst.
Du hast <strong>Automationen</strong> für Dinge gebaut, die andere Leute nicht mal als Problem erkannt haben.
<br><br>
Du bist <em>Tryhard</em>. Und du bist nicht allein.
</div>
</div>
</section>
<section class="stats">
<div class="container">
<div class="stats-grid">
<div class="stat">
<div class="stat-value">4.2</div>
<div class="stat-label">W/kg FTP</div>
</div>
<div class="stat">
<div class="stat-value">97</div>
<div class="stat-label">Automationen</div>
</div>
<div class="stat">
<div class="stat-value">1.</div>
<div class="stat-label">Comunio Liga</div>
</div>
<div class="stat">
<div class="stat-value">0</div>
<div class="stat-label">Ruhetage</div>
</div>
</div>
</div>
</section>
<section class="domains">
<div class="container">
<h2>Tryhard-Disziplinen</h2>
<p class="subtitle">Wer nur in einem Bereich optimiert, hat das Prinzip nicht verstanden.</p>
<div class="domains-grid">
<div class="domain-card">
<div class="domain-icon">🚴</div>
<h3>Rennrad</h3>
<p>Carbon-Rahmen. Titanschrauben. Aerosocken. Jedes Jedermannrennen wird behandelt wie eine Königsetappe. Beine rasiert — aus aerodynamischen Gründen, natürlich.</p>
</div>
<div class="domain-card">
<div class="domain-icon">🤖</div>
<h3>Comunio</h3>
<p>Warum selbst scouten, wenn ein KI-Assistent den Transfermarkt per Browser automatisiert? Gebote um 2:59 Uhr, perfektes Lineup, Datenanalyse auf Knopfdruck.</p>
<a href="/comunio">→ Comunio AI Guide</a>
</div>
<div class="domain-card">
<div class="domain-icon">💪</div>
<h3>Fitness</h3>
<p>Periodisierung. Makros. Progressive Overload. HRV-Tracking. Supplements nach Peer-Review. Das Bier danach ist „Recovery Nutrition".</p>
</div>
<div class="domain-card">
<div class="domain-icon"></div>
<h3>Productivity</h3>
<p>Jeder Workflow hat eine Automation. Jede Automation hat ein Dashboard. Jedes Dashboard hat ein Alert. Und wenn alles grün ist, optimierst du das Dashboard.</p>
</div>
</div>
</div>
</section>
<section class="gains">
<div class="container">
<h2>The Marginal Gains Protocol</h2>
<div class="gains-grid">
<div class="gain-card">
<div class="gain-num">GAIN 01</div>
<h3>Messen</h3>
<p>Wenn du es nicht misst, existiert es nicht. Watt, Kalorien, Schlaf, Screentime, Comunio-Punkteschnitt — alles wird getrackt.</p>
</div>
<div class="gain-card">
<div class="gain-num">GAIN 02</div>
<h3>Automatisieren</h3>
<p>Alles was du zweimal machst, verdient ein Script. Alles was ein Script macht, verdient einen KI-Agenten. Alles was ein Agent macht, verdient ein Monitoring.</p>
</div>
<div class="gain-card">
<div class="gain-num">GAIN 03</div>
<h3>Iterieren</h3>
<p>Version 1 ist nie gut genug. Version 47 ist ein guter Anfang. Der perfekte Prozess existiert nicht — aber die Annäherung macht süchtig.</p>
</div>
<div class="gain-card">
<div class="gain-num">GAIN 04</div>
<h3>Eliminieren</h3>
<p>Gewicht am Rad. Unnötige Meetings. Schlechte Spieler im Kader. Alles was keinen Mehrwert bringt, fliegt raus. Gnadenlos.</p>
</div>
<div class="gain-card">
<div class="gain-num">GAIN 05</div>
<h3>Investieren</h3>
<p>N+1 ist kein Meme, es ist eine Investitionsstrategie. Gilt für Räder, Tools, Subscriptions — und das Comunio-Pro-Upgrade.</p>
</div>
<div class="gain-card">
<div class="gain-num">GAIN 06</div>
<h3>Dominieren</h3>
<p>Ob Jedermannrennen oder Fantasy-Liga — Zweiter ist Erster der Verlierer. Der Tryhard kennt nur ein Ziel: oben stehen.</p>
</div>
</div>
</div>
</section>
<section class="quote-section">
<div class="container">
<div class="quote">"Andere Leute haben Hobbys. Wir haben KPIs."</div>
<div class="quote-attr">— Jeder Tryhard, immer</div>
</div>
</section>
<footer>
<div class="container">
<p>traihard.de</p>
</div>
</footer>
</body>
</html>

View File

@@ -0,0 +1,4 @@
domain: traihard.de
template: custom
title: "traihard.de"
description: "Trai Hard — AI-powered cycling routes"

View File

@@ -0,0 +1,385 @@
<!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>
<script src="/shared/impressum.js" data-owner="flexsiebels"></script>
</body>
</html>

View File

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