CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / integration (push) Successful in 34s
CI & Build / Python tests (push) Successful in 1m12s
CI & Build / Build & push image (push) Successful in 1m2s
The badge fix (#3132) exposed the same defect everywhere: 48 rules painting a token as TEXT on an inline color-mix tint of that same token. Worst raw measurements, across every tint strength in use, both modes, over page/raised/hover: accent 1.53:1 · success 1.67:1 · text-tertiary 2.15:1 warning 2.32:1 · error 2.36:1 against AA's 4.5 THE DEFECT IS IN THE HOUSE, NOT IN SCRIBE. The semantic hues are shared family-wide, and the accent case was measured against every app's real accent, not assumed from Scribe's: Minstrel 1.81, Forge 1.87, Steward 1.65, Roundtable 3.01 — all failing. So the six -fg tokens are recorded on FabledSword (design system 1), where their parents live, rather than copied into each app. 45% toward --fs-text-primary clears AA for ALL FIVE accents (4.56-5.00), so this is one house token rather than five overrides, and it keeps deriving from --fs-accent — an app that overrides its accent still gets a legible tinted-text colour in its own colour, the same mechanism as --fs-accent-soft. The tokens are additive: a sibling app is unaffected until it regenerates its own stylesheet. One token is honestly redundant. --fs-text-secondary already passes at 4.82:1, and --fs-text-secondary-fg barely moves it. It exists so the rule has NO exceptions, because the alternative is a permanent allow-list entry for the one case that happens to pass — and a guard with an invisible exception is a guard that erodes. 46 substitutions across 18 files, each rewriting only the `color:` inside a block that tints its own background. THE CHECK NOW GATES BOTH SPELLINGS. It previously reported the inline form, because a gate nobody can satisfy on the day it lands gets switched off. Both are clean, so both fail the build now. And the check had a false-positive bug worth naming: its `color\s*:` regex matched the tail of `border-color`, `border-left-color` and `outline-color`, so it flagged seven rules that were already correct. A border is a non-text graphic with a 3:1 floor, not text at 4.5. A check that cries wolf on correct code is one that gets muted, so that mattered more than the noise. Verified by construction, not by passing: reintroduced each defect form (exit 1 each), and confirmed a legitimate border-only rule still exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
334 lines
13 KiB
CSS
334 lines
13 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.
|
|
|
|
The -fg tokens are a badge's TEXT colour, added because the ladder used its
|
|
raw hue as text on a 12% tint of the same hue — measured 1.60-2.97:1 on the
|
|
dark palette against the kit's AA floor of 4.5. Each is the hue mixed toward
|
|
--fs-text-primary until it clears 4.5:1 worst-case over surface-raised and
|
|
surface-hover in BOTH modes. Mixing toward that token is what makes one
|
|
declaration cover both: it inverts, so the text follows the mode.
|
|
|
|
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-accent-fg: color-mix(in srgb, var(--fs-accent) 45%, var(--fs-text-primary)); /* Accent TEXT on an 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-low-fg: color-mix(in srgb, var(--fs-priority-low) 45%, var(--fs-text-primary)); /* Badge TEXT for low priority — the readable partner of the -bg tint */
|
|
--fs-priority-medium: var(--fs-warning);
|
|
--fs-priority-medium-bg: color-mix(in srgb, var(--fs-priority-medium) 12%, transparent);
|
|
--fs-priority-medium-fg: color-mix(in srgb, var(--fs-priority-medium) 55%, var(--fs-text-primary)); /* Badge TEXT for medium priority */
|
|
--fs-priority-high: var(--fs-error);
|
|
--fs-priority-high-bg: color-mix(in srgb, var(--fs-priority-high) 12%, transparent);
|
|
--fs-priority-high-fg: color-mix(in srgb, var(--fs-priority-high) 55%, var(--fs-text-primary)); /* Badge TEXT for high priority */
|
|
|
|
/* 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-success-fg: color-mix(in srgb, var(--fs-success) 45%, var(--fs-text-primary)); /* Success TEXT on a success tint */
|
|
--fs-warning: #8B6F1E;
|
|
--fs-warning-fg: color-mix(in srgb, var(--fs-warning) 50%, var(--fs-text-primary)); /* Warning TEXT on a warning tint */
|
|
--fs-error: #C04A1F;
|
|
--fs-error-fg: color-mix(in srgb, var(--fs-error) 50%, var(--fs-text-primary)); /* Error TEXT on an error tint */
|
|
--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-todo-fg: color-mix(in srgb, var(--fs-status-todo) 40%, var(--fs-text-primary)); /* Badge TEXT for a not-started task */
|
|
--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-in-progress-fg: color-mix(in srgb, var(--fs-status-in-progress) 45%, var(--fs-text-primary)); /* Badge TEXT for a task underway */
|
|
--fs-status-done: var(--fs-success);
|
|
--fs-status-done-bg: color-mix(in srgb, var(--fs-status-done) 12%, transparent);
|
|
--fs-status-done-fg: color-mix(in srgb, var(--fs-status-done) 50%, var(--fs-text-primary)); /* Badge TEXT for a completed task */
|
|
--fs-overdue: var(--fs-error);
|
|
--fs-status-cancelled: var(--fs-text-tertiary); /* set aside, not failed */
|
|
--fs-status-cancelled-fg: color-mix(in srgb, var(--fs-status-cancelled) 60%, var(--fs-text-primary)); /* Badge TEXT for a cancelled task */
|
|
|
|
/* 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-secondary-fg: color-mix(in srgb, var(--fs-text-secondary) 90%, var(--fs-text-primary)); /* Secondary TEXT on a secondary tint (barely moves; no exceptions) */
|
|
--fs-text-tertiary: #9C9A92;
|
|
--fs-text-tertiary-fg: color-mix(in srgb, var(--fs-text-tertiary) 55%, var(--fs-text-primary)); /* Tertiary TEXT on a tertiary tint */
|
|
--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"] {
|
|
min-height: 44px;
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|