Files
design-v3/design-system-v3.css
T

395 lines
20 KiB
CSS
Raw Normal View History

2026-09-19 11:31:50 -04:00
/*
* CubeCraftLabs Design System v3 (proposal): Soft UI / Neumorphism
*
* Built on Design System v2.0 (http://pages.ccl/design-v2.0/, verified
* identical to Extrudex web/src/theme.css at dev 321ce0d on 2026-09-18).
*
* Structure
* 1. Font faces JetBrains Mono, self-hosted woff2, no CDN
* 2. v2.0 base scale kept verbatim, so v2 references still resolve
* 3. v3 theme tokens dark (default, as in v2.0) and light (new)
* 4. Neumorphism surfaces, raised/inset shadow pairs, intensity, radius
* 5. State tokens focus, pressed, disabled, error
* 6. Reference components used by the mockups
*
* Accessibility rules this file enforces:
* - Shadows are decoration. Every control also has a border at 3:1 or more
* against its surface (WCAG 1.4.11), so it stays visible with shadows off.
* - Focus, pressed, disabled and error each change something besides shadow
* direction: the outline, the fill plus a check glyph, a dashed border, or
* a red border plus an icon plus a message.
* - The contrast table is in design-system-v3.md.
*
* Theme selection: dark is the default because v2.0 is dark. Light applies
* when the OS prefers it and nothing is forced, or with data-theme="light" on
* <html>. data-theme="dark" forces dark.
*/
/* ── 1. Font faces ──────────────────────────────────────────────────────── */
/* The same files @fontsource/jetbrains-mono 5.3.0 ships (the app's dependency). */
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-display: swap; font-weight: 400; src: url('./fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-display: swap; font-weight: 500; src: url('./fonts/jetbrains-mono-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-display: swap; font-weight: 700; src: url('./fonts/jetbrains-mono-latin-700-normal.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-display: swap; font-weight: 800; src: url('./fonts/jetbrains-mono-latin-800-normal.woff2') format('woff2'); }
/* ── 2. v2.0 base scale (unchanged) ─────────────────────────────────────── */
:root {
--ccl-charcoal: #2c2c30;
--ccl-charcoal-2: #36363a;
--ccl-charcoal-3: #1b1b1e;
--ccl-gold: #8f7ae0;
--ccl-gold-dim: #7a63cc;
--ccl-text: #f4f4f6;
--ccl-text-muted: #b2b2ba;
--ccl-border: #3d3d44;
--ccl-border-2: #4e4e57;
--ccl-green: #a9d88b;
--ccl-red: #f08b93;
--ccl-white: #ffffff;
--ccl-ink: #2a2431;
--ccl-grey: var(--ccl-charcoal);
--ccl-grey-2: var(--ccl-charcoal-2);
--ccl-grey-3: var(--ccl-charcoal-3);
--ccl-purple: var(--ccl-gold);
--ccl-purple-dim: var(--ccl-gold-dim);
--ccl-purple-wash: rgba(143, 122, 224, 0.12);
/* v3 additions to the base scale: tints derived from the v2.0 hues */
--ccl-gold-light: #a99af0; /* accent lifted to pass 4.5:1 as text on charcoal */
--ccl-violet-deep: #5b43b8; /* accent darkened to pass 4.5:1 on the light surface */
--ccl-charcoal-inset:#232327; /* between charcoal and charcoal-3: dark wells */
--ccl-amber: #e8c170; /* new: warning (v2.0 had none) */
--ccl-blue: #8fc3f0; /* new: informational / provenance */
--ccl-mist: #e4e2ea; /* light page: charcoal's hue, inverted */
--ccl-mist-2: #f1f0f5; /* light high surface */
--ccl-mist-3: #d9d6e1; /* light inset */
/* Typography, spacing and motion: unchanged from v2.0 */
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
--font-body: var(--font-mono);
--font-display: var(--font-mono);
--fw-regular: 400; --fw-medium: 500; --fw-bold: 700; --fw-display: 800;
--fs-caption: 11px; --fs-label: 12px; --fs-body: 14px; --fs-body-lg: 15px;
--fs-h3: 18px; --fs-h2: 22px; --fs-h1: 28px; --fs-display: 40px;
--lh-tight: 1.15; --lh-snug: 1.35; --lh-body: 1.7;
--tracking-wordmark: 0.02em; --tracking-caps: 0.08em; --tracking-caps-wide: 0.14em;
--space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px;
--space-6: 24px; --space-8: 32px; --space-10: 40px; --space-14: 56px; --space-16: 64px;
--ease-out: cubic-bezier(0.2, 0, 0, 1);
--dur-fast: 100ms; --dur-base: 160ms;
--dur-press: 60ms; /* v3: pressing reacts faster than hover */
--scrollbar-width: 6px;
/* ── 4a. Radius scale (v3; replaces v2.0's "0 is the rule") ── */
--radius-none: 0px; /* tables, code, grid lines: square stays available */
--radius-xs: 4px; /* chips, swatches, inline code */
--radius-sm: 8px; /* inputs, small buttons */
--radius-md: 12px; /* buttons, list rows */
--radius-lg: 18px; /* cards, panels */
--radius-xl: 26px; /* dialogs, the app shell */
--radius-pill: 999px; /* toggles, progress tracks, badges */
/* ── 4b. Shadow geometry: distance + blur per intensity level ── */
--nm-d-1: 2px; --nm-b-1: 5px; /* level 1: chips, small controls */
--nm-d-2: 4px; --nm-b-2: 10px; /* level 2: buttons, rows */
--nm-d-3: 8px; --nm-b-3: 18px; /* level 3: cards, panels */
--nm-d-4: 14px; --nm-b-4: 30px; /* level 4: dialogs */
/* Focus ring geometry: identical in both themes */
--focus-width: 2px;
--focus-offset: 3px;
--border-control-width: 1px;
--border-error-width: 2px;
}
/* ── 3. Theme tokens: DARK (default) ───────────────────────────────────── */
:root,
:root[data-theme="dark"] {
color-scheme: dark;
/* Surfaces */
--surface-page: var(--ccl-charcoal); /* #2c2c30 */
--surface-raised: var(--ccl-charcoal); /* v3: same as page; the lift comes from the shadow */
--surface-high: var(--ccl-charcoal-2); /* #36363a dialogs, menus */
--surface-inset: var(--ccl-charcoal-inset); /* #232327 inputs, wells, progress tracks */
--surface-code: var(--ccl-charcoal-3);
/* Text */
--text-primary: var(--ccl-text); /* 12.66:1 on page */
--text-secondary: var(--ccl-text-muted); /* 6.60:1 on page */
--text-accent: var(--ccl-gold-light); /* 5.67:1 on page */
--text-on-accent: var(--ccl-ink); /* on --accent-fill */
--text-on-gold: var(--ccl-ink);
/* Accent */
--accent: var(--ccl-gold); /* brand hue, for large marks only */
--accent-hover: var(--ccl-gold-dim);
--accent-fill: var(--ccl-gold-light); /* primary button fill; ink on it: 6.14:1 */
--accent-fill-press: #9785e6;
--accent-wash: rgba(169, 154, 240, 0.10);
/* Status: the v2.0 hues plus two new ones */
--status-success: var(--ccl-green);
--status-error: var(--ccl-red);
--status-warning: var(--ccl-amber);
--status-info: var(--ccl-blue);
--wash-success: rgba(169, 216, 139, 0.10);
--wash-error: rgba(240, 139, 147, 0.10);
--wash-warning: rgba(232, 193, 112, 0.10);
--wash-info: rgba(143, 195, 240, 0.10);
/* Borders */
--border-hairline: var(--ccl-border); /* decorative card edges */
--border-strong: var(--ccl-border-2);
--border-control: #85858f; /* 1.4.11: 3.81:1 page, 4.29:1 inset, 3.29:1 high */
--focus-ring: var(--ccl-gold-light); /* 5.67:1 on page */
/* ── 4c. Neumorphism shadow pairs, DARK ──
Dark soft UI fails when the "dark" shadow is only a darker charcoal:
nothing on #2c2c30 is visibly darker than #1b1b1e. Four rules make it
work here:
a. The dark half is true black at high alpha, not a darker gray.
b. The light half is white at 4–7% alpha. It reads as sheen, not glow.
c. Raised surfaces add a 1px top rim light (inset, white 6%). The rim
stays visible even where the drop shadow merges into the background.
d. Every control has a 3:1 border anyway, so the shadow never carries
structure on its own. */
--nm-shadow-dark: rgba(0, 0, 0, 0.62);
--nm-shadow-light: rgba(255, 255, 255, 0.055);
--nm-rim: inset 0 1px 0 rgba(255, 255, 255, 0.06);
--nm-inset-dark: rgba(0, 0, 0, 0.55);
--nm-inset-light: rgba(255, 255, 255, 0.045);
--nm-raised-gradient: linear-gradient(145deg, #313136, #29292d);
}
/* ── 3. Theme tokens: LIGHT (new in v3) ────────────────────────────────── */
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) {
color-scheme: light;
--surface-page: var(--ccl-mist);
--surface-raised: var(--ccl-mist);
--surface-high: var(--ccl-mist-2);
--surface-inset: var(--ccl-mist-3);
--surface-code: #d2cfdb;
--text-primary: var(--ccl-ink);
--text-secondary: #55505f;
--text-accent: var(--ccl-violet-deep);
--text-on-accent: var(--ccl-white);
--text-on-gold: var(--ccl-white);
--accent: var(--ccl-gold);
--accent-hover: var(--ccl-gold-dim);
--accent-fill: var(--ccl-violet-deep);
--accent-fill-press: #4d37a3;
--accent-wash: rgba(91, 67, 184, 0.10);
--status-success: #2c6619;
--status-error: #a8202f;
--status-warning: #7c5100;
--status-info: #1d5e94;
--wash-success: rgba(44, 102, 25, 0.10);
--wash-error: rgba(168, 32, 47, 0.10);
--wash-warning: rgba(124, 81, 0, 0.10);
--wash-info: rgba(29, 94, 148, 0.10);
--border-hairline: #cfccd8;
--border-strong: #b9b5c6;
--border-control: #736e86;
--focus-ring: var(--ccl-violet-deep);
--nm-shadow-dark: rgba(88, 80, 120, 0.30);
--nm-shadow-light: rgba(255, 255, 255, 0.95);
--nm-rim: inset 0 1px 0 rgba(255, 255, 255, 0.7);
--nm-inset-dark: rgba(88, 80, 120, 0.28);
--nm-inset-light: rgba(255, 255, 255, 0.9);
--nm-raised-gradient: linear-gradient(145deg, #eceaf1, #dcd9e3);
}
}
:root[data-theme="light"] {
color-scheme: light;
--surface-page: var(--ccl-mist);
--surface-raised: var(--ccl-mist);
--surface-high: var(--ccl-mist-2);
--surface-inset: var(--ccl-mist-3);
--surface-code: #d2cfdb;
--text-primary: var(--ccl-ink);
--text-secondary: #55505f;
--text-accent: var(--ccl-violet-deep);
--text-on-accent: var(--ccl-white);
--text-on-gold: var(--ccl-white);
--accent: var(--ccl-gold);
--accent-hover: var(--ccl-gold-dim);
--accent-fill: var(--ccl-violet-deep);
--accent-fill-press: #4d37a3;
--accent-wash: rgba(91, 67, 184, 0.10);
--status-success: #2c6619;
--status-error: #a8202f;
--status-warning: #7c5100;
--status-info: #1d5e94;
--wash-success: rgba(44, 102, 25, 0.10);
--wash-error: rgba(168, 32, 47, 0.10);
--wash-warning: rgba(124, 81, 0, 0.10);
--wash-info: rgba(29, 94, 148, 0.10);
--border-hairline: #cfccd8;
--border-strong: #b9b5c6;
--border-control: #736e86;
--focus-ring: var(--ccl-violet-deep);
--nm-shadow-dark: rgba(88, 80, 120, 0.30);
--nm-shadow-light: rgba(255, 255, 255, 0.95);
--nm-rim: inset 0 1px 0 rgba(255, 255, 255, 0.7);
--nm-inset-dark: rgba(88, 80, 120, 0.28);
--nm-inset-light: rgba(255, 255, 255, 0.9);
--nm-raised-gradient: linear-gradient(145deg, #eceaf1, #dcd9e3);
}
/* ── 4d. Composed shadows: theme-agnostic, built from the pairs above ── */
:root {
--nm-raised-1: calc(var(--nm-d-1) * -1) calc(var(--nm-d-1) * -1) var(--nm-b-1) var(--nm-shadow-light),
var(--nm-d-1) var(--nm-d-1) var(--nm-b-1) var(--nm-shadow-dark), var(--nm-rim);
--nm-raised-2: calc(var(--nm-d-2) * -1) calc(var(--nm-d-2) * -1) var(--nm-b-2) var(--nm-shadow-light),
var(--nm-d-2) var(--nm-d-2) var(--nm-b-2) var(--nm-shadow-dark), var(--nm-rim);
--nm-raised-3: calc(var(--nm-d-3) * -1) calc(var(--nm-d-3) * -1) var(--nm-b-3) var(--nm-shadow-light),
var(--nm-d-3) var(--nm-d-3) var(--nm-b-3) var(--nm-shadow-dark), var(--nm-rim);
--nm-raised-4: calc(var(--nm-d-4) * -1) calc(var(--nm-d-4) * -1) var(--nm-b-4) var(--nm-shadow-light),
var(--nm-d-4) var(--nm-d-4) var(--nm-b-4) var(--nm-shadow-dark), var(--nm-rim);
--nm-inset-1: inset var(--nm-d-1) var(--nm-d-1) var(--nm-b-1) var(--nm-inset-dark),
inset calc(var(--nm-d-1) * -1) calc(var(--nm-d-1) * -1) var(--nm-b-1) var(--nm-inset-light);
--nm-inset-2: inset var(--nm-d-2) var(--nm-d-2) var(--nm-b-2) var(--nm-inset-dark),
inset calc(var(--nm-d-2) * -1) calc(var(--nm-d-2) * -1) var(--nm-b-2) var(--nm-inset-light);
--nm-flat: none;
/* v2.0 names kept so old rules still resolve */
--shadow-none: none;
--shadow-raised: var(--nm-raised-2);
/* Borders, v2.0 names */
--border-width: 1px;
--border: var(--border-width) solid var(--border-hairline);
--border-strong-rule: var(--border-width) solid var(--border-strong);
--border-accent: 1.5px solid var(--accent);
--scrollbar-thumb: var(--border-strong);
--scrollbar-thumb-hover: var(--accent);
}
/* ── 6. Reference components ────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--surface-page); }
body {
margin: 0;
background: var(--surface-page);
color: var(--text-primary);
font-family: var(--font-body);
font-size: var(--fs-body);
line-height: var(--lh-body);
-webkit-font-smoothing: antialiased;
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
/* Surfaces */
.nm-raised { background: var(--nm-raised-gradient); box-shadow: var(--nm-raised-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-lg); }
.nm-raised-sm { background: var(--surface-raised); box-shadow: var(--nm-raised-2); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); }
.nm-inset { background: var(--surface-inset); box-shadow: var(--nm-inset-2); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); }
.nm-dialog { background: var(--surface-high); box-shadow: var(--nm-raised-4); border: 1px solid var(--border-strong); border-radius: var(--radius-xl); }
/* Buttons
* rest raised-2 + 1px control border
* hover border takes the accent color
* pressed inset shadow + accent wash fill + accent border (+ ✓ on toggles)
* focus 2px solid outline, offset 3px, in the focus color
* disabled no shadow, dashed border, secondary text, not-allowed cursor
*/
.btn {
font: var(--fw-medium) var(--fs-body)/1 var(--font-body);
color: var(--text-primary);
background: var(--surface-raised);
border: var(--border-control-width) solid var(--border-control);
border-radius: var(--radius-md);
padding: 10px 16px;
min-height: 40px;
box-shadow: var(--nm-raised-2);
cursor: pointer;
display: inline-flex; align-items: center; gap: var(--space-2);
transition: box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-fast), background var(--dur-fast);
}
.btn:hover { border-color: var(--text-accent); }
.btn:active, .btn[aria-pressed="true"], .btn.is-pressed {
box-shadow: var(--nm-inset-1);
background: var(--accent-wash);
border-color: var(--text-accent);
transition-duration: var(--dur-press);
}
.btn[aria-pressed="true"]::before, .btn.is-pressed::before { content: "✓"; font-weight: var(--fw-bold); color: var(--text-accent); }
.btn:focus-visible, .btn.is-focused, .input:focus-visible, .input.is-focused, .select:focus-visible, .select.is-focused {
outline: var(--focus-width) solid var(--focus-ring);
outline-offset: var(--focus-offset);
}
.btn:disabled, .btn.is-disabled {
box-shadow: none;
border-style: dashed;
color: var(--text-secondary);
background: transparent;
cursor: not-allowed;
}
.btn-primary { background: var(--accent-fill); color: var(--text-on-accent); border-color: var(--accent-fill); font-weight: var(--fw-bold); }
.btn-primary:hover { border-color: var(--text-primary); }
.btn-primary:active, .btn-primary.is-pressed { background: var(--accent-fill-press); color: var(--text-on-accent); border-color: var(--text-primary); }
.btn-primary.is-pressed::before { color: var(--text-on-accent); }
.btn-danger { color: var(--status-error); border-color: var(--status-error); }
.btn-sm { min-height: 32px; padding: 6px 12px; font-size: var(--fs-label); border-radius: var(--radius-sm); box-shadow: var(--nm-raised-1); }
.btn-icon { width: 40px; padding: 0; justify-content: center; }
/* Inputs: always inset, always bordered */
.input, .select {
font: var(--fw-regular) var(--fs-body)/1.4 var(--font-body);
color: var(--text-primary);
background: var(--surface-inset);
border: var(--border-control-width) solid var(--border-control);
border-radius: var(--radius-sm);
box-shadow: var(--nm-inset-1);
padding: 9px 12px;
min-height: 40px;
width: 100%;
}
.input::placeholder { color: var(--text-secondary); }
.input:disabled, .input.is-disabled, .select:disabled {
box-shadow: none; border-style: dashed; color: var(--text-secondary); background: transparent;
}
/* Error: 2px red border + icon in the label + message. Never color alone. */
.field.has-error .input, .field.has-error .select, .input.is-error {
border: var(--border-error-width) solid var(--status-error);
padding: 8px 11px;
}
.field-error {
display: flex; gap: var(--space-2); align-items: flex-start;
color: var(--status-error); font-size: var(--fs-label); line-height: var(--lh-snug); margin-top: var(--space-1);
}
.field-error::before { content: "⚠"; font-weight: var(--fw-bold); }
/* Chips / badges: text + wash + border + glyph */
.chip {
display: inline-flex; align-items: center; gap: 6px;
font-size: var(--fs-caption); font-weight: var(--fw-bold);
letter-spacing: var(--tracking-caps); text-transform: uppercase;
padding: 2px 9px; border-radius: var(--radius-pill);
border: 1px solid currentColor; line-height: 1.6;
white-space: nowrap;
}
.chip-ok { color: var(--status-success); background: var(--wash-success); }
.chip-err { color: var(--status-error); background: var(--wash-error); }
.chip-warn { color: var(--status-warning); background: var(--wash-warning); }
.chip-info { color: var(--status-info); background: var(--wash-info); }
.chip-accent { color: var(--text-accent); background: var(--accent-wash); }
.chip-neutral { color: var(--text-secondary); background: transparent; }
.chip-dashed { border-style: dashed; }
/* Progress track (inset) + fill (raised) */
.track { height: 10px; border-radius: var(--radius-pill); background: var(--surface-inset); box-shadow: var(--nm-inset-1); border: 1px solid var(--border-hairline); overflow: hidden; }
.track > span { display: block; height: 100%; border-radius: var(--radius-pill); background: var(--text-accent); }
.track.is-low > span { background: var(--status-error); }
/* Swatch: 1–3 colors, a hairline so white/black filament shows on either theme */
.swatch { display: inline-flex; width: 26px; height: 26px; border-radius: 50%; overflow: hidden; border: 1px solid var(--border-control); box-shadow: var(--nm-raised-1); flex: none; }
.swatch > i { flex: 1; }
.swatch-sm { width: 18px; height: 18px; }
/* Text helpers */
.muted { color: var(--text-secondary); }
.accent { color: var(--text-accent); }
.caps { text-transform: uppercase; letter-spacing: var(--tracking-caps-wide); font-size: var(--fs-caption); font-weight: var(--fw-bold); }
.num { font-variant-numeric: tabular-nums; }