Files
FabledScribe/frontend/src/assets/theme.css
T
bvandeusen 4ba544e2af
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 37s
refactor(theme): retire the --color-* shim — the sweep it promised, run
#2533. theme.css claimed "removing this block is a rename sweep across the
components, tracked separately" — written in 67a529a, never filed, which made
the comment itself an instance of the survey's presence-without-reference
pattern. This is that sweep.

73 alias declarations deleted; 69 files rewritten; every --color-*-style name
now references its --fs-* token directly. Mechanical by construction: the map
IS the alias block, applied longest-name-first with a boundary guard so
--color-text never matched inside --color-text-muted. Zero survivors outside
theme.css, verified by grep rather than assumed.

One deliberate survivor: --color-shadow stays DECLARED, because it was never
an alias — it is a literal value the design system has no token for. Marked
in place as a recorded gap: promote it to an --fs-* token when a second app
needs it, don't copy the line.

Nothing is lost mode-wise: the aliases' resolve-at-use-time trick (which
absorbed 48 dark-mode overrides) lives one layer down in the --fs-* tokens'
own derivations, which is why the sweep is a pure rename. Both CSS checkers
green.

Why now rather than never: check_snippets_against_design_system reports every
--color-* reference as "unknown — renders as NOTHING", and nine recipe
snippets recorded from components.css carried the deprecated names, making
them prior art pointing the wrong way. With the sweep in, the checker's
report over re-recorded snippets should be EMPTY — the acceptance test that
proves the checker was right all along (#2517's correction).

Refs #2533
2026-08-08 22:42:37 -04:00

321 lines
11 KiB
CSS

@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Inter:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:ital,wght@0,400;1,400&display=swap');
/* ==========================================================================
GENERATED FROM THE DESIGN SYSTEM — Scribe (design system 2), which inherits
the FabledSword house style (design system 1).
Do not hand-edit the --fs-* block below. Edit the design system in the app
and regenerate: /design-systems -> Master stylesheet -> Copy.
DARK IS THE BASE LAYER. The kit is dark-mode-first, so :root carries the dark
palette and [data-theme="light"] overrides it. That is the inverse of how this
file used to read, and it is deliberate: the light palette was never specified
by any rule, so it is recorded as a departure rather than as the default.
Only 12 tokens differ between modes. Everything else — spacing, type, motion,
radius, and every derived colour — is stated once, because a value built with
var() resolves where it is USED, not where it is written.
========================================================================== */
:root {
/* accent */
--fs-accent: #5B4A8A; /* Scribe's signature colour */
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 15%, transparent); /* Pill, tag and badge backgrounds */
--fs-accent-faint: color-mix(in srgb, var(--fs-accent) 8%, transparent); /* The faintest accent wash */
--fs-accent-deep: color-mix(in srgb, var(--fs-accent) 70%, black); /* The accent, darkened */
--fs-accent-wash: color-mix(in srgb, var(--fs-accent) 22%, transparent); /* Heaviest accent tint */
--fs-gradient-cta: linear-gradient(135deg, var(--fs-accent), var(--fs-accent-deep));
--fs-glow-cta: 0 2px 10px color-mix(in srgb, var(--fs-accent) 35%, transparent);
--fs-glow-cta-hover: 0 4px 24px color-mix(in srgb, var(--fs-accent) 65%, transparent);
/* action */
--fs-action-primary: #4A5D3F; /* Save, Submit, Confirm */
--fs-action-secondary: #8B7355; /* Non-destructive alternates */
--fs-action-tertiary: var(--fs-border-color); /* Ghost / outline actions */
--fs-action-destructive: var(--fs-destructive);
--fs-action-primary-hover: color-mix(in srgb, var(--fs-action-primary) 88%, white);
--fs-action-secondary-hover: color-mix(in srgb, var(--fs-action-secondary) 88%, white);
--fs-action-destructive-hover: color-mix(in srgb, var(--fs-action-destructive) 88%, white);
/* border */
--fs-border-color: #3F4651; /* Borders, dividers and ghost outlines */
--fs-border: 0.5px solid var(--fs-border-color);
--fs-border-hover: 0.5px solid color-mix(in srgb, var(--fs-text-secondary) 30%, transparent);
--fs-border-active: 2px solid var(--fs-accent); /* Selected card or active tab only */
/* editor */
--fs-wikilink: var(--fs-accent);
/* elevation */
--fs-shadow-1: 0 1px 0 rgba(0,0,0,0.4); /* Hairline lift */
--fs-shadow-2: 0 4px 12px rgba(0,0,0,0.35); /* Dropdowns, popovers */
--fs-shadow-3: 0 16px 40px rgba(0,0,0,0.5); /* Modals */
/* focus */
--fs-focus-ring: 0 0 0 2px var(--fs-accent);
/* font */
--fs-font-display: 'Fraunces', Georgia, serif;
--fs-font-body: 'Inter', system-ui, sans-serif;
--fs-font-mono: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
/* icon */
--fs-icon-stroke: 1.5px; /* at 24px */
--fs-icon-stroke-sm: 1px; /* at 16px */
/* layout */
--fs-layout-page-max: 1200px;
--fs-layout-page-pad: 1rem;
--fs-layout-sidebar: 260px;
--fs-layout-header: 52px;
/* motion */
--fs-ease: cubic-bezier(0.2, 0.6, 0.2, 1); /* the one curve */
--fs-dur-fast: 120ms;
--fs-dur-base: 180ms;
--fs-dur-slow: 280ms;
/* priority */
--fs-priority-low: var(--fs-info);
--fs-priority-low-bg: color-mix(in srgb, var(--fs-priority-low) 12%, transparent);
--fs-priority-medium: var(--fs-warning);
--fs-priority-medium-bg: color-mix(in srgb, var(--fs-priority-medium) 12%, transparent);
--fs-priority-high: var(--fs-error);
--fs-priority-high-bg: color-mix(in srgb, var(--fs-priority-high) 12%, transparent);
/* radius */
--fs-radius-sm: 4px; /* pills, tags, code spans */
--fs-radius-md: 8px; /* buttons, inputs, small cards */
--fs-radius-lg: 12px; /* cards, panels, modals */
--fs-radius-xl: 16px; /* hero containers */
--fs-radius-pill: 9999px;
/* semantic */
--fs-success: var(--fs-action-primary);
--fs-warning: #8B6F1E;
--fs-error: #C04A1F;
--fs-info: #3D5A6E;
--fs-destructive: #6B2118; /* irreversible — deliberately not the error colour */
/* space */
--fs-space-1: 4px;
--fs-space-2: 8px;
--fs-space-3: 12px;
--fs-space-4: 16px;
--fs-space-5: 20px;
--fs-space-6: 24px;
--fs-space-7: 32px;
--fs-space-8: 48px;
--fs-space-9: 64px;
--fs-space-10: 96px;
/* state */
--fs-disabled-opacity: 0.5;
--fs-overlay: rgba(0, 0, 0, 0.65);
/* status */
--fs-status-todo: var(--fs-border-color);
--fs-status-todo-bg: color-mix(in srgb, var(--fs-status-todo) 12%, transparent);
--fs-status-in-progress: var(--fs-accent);
--fs-status-in-progress-bg: color-mix(in srgb, var(--fs-status-in-progress) 12%, transparent);
--fs-status-done: var(--fs-success);
--fs-status-done-bg: color-mix(in srgb, var(--fs-status-done) 12%, transparent);
--fs-overdue: var(--fs-error);
--fs-status-cancelled: var(--fs-text-tertiary); /* set aside, not failed */
/* surface */
--fs-surface-page: #14171A; /* page bg, deepest surface */
--fs-surface-raised: #1E2228; /* cards, raised elements */
--fs-surface-hover: #2C313A; /* hovered surfaces */
--fs-surface-code: var(--fs-surface-page);
--fs-surface-code-inline: var(--fs-surface-raised);
--fs-table-stripe: color-mix(in srgb, var(--fs-text-primary) 3%, transparent);
/* text */
--fs-text-primary: #E8E4D8; /* body, headings, labels — inverts by mode */
--fs-text-secondary: #C2BFB4;
--fs-text-tertiary: #9C9A92;
--fs-text-on-action: #E8E4D8; /* text on a filled colour — NOT mode-dependent */
/* type */
--fs-size-display: 40px;
--fs-size-h1: 32px;
--fs-size-h2: 24px;
--fs-size-h3: 18px;
--fs-size-body: 15px;
--fs-size-body-sm: 13px;
--fs-size-label: 12px;
--fs-size-code: 13px;
--fs-size-tiny: 11px; /* the only ALL CAPS, the only non-default tracking */
--fs-weight-regular: 400;
--fs-weight-medium: 500; /* the heaviest the system goes */
--fs-leading-heading: 1.3;
--fs-leading-body: 1.5;
--fs-leading-longform: 1.7;
--fs-tracking-tiny: 0.08em;
}
[data-theme="light"] {
/* accent */
--fs-accent-wash: color-mix(in srgb, var(--fs-accent) 20%, transparent);
--fs-glow-cta-hover: 0 4px 20px color-mix(in srgb, var(--fs-accent) 55%, transparent);
/* border */
--fs-border-color: #D9D6CE;
/* state */
--fs-overlay: rgba(0, 0, 0, 0.45);
/* surface */
--fs-surface-page: #F5F1E8;
--fs-surface-raised: #FBF8F0;
--fs-surface-hover: #EFEAE0;
--fs-surface-code: #EBEDF0;
--fs-surface-code-inline: #EBEDF0;
/* text */
--fs-text-primary: #14171A;
--fs-text-secondary: #5A5852;
--fs-text-tertiary: #9A9890;
}
/* SUPERSEDES — write the token, not the literal.
* #fff -> --fs-text-on-action
* #ffffff -> --fs-text-on-action
* white -> --fs-text-on-action
* bold -> --fs-weight-medium
* bolder -> --fs-weight-medium
*/
/* ==========================================================================
The compatibility-alias block that lived here is GONE (#2533). It let ~55
components keep their historical --color-* names while theme.css was
repointed at the design system; the rename sweep it promised ran on
2026-08-08 and every component now references --fs-* directly. Do not
reintroduce app-local alias names — the design system's tokens are the
vocabulary, and check_snippets_against_design_system can only see through
names the system actually declares.
========================================================================== */
:root {
/* A VALUE, not an alias — the one survivor of the alias block. The design
system has no shadow-colour token yet, so this is a recorded gap: when a
second app needs it, promote it to an --fs-* token in the system and
regenerate, rather than copying this line. */
--color-shadow: rgba(0, 0, 0, 0.4);
}
/* ==========================================================================
Base element styles
========================================================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--fs-surface-page);
color: var(--fs-text-primary);
font-family: var(--fs-font-body);
font-feature-settings: "cv11";
line-height: var(--fs-leading-body);
transition: background-color var(--fs-dur-base) var(--fs-ease),
color var(--fs-dur-base) var(--fs-ease);
}
h1, h2 {
font-family: var(--fs-font-display);
font-optical-sizing: auto;
font-weight: var(--fs-weight-medium);
line-height: var(--fs-leading-heading);
}
h3 {
font-family: var(--fs-font-body);
font-weight: var(--fs-weight-medium);
line-height: var(--fs-leading-heading);
}
code, pre, kbd, samp {
font-family: var(--fs-font-mono);
font-feature-settings: "liga", "calt";
}
::selection {
background: var(--fs-accent-wash);
color: var(--fs-text-primary);
}
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
button:focus-visible,
a:focus-visible {
outline: none;
box-shadow: var(--fs-focus-ring);
border-radius: var(--fs-radius-sm);
}
button:not(:disabled):active,
.btn:not(:disabled):active,
[role="button"]:not(:disabled):active {
transform: scale(0.97);
transition: transform 0.08s var(--fs-ease);
}
/* Responsive breakpoints: 480px (phone), 768px (tablet), 1024px (desktop) */
@media (max-width: 768px) {
.hide-mobile {
display: none !important;
}
button,
[role="button"],
.btn-new-conv,
.btn-send {
min-height: 44px;
}
}
@media (min-width: 769px) {
.hide-desktop {
display: none !important;
}
}
/* Neutral hairline scrollbars — chrome is structural, not branded */
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--fs-border-color);
border-radius: var(--fs-radius-pill);
}
::-webkit-scrollbar-thumb:hover {
background: var(--fs-text-tertiary);
}
/* Floating inline assist button (teleported to body, cannot be scoped) */
.inline-assist-btn {
position: fixed;
z-index: 150;
transform: translateX(-50%);
background: var(--fs-accent);
color: var(--fs-text-primary);
border: none;
border-radius: var(--fs-radius-pill);
padding: 0.3rem 0.8rem;
font-size: var(--fs-size-body-sm);
cursor: pointer;
box-shadow: var(--fs-shadow-2);
white-space: nowrap;
}
.inline-assist-btn:hover {
filter: brightness(1.1);
}