chore(qa): move Ethos design kit out of frontend/ into tracked design-system/

The kit (SKILL.md, ethos.tokens.css, ethos-icons.svg, .oxlintrc.json) lived
inside frontend/ — the exact dir freestyle runs scaffold into. vite refuses a
non-empty target, so every session improvised a stash-aside (frontend_backup/,
frontend_temp/) and left scratch dirs behind. Move the kit to a sibling
design-system/ so frontend/ starts clean, and deliver it via the L0 conventions
channel (project.toml) rather than seeding files in the scaffold target: copy
the token/icon assets in (don't read them), SKILL.md for rules only. Un-ignore
frontend/ so scaffolded output is tracked going forward.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMbPmZZjcXhR2crU82zZ8S
This commit is contained in:
2026-07-21 21:11:42 +04:00
parent ac4601562a
commit cf9eecc895
6 changed files with 343 additions and 3 deletions
+1
View File
@@ -6,6 +6,7 @@ conventions = [
"No bare try-catch; use runCatching and sealed domain error types",
"Every new EventPayload must be registered in the eventModule polymorphic block (Serialization.kt)",
"Track multi-session or handoff work as native tasks: search before creating to avoid duplicates (task_search), task_context before starting, claim before working, submit_for_review when ready, complete after review. Don't open a task for a single self-contained edit you finish now. When a goal has dependency seams or independent review points, task_decompose it into a parent + DEPENDS_ON-linked children (one approval) instead of one big task; a session works one task at a time, so siblings are claimed by later runs as they unblock.",
"UI uses the Ethos design system in ./design-system. COPY design-system/ethos.tokens.css and ethos-icons.svg into the project and import them — do NOT read their contents. All colors/spacing/radii come from the token CSS vars (never hardcode). Read design-system/SKILL.md only for component/layout rules.",
]
[commands]
-3
View File
@@ -81,6 +81,3 @@ apps/server/logs/
# local QA scratch workspace (nested git repo)
/qa/
# QA scaffold artifact (freestyle build-gate runs)
frontend/
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
+125
View File
@@ -0,0 +1,125 @@
---
name: ethos-design
description: Use when building or editing any user interface for an Ethos app (muzick, kdrive, nginx panel, xray manager, kaneo, or any new kvmx.ru app), when authoring or extending components in the @ethos/ui package, or when any frontend/React/HTML/SVG work must carry the Ethos visual identity. Triggers on requests to design a screen, build a component, add an app to the system, restyle existing UI, create icons, or set up per-app theming. Covers the token system, the five laws, the mono/sans split, warm-room depth, per-app accent + motif, the shared shell (desktop + mobile), copy voice, the trap list to refuse, and the render-based verification protocol.
---
# Ethos
Ethos is the shared design language for the kvmx.ru self-hosted apps. One system, one character, many apps. Switching from kdrive to the xray manager should feel like switching tabs in one instrument, not opening a different product.
## The thesis: instruments, not appliances, that don't lie
These apps are tools the operator runs their own infrastructure with — closer to an oscilloscope, a mixing desk, or good anodized audio gear than to a consumer product. Dense where it needs to be, every element earning its place, honest about the machinery instead of papering over it.
But not cold. The move nobody else makes: **warmth AND honesty in one room.** Consumer apps give warmth with no honesty (glossy, hides state behind a spinner). Dev dashboards give honesty with no warmth (cold, dense). Ethos is a calm, warm, well-lit space that still shows real throughput, real queue depth, real bytes. It can do both because the operator is the only user — the apps don't need to sell or hide anything.
Design failure looks like either extreme: a techy grey dashboard, or a glossy blurred-glass consumer screen. Both are the trap.
## The five laws (non-negotiable, shared across every app)
1. **Mono/sans split is law.** Geist Mono for anything the machine owns — ips, ports, hashes, sizes, timestamps, ids, paths, throughput, durations, bitrates, counts, percentages. Geist Sans for anything a human wrote — labels, prose, headings, track/file names. This single rule is the strongest signature; it must read identically across every app. A label is human (sans); its value is machine (mono). `Bitrate` in sans, `1411 kbps` in mono.
2. **Depth from light, not blur.** Elevation comes from soft warm shadows, layered surface steps in the neutral ramp, and 1px hairline borders. Never `backdrop-filter`/frosted glass, never glossy gradients. Reads engineered, not soft, and costs nothing per repaint.
3. **Shared shell.** Identical app frame everywhere — same rail/topbar geometry, same status grammar, same ⌘K command surface. See the shell anatomy below. Per-app difference is only accent + motif, never structure.
4. **Mechanical motion.** 120180ms, `cubic-bezier(0.2, 0, 0, 1)`, no bounce, no spring. Motion confirms a state change and gets out of the way. Respect `prefers-reduced-motion`.
5. **Show the machinery.** Real numbers over spinners. Queue depth, bytes/sec, buffer %, actual progress, indexed counts. A vague "loading…" is the appliance move — never do it. Empty and error states state what's true and what to do, in the interface's voice.
### The balance principle (how the accent behaves)
**Content is the color. Accent is the signal. Shell is quiet.** The apps hold warm content — album art, files, manga panels — and that content carries the color. The shell stays quiet and warm so the content glows. The accent is NOT fill-everything paint; it marks the one thing that matters: active nav, focus rings, primary action, the played portion of a scrubber. If a whole panel is washed in the accent, it's wrong — pull it back.
## Tokens
Single source of truth is `ethos.tokens.css` — the neutral system, the light-theme block, and the per-app override slots, shipped as CSS custom properties. (Generate typed TS from it later if an app wants typed access; the CSS stays authoritative so nothing drifts.) Values below are the dark-theme defaults.
### Neutral ramp — warm, brown-tinted (NOT cool grey, NOT cream)
```
--bg-0: #14110D /* deepest room */
--bg-1: #1B1712 /* surface */
--bg-2: #221D17 /* raised */
--bg-3: #2C261D /* hover / raised */
--bg-4: #372F24 /* pressed / high */
--line: rgba(244,234,220,0.09) /* hairline */
--line-hi: rgba(244,234,220,0.16) /* hairline emphasized */
--text-hi: #F4EEE4 /* human primary */
--text-mid: #B4AA98 /* human secondary */
--text-lo: #756C5C /* human tertiary / idle */
--text-machine: #9C917D /* mono default */
```
### Type
```
--sans: 'Geist', -apple-system, system-ui, sans-serif
--mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace
```
Scale: display 3244 / heading 20 / body 1416 / label 1011 uppercase 0.080.12em tracking / machine data 1215 mono. Mono always `font-feature-settings: "tnum" 1, "zero" 1` and slight negative tracking. Headings go editorial and large; let type be a memorable part of the design, not a neutral delivery vehicle.
### Motion / radii / shadow
```
--ease: cubic-bezier(0.2, 0, 0, 1)
--fast: 130ms --med: 170ms
--r-sm: 8px --r-md: 12px --r-lg: 18px --r-xl: 24px
--shadow-soft: 0 2px 8px rgba(0,0,0,.35), 0 12px 32px rgba(0,0,0,.28)
```
Fonts are self-hosted (subset + woff2, immutable caching, served off own nginx). No Google/Vercel font CDN — it phones home and fails the threat model.
## Per-app fingerprint (ask, don't assume)
Each app gets exactly two things of its own — one accent hue and one motif — set in a single `[data-app]` block in `ethos.tokens.css`. Everything else is inherited.
**Do not pick these silently.** When a new app joins the system, run a short intake with the operator before writing any override:
- Ask for the **accent**: a name and a hex. Bring **23 of your own suggestions** grounded in what the app *does* — reason from its function and content, not from a palette wheel — and say why each fits. The operator decides; your suggestions are there to react to.
- Ask for the **motif**: the recurring geometric signature tied to the app's function. Again, offer a couple of options with a one-line rationale each. A motif shows up in the app mark, empty states, loading, the favicon, and one hero moment.
- Once chosen, fill the `[data-app]` TEMPLATE in `ethos.tokens.css` (accent + the derived `-hi/-dim/-line/-glow`) and seed the motif (a symbol in the icon set, plus any gradient hooks).
The one worked example that already exists is **muzick — honey amber `#EDA24E` · waveform**; use it as the reference for how an override block and a motif are shaped, not as a set to copy from. Same skeleton, different soul.
## Shared shell anatomy
**Desktop (≥ 640px):** vertical `Rail` (64px, icon nav, active = accent icon + `accent-dim` bg + 3px accent edge) · `TopBar` (56px, wordmark + ⌘K search + right-aligned machine readouts) · scrollable `Main` · optional docked bar (e.g. muzick's player). Command palette (⌘K) is the shared command surface.
**Mobile (≤ 640px) — shell reflow, same law for every app:**
- Rail → full-width bottom tab bar (thumb reach). Active = accent icon + short top edge mark. Keep to **≤ 5 primary tabs**; fold secondary nav into a parent and push settings into the header. More than 5 is past the thumb ceiling.
- TopBar sheds what it can't afford — the wide machine readouts drop from the header and relocate to where there's room (detail views, per-row values). Honesty is relocated, never deleted.
- Large docked bars collapse to a compact mini (thumb + title + primary action), with any waveform/scrubber condensed to a thin accent progress line. The motif is expressed at whatever scale the form allows.
- Heroes restack to single column: content-art centered and fluid (`aspect-ratio: 1`), heading down a step, spec rows wrap.
## Icons
The set is `ethos-icons.svg` — a `<symbol>` sprite. Reference a glyph with `<use href="/ethos-icons.svg#i-NAME"/>`; color and size come from the consumer. One 24px grid, one stroke width (~1.7), one corner radius, `stroke-linecap/linejoin: round`, `fill: none` line style (filled only for transport glyphs — play, pause, prev, next, more). **Extend by adding a `<symbol>`** on the same grid and hand; never diverge, and never import lucide or a generic pack — that breaks the single-hand rule. Two motif seeds ship in the set (`i-wave`, `i-grid`) to start apps from.
## Copy voice
Words are design material. Name things by what the person controls, not how the system is built — but Ethos still shows machine values, so: the **label** is human (sans, plain), the **value** is honest (mono). Active voice, sentence case, one name per action through the whole flow (a button that says Publish yields a toast that says Published). Errors don't apologize and are never vague — they say what happened and how to fix it. Empty states are invitations to act. Register is terse and plain, no filler.
## Traps to refuse
These are the defaults that make a design generic. Do not ship them, even if asked casually:
- **Frosted glass / `backdrop-filter` over a blurred hero photo** — the 2023 AI-premium tell, and a repaint cost. Depth comes from light instead (law 2).
- **Cream (#F4F1EA) + high-contrast serif + terracotta accent** — the AI-cream default; terracotta near #D97757 also reads as an Anthropic tell.
- **Near-black + one acid-green/vermilion accent** — the other AI default.
- **Accent as wash** — accent filling a whole panel. It's a signal, not paint (balance principle).
- **Spinners / vague "loading…"** — hiding real state. Show the number (law 5).
- **Shadows/glass for elevation instead of hairlines + surface steps.**
- **Absolute-positioned `inset: 0` fill divs for backgrounds.** They stay contained only by a positioned overflow-hidden parent, and escape to the whole viewport in stricter renderers. Put the gradient/background directly on the sized element (the 260px art box, the 52px thumb), not on an inner fill layer. This is a real bug that has shipped.
- **Google/Vercel font CDN** — self-host (law-adjacent, threat model).
- **`localStorage`/`sessionStorage` in sandboxed artifact demos** — fails silently; use in-memory state for demos, sqlite for real apps.
## Build process
Plan → critique → build → **screenshot** → critique → fix. Match complexity to the vision; spend boldness in one place (the motif) and keep everything around it quiet. Before calling something done, remove one accessory.
## Verification protocol (do not skip)
**"Can't see it" means unverified. Never sign off on computed values as a substitute for a render.** Reading back a color or a token value confirms the value parsed; it does NOT confirm the layout, containment, stacking, or overflow. A screen can be completely broken while every computed color is correct.
Before declaring any screen done:
1. Render it and **actually look at the pixels.** Screenshot, view, critique.
2. **Geometry check:** no horizontal overflow (`scrollWidth === clientWidth`); key elements sized and contained (an art box is its own dimensions, not the viewport); nothing painting the full screen that shouldn't.
3. Check **both** desktop and mobile (cross the 640px line) before "done."
4. If you genuinely cannot screenshot, **say so plainly and hand visual sign-off to the human.** Do not fill the gap with confidence. State what you verified (geometry) and what you couldn't (appearance).
+87
View File
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
ETHOS — icon set
One hand, one grid. Every icon: viewBox 0 0 24 24, stroke="currentColor",
stroke-width 1.7, round caps/joins, fill="none". Transport glyphs (play,
pause, prev, next, more) are the only filled exceptions.
USE: <svg class="icon" width="22" height="22"><use href="/ethos-icons.svg#i-search"/></svg>
color + width/height come from the consumer; the icon inherits them.
EXTEND: add a new <symbol id="i-NAME"> on the same 24px grid, same stroke,
same corner feel. Match the existing hand — do not import lucide or
any other pack. Keep ids prefixed i- and kebab-cased.
-->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<!-- ── app mark / motif seeds ── -->
<symbol id="i-wave" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round">
<path d="M2 12h1M6 8v8M10 4v16M14 7v10M18 5v14M21 11v2"/>
</symbol>
<symbol id="i-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round">
<rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/>
<rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/>
</symbol>
<!-- ── navigation ── -->
<symbol id="i-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 11l8-6 8 6M6 10v9h12v-9"/>
</symbol>
<symbol id="i-listen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 18V9l14-3v9"/><circle cx="6" cy="18" r="2.4"/><circle cx="18" cy="15" r="2.4"/>
</symbol>
<symbol id="i-library" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round">
<rect x="4" y="4" width="6" height="16" rx="1.5"/><rect x="14" y="4" width="6" height="16" rx="1.5"/>
</symbol>
<symbol id="i-album" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
<circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="2"/>
</symbol>
<symbol id="i-artist" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4"/><path d="M5 20c0-3.5 3.1-6 7-6s7 2.5 7 6"/>
</symbol>
<symbol id="i-queue" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 7h11M4 12h11M4 17h7M18 9v8"/><circle cx="18" cy="18.5" r="1.6"/>
</symbol>
<symbol id="i-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round">
<path d="M4 7a1 1 0 011-1h4.5l2 2H19a1 1 0 011 1v8a1 1 0 01-1 1H5a1 1 0 01-1-1z"/>
</symbol>
<symbol id="i-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round">
<path d="M14 3H7a1 1 0 00-1 1v16a1 1 0 001 1h10a1 1 0 001-1V8z"/><path d="M14 3v5h5"/>
</symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3.2"/><path d="M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/>
</symbol>
<!-- ── transport (filled) ── -->
<symbol id="i-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5h3v14H8zM13 5h3v14h-3z"/></symbol>
<symbol id="i-prev" viewBox="0 0 24 24" fill="currentColor"><path d="M6 6h2v12H6z"/><path d="M20 6v12l-9-6z"/></symbol>
<symbol id="i-next" viewBox="0 0 24 24" fill="currentColor"><path d="M16 6h2v12h-2z"/><path d="M6 6v12l9-6z"/></symbol>
<symbol id="i-shuffle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h4v4M20 4l-6 6M4 20l16-16M16 20h4v-4M14 14l6 6M4 4l4 4"/>
</symbol>
<symbol id="i-repeat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 2l3 3-3 3M20 5H8a4 4 0 00-4 4v1M7 22l-3-3 3-3M4 19h12a4 4 0 004-4v-1"/>
</symbol>
<!-- ── actions / status ── -->
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/>
</symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l4.5 4.5L20 7"/></symbol>
<symbol id="i-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"/></symbol>
<symbol id="i-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></symbol>
<symbol id="i-more" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 9a6 6 0 0112 0c0 5 2 6 2 6H4s2-1 2-6M10 21h4"/>
</symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 4v11M8 11l4 4 4-4M5 20h14"/>
</symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 15V4M8 8l4-4 4 4M5 20h14"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

+122
View File
@@ -0,0 +1,122 @@
/* ═══════════════════════════════════════════════════════════════
ETHOS — tokens
Shared design language for the kvmx.ru apps.
The neutral system (ramp, type, motion, radii, shadow) is shared by
every app and does not change. The only per-app difference is the
accent (one hue) and the motif (one geometric signature).
NEW APP: do not invent the accent silently. Ask the operator for the
accent name + hex and the motif, then add one [data-app] block at the
bottom using the TEMPLATE. Fonts are self-hosted (subset + woff2,
immutable caching) — no font CDN.
═══════════════════════════════════════════════════════════════ */
@font-face {
font-family: 'Geist';
src: url('/fonts/Geist-Variable.woff2') format('woff2');
font-weight: 100 900; font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('/fonts/GeistMono-Variable.woff2') format('woff2');
font-weight: 100 900; font-display: swap;
}
:root {
/* ── neutral ramp — warm, brown-tinted (dark, default) ── */
--bg-0: #14110D; /* deepest room */
--bg-1: #1B1712; /* surface */
--bg-2: #221D17; /* raised */
--bg-3: #2C261D; /* hover / raised */
--bg-4: #372F24; /* pressed / high */
--line: rgba(244, 234, 220, 0.09); /* hairline */
--line-hi: rgba(244, 234, 220, 0.16); /* hairline emphasized */
--text-hi: #F4EEE4; /* human primary (sans) */
--text-mid: #B4AA98; /* human secondary (sans) */
--text-lo: #756C5C; /* human tertiary / idle */
--text-machine: #9C917D; /* machine default (mono) */
/* ── type ── */
--sans: 'Geist', -apple-system, system-ui, sans-serif;
--mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;
/* mono numerics: apply on any element using --mono */
/* font-feature-settings: "tnum" 1, "zero" 1; letter-spacing: -0.01em; */
/* ── motion — mechanical, no bounce ── */
--ease: cubic-bezier(0.2, 0, 0, 1);
--fast: 130ms;
--med: 170ms;
/* ── radii ── */
--r-sm: 8px;
--r-md: 12px;
--r-lg: 18px;
--r-xl: 24px;
/* ── elevation: depth from light, never blur ── */
--shadow-soft: 0 2px 8px rgba(0,0,0,0.35), 0 12px 32px rgba(0,0,0,0.28);
--shadow-lift: 0 4px 14px rgba(0,0,0,0.40), 0 20px 48px rgba(0,0,0,0.34);
/* ── accent slot ──
Neutral fallback so an app with no [data-app] is never unstyled.
Real values come from the per-app block below. Accent is a SIGNAL
(active state, focus ring, primary action, one lit detail) — never a
fill-everything wash. */
--accent: var(--text-mid);
--accent-hi: var(--text-hi);
--accent-dim: rgba(244, 234, 220, 0.10);
--accent-line: rgba(244, 234, 220, 0.24);
--accent-glow: rgba(244, 234, 220, 0.14);
}
/* ── light theme — warm, off-white (kept off cream to dodge the AI-cream tell) ── */
[data-theme="light"] {
--bg-0: #F1ECE3;
--bg-1: #EAE4D8;
--bg-2: #E2DACB;
--bg-3: #D7CDBB;
--bg-4: #C9BDA7;
--line: rgba(28, 22, 14, 0.10);
--line-hi: rgba(28, 22, 14, 0.18);
--text-hi: #1B1712;
--text-mid: #544C3E;
--text-lo: #877E6C;
--text-machine: #6B6252;
--shadow-soft: 0 2px 8px rgba(60,45,25,0.10), 0 12px 32px rgba(60,45,25,0.08);
--shadow-lift: 0 4px 14px rgba(60,45,25,0.12), 0 20px 48px rgba(60,45,25,0.10);
}
/* ═══════════════════════════════════════════════════════════════
PER-APP OVERRIDES — one small block each. Accent + optional motif
hooks only; never touch the neutral ramp.
═══════════════════════════════════════════════════════════════ */
/* muzick — reference app · honey amber · waveform */
[data-app="muzick"] {
--accent: #EDA24E;
--accent-hi: #F5B667;
--accent-dim: rgba(237, 162, 78, 0.14);
--accent-line: rgba(237, 162, 78, 0.32);
--accent-glow: rgba(237, 162, 78, 0.22);
/* motif hook: cool content-art gradient so art carries color, amber stays signal */
--np-art: radial-gradient(120% 120% at 22% 14%, #7FB3BC 0%, #2F6E7A 42%, #1C4552 78%, #14262E 100%);
}
/* ── TEMPLATE — copy for a new app AFTER intake with the operator ──
[data-app="APPNAME"] {
--accent: #RRGGBB; // the operator's chosen hue
--accent-hi: #RRGGBB; // ~ +1014% lightness
--accent-dim: rgba(R, G, B, 0.14); // active-state backgrounds
--accent-line: rgba(R, G, B, 0.32); // accent hairlines
--accent-glow: rgba(R, G, B, 0.22); // restrained ambient pool
// optional motif hooks (gradients / seeds) go here, app-specific
}
*/