/* * 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 * . 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; }