chati-dev 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -13
- package/bin/chati.js +32 -4
- package/framework/agents/build/dev.md +9 -5
- package/framework/agents/discover/brief.md +2 -0
- package/framework/agents/discover/brownfield-wu.md +2 -0
- package/framework/agents/discover/greenfield-wu.md +18 -1
- package/framework/agents/plan/detail.md +2 -0
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +33 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +59 -5
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +20 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/rules/human-writing-style.md +47 -0
- package/framework/scaffold/motion-premium/README.md +12 -2
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
- package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
- package/framework/scaffold/motion-premium-3d/README.md +10 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +101 -3
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +13 -3
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +186 -15
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +64 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* app/globals.css -- saas-dashboard preset v1.0.0
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS
|
|
5
|
+
* --------------------
|
|
6
|
+
* Every CSS custom property that any component in this preset may reference
|
|
7
|
+
* is declared exactly once here. Components never use raw hex values, raw
|
|
8
|
+
* pixel numbers, or bare ease keywords. The rule is: if you cannot find a
|
|
9
|
+
* CSS variable for the value you want, add it here first, THEN use it.
|
|
10
|
+
*
|
|
11
|
+
* ARCHITECTURE: THREE LAYERS
|
|
12
|
+
*
|
|
13
|
+
* Layer 1 -- Raw scale (neutral-0 through neutral-1000, accent raw values):
|
|
14
|
+
* Calibrated hex values. Nothing references these directly except
|
|
15
|
+
* Layer 2 semantic tokens. Changing a raw value here propagates
|
|
16
|
+
* everywhere that semantic token is consumed.
|
|
17
|
+
*
|
|
18
|
+
* Layer 2 -- Semantic tokens (bg-base, text-primary, border-default, etc.):
|
|
19
|
+
* Named by intent, not by value. Components consume only these.
|
|
20
|
+
* A theme switch (dark <-> light) overrides only Layer 2, in the
|
|
21
|
+
* [data-theme='light'] block below, without touching any component file.
|
|
22
|
+
*
|
|
23
|
+
* Layer 3 -- Component-level tokens (btn-height, input-pad, etc.):
|
|
24
|
+
* Derived from Layer 2. Centralise component defaults so a single
|
|
25
|
+
* change in :root propagates to all instances without touching TSX.
|
|
26
|
+
*
|
|
27
|
+
* MOTION SYSTEM
|
|
28
|
+
* -------------
|
|
29
|
+
* Every transition in the codebase must:
|
|
30
|
+
* 1. Name EXACTLY the property being transitioned (never transition:all).
|
|
31
|
+
* 2. Use a duration token from --duration-*.
|
|
32
|
+
* 3. Use an easing token from --ease-*.
|
|
33
|
+
* 4. Match the spatial metaphor: ease-out entering, ease-in leaving,
|
|
34
|
+
* ease-in-out toggling, ease-spring for delightful micro-interactions.
|
|
35
|
+
*
|
|
36
|
+
* The @media (prefers-reduced-motion: reduce) block at the bottom of this
|
|
37
|
+
* file collapses all durations to 0ms or 80ms and removes transform
|
|
38
|
+
* animations. This is the ONLY place reduced-motion is handled; components
|
|
39
|
+
* read it via the useReducedMotion() hook from framer-motion.
|
|
40
|
+
*
|
|
41
|
+
* TAILWIND v4 INTEGRATION
|
|
42
|
+
* -----------------------
|
|
43
|
+
* The @theme block registers tokens as Tailwind utility classes. This means
|
|
44
|
+
* bg-bg-base, text-text-primary, border-border-default etc. work without
|
|
45
|
+
* arbitrary value syntax. The xl breakpoint is overridden to 75rem (1200px)
|
|
46
|
+
* to eliminate the 1200-1279px dead zone on MacBook Air viewports where
|
|
47
|
+
* lg: applied but xl: did not, producing layout gaps on laptops.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
@import "tailwindcss";
|
|
51
|
+
|
|
52
|
+
/* Exclude non-source directories from Tailwind v4 scanning. Non-existent paths
|
|
53
|
+
are a no-op in Tailwind v4, so this is safe in both a full chati.dev project
|
|
54
|
+
(where these dirs exist) and a plain Next.js app (where they do not). */
|
|
55
|
+
@source not "../artifacts";
|
|
56
|
+
@source not "../.chati";
|
|
57
|
+
@source not "../.chati.dev";
|
|
58
|
+
@source not "../docs";
|
|
59
|
+
|
|
60
|
+
/* ================================================================
|
|
61
|
+
FONT IMPORTS
|
|
62
|
+
Inter Variable and JetBrains Mono Variable from Google Fonts.
|
|
63
|
+
next/font handles this in layout.tsx; these @import declarations
|
|
64
|
+
are the CSS fallback for storybook / non-Next contexts.
|
|
65
|
+
================================================================ */
|
|
66
|
+
|
|
67
|
+
/* In Next.js the fonts are loaded via next/font in layout.tsx.
|
|
68
|
+
This comment is intentionally left as a reminder; do not add
|
|
69
|
+
@import url() here as it conflicts with next/font's optimization. */
|
|
70
|
+
|
|
71
|
+
/* ================================================================
|
|
72
|
+
ROOT: DESIGN TOKEN DECLARATIONS
|
|
73
|
+
All custom properties for both dark (default) and light themes.
|
|
74
|
+
================================================================ */
|
|
75
|
+
|
|
76
|
+
:root {
|
|
77
|
+
color-scheme: dark;
|
|
78
|
+
|
|
79
|
+
/* ---------------------------------------------------------------
|
|
80
|
+
LAYER 1: NEUTRAL SCALE
|
|
81
|
+
Philosophy: A single hue (220deg, blue-shifted) at 12% saturation
|
|
82
|
+
produces a neutral that reads as "cool grey" without drifting
|
|
83
|
+
toward purple or green. The 14 steps provide even perceptual
|
|
84
|
+
luminance increments -- verified against APCA contrast model.
|
|
85
|
+
--------------------------------------------------------------- */
|
|
86
|
+
--color-neutral-0: #ffffff; /* pure white */
|
|
87
|
+
--color-neutral-50: #f7f8fa; /* off-white surface */
|
|
88
|
+
--color-neutral-100: #eef0f4; /* subtle divider bg */
|
|
89
|
+
--color-neutral-200: #dde1e9; /* border default (light mode) */
|
|
90
|
+
--color-neutral-300: #c4cad6; /* border emphasis (light mode) */
|
|
91
|
+
--color-neutral-400: #9aa3b4; /* placeholder text */
|
|
92
|
+
--color-neutral-500: #6b7689; /* muted text */
|
|
93
|
+
--color-neutral-600: #4e5769; /* secondary text */
|
|
94
|
+
--color-neutral-700: #343d4f; /* primary text (light mode), strong border (dark) */
|
|
95
|
+
--color-neutral-800: #1f2635; /* card surface (dark mode) */
|
|
96
|
+
--color-neutral-850: #171d2b; /* sidebar/modal background */
|
|
97
|
+
--color-neutral-900: #0f1420; /* page background (dark mode) */
|
|
98
|
+
--color-neutral-950: #080c14; /* deepest dark surface */
|
|
99
|
+
--color-neutral-1000: #000000; /* pure black */
|
|
100
|
+
|
|
101
|
+
/* ---------------------------------------------------------------
|
|
102
|
+
LAYER 1: ACCENT SCALE
|
|
103
|
+
Violet (258deg) is used in exactly two semantic roles:
|
|
104
|
+
Role 1 -- interactive affordance: buttons, links, active states.
|
|
105
|
+
Role 2 -- brand emphasis: tinted backgrounds, selected rows.
|
|
106
|
+
Using it in more than two roles kills hierarchy and makes the
|
|
107
|
+
UI feel "purple-washed". If you need a third role, create a
|
|
108
|
+
new semantic token with a different hue.
|
|
109
|
+
--------------------------------------------------------------- */
|
|
110
|
+
--color-accent: {{ACCENT_HEX}}; /* role 1: interactive affordance */
|
|
111
|
+
--color-accent-hover: {{ACCENT_HOVER_HEX}}; /* role 1 hover state (+15% lightness) */
|
|
112
|
+
--color-accent-active: #6644e8; /* role 1 pressed state (-10% lightness) */
|
|
113
|
+
--color-accent-subtle: #1e1640; /* role 2: tinted background (dark) */
|
|
114
|
+
--color-accent-muted: #4a3490; /* role 2: tinted border (dark) */
|
|
115
|
+
|
|
116
|
+
/* ---------------------------------------------------------------
|
|
117
|
+
LAYER 2: SEMANTIC SURFACE TOKENS
|
|
118
|
+
Components reference these, never raw neutrals.
|
|
119
|
+
The hierarchy communicates depth: base -> raised -> overlay.
|
|
120
|
+
Sunken is lower than base (input wells, code blocks).
|
|
121
|
+
--------------------------------------------------------------- */
|
|
122
|
+
--color-bg-base: var(--color-neutral-900); /* page background */
|
|
123
|
+
--color-bg-raised: var(--color-neutral-800); /* card surfaces */
|
|
124
|
+
--color-bg-raised-hover: var(--color-neutral-700); /* raised-surface hover (e.g. secondary button) */
|
|
125
|
+
--color-bg-raised-active: var(--color-neutral-800); /* raised-surface pressed */
|
|
126
|
+
--color-bg-overlay: var(--color-neutral-850); /* sidebar, modal backdrop */
|
|
127
|
+
--color-bg-sunken: var(--color-neutral-950); /* input wells, code bg */
|
|
128
|
+
--color-bg-highlight: var(--color-accent-subtle); /* selected rows, active nav */
|
|
129
|
+
|
|
130
|
+
/* ---------------------------------------------------------------
|
|
131
|
+
LAYER 2: SEMANTIC TEXT TOKENS
|
|
132
|
+
Contrast ratios verified at their intended sizes (WCAG 2.1 AA):
|
|
133
|
+
text-primary on bg-base: 19.4:1 (passes AAA)
|
|
134
|
+
text-secondary on bg-base: 5.8:1 (passes AA at body size)
|
|
135
|
+
text-muted on bg-base: 3.9:1 (passes AA at 18px+)
|
|
136
|
+
--------------------------------------------------------------- */
|
|
137
|
+
--color-text-primary: var(--color-neutral-50); /* headings, labels, strong copy */
|
|
138
|
+
--color-text-secondary: var(--color-neutral-400); /* supporting copy, metadata */
|
|
139
|
+
--color-text-muted: var(--color-neutral-500); /* captions, placeholders, disabled */
|
|
140
|
+
--color-text-inverse: var(--color-neutral-950); /* text on light/accent backgrounds */
|
|
141
|
+
--color-text-accent: var(--color-accent-hover); /* links, active sidebar items */
|
|
142
|
+
|
|
143
|
+
/* ---------------------------------------------------------------
|
|
144
|
+
LAYER 2: SEMANTIC BORDER TOKENS
|
|
145
|
+
--------------------------------------------------------------- */
|
|
146
|
+
--color-border-default: var(--color-neutral-700); /* cards, dividers, table rows */
|
|
147
|
+
--color-border-emphasis: var(--color-neutral-600); /* focused inputs, hover borders */
|
|
148
|
+
--color-border-accent: var(--color-accent-muted); /* accent focus ring, active cards */
|
|
149
|
+
|
|
150
|
+
/* ---------------------------------------------------------------
|
|
151
|
+
LAYER 2: STATUS TOKENS
|
|
152
|
+
These are the only raw hex values in Layer 2 because status
|
|
153
|
+
colors are hue-distinct by nature and do not belong to the
|
|
154
|
+
neutral or accent ramps.
|
|
155
|
+
--------------------------------------------------------------- */
|
|
156
|
+
--color-success: #22c55e; /* text, icon on success-subtle */
|
|
157
|
+
--color-success-subtle: #052e16; /* background tint */
|
|
158
|
+
--color-warning: #f59e0b; /* text, icon on warning-subtle */
|
|
159
|
+
--color-warning-subtle: #27200a; /* background tint */
|
|
160
|
+
--color-danger: #ef4444; /* text, icon on danger-subtle */
|
|
161
|
+
--color-danger-subtle: #2a0a0a; /* background tint */
|
|
162
|
+
--color-danger-hover-bg: rgba(239, 68, 68, 0.20); /* destructive hover surface */
|
|
163
|
+
--color-danger-active-bg: rgba(239, 68, 68, 0.30); /* destructive active surface */
|
|
164
|
+
--color-overlay-scrim: rgba(8, 12, 20, 0.72); /* modal and palette backdrop */
|
|
165
|
+
--color-info: #38bdf8; /* text, icon on info-subtle */
|
|
166
|
+
--color-info-subtle: #082030; /* background tint */
|
|
167
|
+
|
|
168
|
+
/* ---------------------------------------------------------------
|
|
169
|
+
DATA VISUALISATION SERIES
|
|
170
|
+
Six perceptually-spaced hues that remain distinguishable:
|
|
171
|
+
- At 4px line weight on dark background
|
|
172
|
+
- To the most common color-vision deficiencies (deuteranopia,
|
|
173
|
+
protanopia, tritanopia)
|
|
174
|
+
The series starts with accent (violet) to tie viz to brand,
|
|
175
|
+
then fans out through sky, emerald, amber, pink, orange --
|
|
176
|
+
no two adjacent hues share the same luminance band.
|
|
177
|
+
--------------------------------------------------------------- */
|
|
178
|
+
--color-viz-1: #7c5af8; /* violet -- accent, brand-tied */
|
|
179
|
+
--color-viz-2: #38bdf8; /* sky -- info-adjacent */
|
|
180
|
+
--color-viz-3: #22c55e; /* emerald -- success-adjacent */
|
|
181
|
+
--color-viz-4: #f59e0b; /* amber -- warning-adjacent */
|
|
182
|
+
--color-viz-5: #f472b6; /* pink -- no semantic overlap */
|
|
183
|
+
--color-viz-6: #fb923c; /* orange -- no semantic overlap */
|
|
184
|
+
|
|
185
|
+
/* ---------------------------------------------------------------
|
|
186
|
+
FOCUS RING
|
|
187
|
+
WCAG 2.1 SC 2.4.11 (Level AA, WCAG 2.2): focus indicator must
|
|
188
|
+
be at least 2px wide and have a contrast ratio >= 3:1 against
|
|
189
|
+
adjacent colors. Our 2px outline at 2px offset on bg-base gives
|
|
190
|
+
approximately 8:1 contrast for the accent color.
|
|
191
|
+
Applied globally via :focus-visible in the @layer base block.
|
|
192
|
+
--------------------------------------------------------------- */
|
|
193
|
+
--color-focus-ring: var(--color-accent);
|
|
194
|
+
|
|
195
|
+
/* ================================================================
|
|
196
|
+
TYPE SCALE
|
|
197
|
+
Ratio: 1.250 (Major Third) applied to a 16px base.
|
|
198
|
+
Fluid clamp() interpolates linearly from 320px to 1440px viewport.
|
|
199
|
+
Formula: clamp(min, min + (max - min) * (100vw - 320px) / 1120, max)
|
|
200
|
+
Simplified: clamp(min_rem, slope_vw + intercept_rem, max_rem)
|
|
201
|
+
|
|
202
|
+
Tracking decisions:
|
|
203
|
+
Display/h1: -0.03em (optical tightening at large sizes prevents
|
|
204
|
+
letterforms from appearing to float apart)
|
|
205
|
+
h2/h3: -0.02em to -0.01em (gradual relaxation toward body size)
|
|
206
|
+
body/small: 0em (neutral -- reading comfort over optical correction)
|
|
207
|
+
caption: +0.01em (slight opening improves legibility at 12px)
|
|
208
|
+
mono: 0em (monospace already has designed optical spacing)
|
|
209
|
+
|
|
210
|
+
Line-height decisions:
|
|
211
|
+
display/h1 (1.1): content is usually 1-2 lines, no long reads;
|
|
212
|
+
tight LH maximises visual impact of large type.
|
|
213
|
+
h2/h3 (1.25-1.30): medium titles may wrap; needs breathing room.
|
|
214
|
+
body (1.5): WCAG 1.4.8 recommends >= 1.5 for body text.
|
|
215
|
+
caption (1.4): compact but not cramped; used only for short labels.
|
|
216
|
+
mono (1.6): extra breathing for code lines improves scan speed.
|
|
217
|
+
================================================================ */
|
|
218
|
+
|
|
219
|
+
--type-display: clamp(2rem, 4vw + 1rem, 3rem);
|
|
220
|
+
--type-display-lh: 1.1;
|
|
221
|
+
--type-display-ls: -0.03em;
|
|
222
|
+
|
|
223
|
+
--type-h1: clamp(1.5rem, 2.5vw + 0.75rem, 2.25rem);
|
|
224
|
+
--type-h1-lh: 1.15;
|
|
225
|
+
--type-h1-ls: -0.025em;
|
|
226
|
+
|
|
227
|
+
--type-h2: clamp(1.25rem, 1.8vw + 0.6rem, 1.75rem);
|
|
228
|
+
--type-h2-lh: 1.25;
|
|
229
|
+
--type-h2-ls: -0.02em;
|
|
230
|
+
|
|
231
|
+
--type-h3: clamp(1.125rem, 1.2vw + 0.5rem, 1.375rem);
|
|
232
|
+
--type-h3-lh: 1.3;
|
|
233
|
+
--type-h3-ls: -0.01em;
|
|
234
|
+
|
|
235
|
+
--type-h4: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);
|
|
236
|
+
--type-h4-lh: 1.35;
|
|
237
|
+
--type-h4-ls: -0.005em;
|
|
238
|
+
|
|
239
|
+
/* Body is not fluid -- a stable 16px baseline prevents micro-jitter
|
|
240
|
+
in dense layouts when the viewport resizes by a few pixels. */
|
|
241
|
+
--type-body: 1rem;
|
|
242
|
+
--type-body-lh: 1.5;
|
|
243
|
+
--type-body-ls: 0em;
|
|
244
|
+
|
|
245
|
+
--type-small: 0.875rem;
|
|
246
|
+
--type-small-lh: 1.45;
|
|
247
|
+
--type-small-ls: 0em;
|
|
248
|
+
|
|
249
|
+
--type-caption: 0.75rem;
|
|
250
|
+
--type-caption-lh: 1.4;
|
|
251
|
+
--type-caption-ls: 0.01em;
|
|
252
|
+
|
|
253
|
+
--type-mono: 0.8125rem; /* 13px -- slightly smaller than body for visual weight parity */
|
|
254
|
+
--type-mono-lh: 1.6;
|
|
255
|
+
--type-mono-ls: 0em;
|
|
256
|
+
|
|
257
|
+
--font-sans: 'Inter Variable', system-ui, -apple-system, sans-serif;
|
|
258
|
+
--font-mono: 'JetBrains Mono Variable', ui-monospace, 'Cascadia Code', monospace;
|
|
259
|
+
|
|
260
|
+
--font-weight-regular: 400;
|
|
261
|
+
--font-weight-medium: 500;
|
|
262
|
+
--font-weight-semibold: 600;
|
|
263
|
+
--font-weight-bold: 700;
|
|
264
|
+
|
|
265
|
+
/* ================================================================
|
|
266
|
+
SPACING SCALE
|
|
267
|
+
Base unit: 4px (--space-1).
|
|
268
|
+
Why 4px and not 8px: dashboard UIs are data-dense. The 4px base
|
|
269
|
+
gives fine-grained control for compact table cells, badge padding,
|
|
270
|
+
and inline micro-gaps without resorting to arbitrary values.
|
|
271
|
+
Named steps ascend as multiples of 4px up to 256px.
|
|
272
|
+
Semantic aliases are what components USE; numeric steps are what
|
|
273
|
+
the design spec references.
|
|
274
|
+
================================================================ */
|
|
275
|
+
|
|
276
|
+
--space-px: 1px;
|
|
277
|
+
--space-0: 0px;
|
|
278
|
+
--space-1: 4px; /* micro: icon-to-text gap, badge inset */
|
|
279
|
+
--space-2: 8px; /* tight: chip padding, table cell vertical */
|
|
280
|
+
--space-3: 12px; /* compact: input vertical padding, small gap */
|
|
281
|
+
--space-4: 16px; /* base: button horizontal padding, list spacing */
|
|
282
|
+
--space-5: 20px; /* medium: label-to-input gap */
|
|
283
|
+
--space-6: 24px; /* card internal padding, section inner spacing */
|
|
284
|
+
--space-7: 28px; /* modal header height, sidebar section gap */
|
|
285
|
+
--space-8: 32px; /* section vertical gap */
|
|
286
|
+
--space-9: 36px;
|
|
287
|
+
--space-10: 40px; /* sidebar item height, large button height */
|
|
288
|
+
--space-11: 44px; /* table row height -- WCAG 2.5.5 touch target */
|
|
289
|
+
--space-12: 48px; /* topbar height */
|
|
290
|
+
--space-14: 56px;
|
|
291
|
+
--space-16: 64px; /* sidebar width collapsed */
|
|
292
|
+
--space-20: 80px;
|
|
293
|
+
--space-24: 96px;
|
|
294
|
+
--space-32: 128px; /* empty state illustration reserved area */
|
|
295
|
+
--space-40: 160px;
|
|
296
|
+
--space-48: 192px;
|
|
297
|
+
--space-56: 224px;
|
|
298
|
+
--space-64: 256px;
|
|
299
|
+
|
|
300
|
+
/* Semantic spacing aliases -- what components reference */
|
|
301
|
+
--spacing-page-edge: clamp(1.5rem, 4vw, 2.5rem); /* 24px to 40px */
|
|
302
|
+
--spacing-section: var(--space-8); /* 32px between page sections */
|
|
303
|
+
--spacing-card-pad: var(--space-6); /* 24px card internal padding */
|
|
304
|
+
--spacing-card-gap: var(--space-5); /* 20px gap between cards in grid */
|
|
305
|
+
--spacing-input-pad-v: var(--space-3); /* 12px input vertical padding */
|
|
306
|
+
--spacing-input-pad-h: var(--space-4); /* 16px input horizontal padding */
|
|
307
|
+
--spacing-topbar-h: var(--space-12); /* 48px topbar height */
|
|
308
|
+
--spacing-sidebar-w: 240px; /* sidebar expanded width */
|
|
309
|
+
--spacing-sidebar-w-col: var(--space-16); /* sidebar collapsed width: 64px */
|
|
310
|
+
|
|
311
|
+
/* ================================================================
|
|
312
|
+
RADIUS SYSTEM
|
|
313
|
+
Three distinct radii communicate affordance tier:
|
|
314
|
+
sharp (2px): data-dense elements: table chips, code blocks.
|
|
315
|
+
Signals precision and density.
|
|
316
|
+
default (8px): cards, inputs, modals. Neutral -- does not
|
|
317
|
+
draw attention to its own rounding.
|
|
318
|
+
large (12px): prominent panels, toasts, sidepanels.
|
|
319
|
+
Communicates soft/approachable without being playful.
|
|
320
|
+
pill: badges, tags, toggles. Unmistakably interactive.
|
|
321
|
+
circle: avatars, icon buttons, spinners.
|
|
322
|
+
================================================================ */
|
|
323
|
+
|
|
324
|
+
--radius-sharp: 2px;
|
|
325
|
+
--radius-sm: 4px;
|
|
326
|
+
--radius-default: 8px;
|
|
327
|
+
--radius-lg: 12px;
|
|
328
|
+
--radius-xl: 16px;
|
|
329
|
+
--radius-pill: 9999px;
|
|
330
|
+
--radius-circle: 50%;
|
|
331
|
+
|
|
332
|
+
/* ================================================================
|
|
333
|
+
ELEVATION SYSTEM (3 levels)
|
|
334
|
+
Philosophy: On dark backgrounds, standard box-shadow produces
|
|
335
|
+
muddy grey blobs that reduce depth instead of conveying it.
|
|
336
|
+
Instead we use:
|
|
337
|
+
- Inset top border: simulates a "light source from above" highlight
|
|
338
|
+
that reads as a raised surface.
|
|
339
|
+
- Subtle ambient shadow: very low opacity dark shadow for the
|
|
340
|
+
physical impression of lift.
|
|
341
|
+
- No colored glows on structural shadows (only focus states use
|
|
342
|
+
a colored outline).
|
|
343
|
+
Level 1: card/panel lift (structural)
|
|
344
|
+
Level 2: floating layer (dropdown, popover, tooltip)
|
|
345
|
+
Level 3: modal layer (dialog, command palette)
|
|
346
|
+
================================================================ */
|
|
347
|
+
|
|
348
|
+
--shadow-1:
|
|
349
|
+
inset 0 1px 0 rgba(255,255,255,0.06),
|
|
350
|
+
0 1px 3px rgba(0,0,0,0.30),
|
|
351
|
+
0 2px 8px rgba(0,0,0,0.20);
|
|
352
|
+
|
|
353
|
+
--shadow-2:
|
|
354
|
+
inset 0 1px 0 rgba(255,255,255,0.08),
|
|
355
|
+
0 4px 12px rgba(0,0,0,0.40),
|
|
356
|
+
0 8px 24px rgba(0,0,0,0.25),
|
|
357
|
+
0 0 0 1px rgba(255,255,255,0.04);
|
|
358
|
+
|
|
359
|
+
--shadow-3:
|
|
360
|
+
0 8px 32px rgba(0,0,0,0.55),
|
|
361
|
+
0 24px 64px rgba(0,0,0,0.40),
|
|
362
|
+
0 0 0 1px rgba(255,255,255,0.06),
|
|
363
|
+
0 0 60px rgba(124,90,248,0.08);
|
|
364
|
+
|
|
365
|
+
/* Focus ring shadow -- supplements outline on complex elements */
|
|
366
|
+
--shadow-focus:
|
|
367
|
+
0 0 0 2px var(--color-bg-base),
|
|
368
|
+
0 0 0 4px var(--color-focus-ring);
|
|
369
|
+
|
|
370
|
+
/* ================================================================
|
|
371
|
+
MOTION SYSTEM
|
|
372
|
+
Design principle: motion communicates spatial metaphor.
|
|
373
|
+
|
|
374
|
+
Entering (ease-out): element travels from its source toward its
|
|
375
|
+
resting position, shedding velocity on arrival. The observer
|
|
376
|
+
sees it "land" naturally.
|
|
377
|
+
|
|
378
|
+
Leaving (ease-in): element builds momentum toward its destination.
|
|
379
|
+
The exit feels intentional, not accidental.
|
|
380
|
+
|
|
381
|
+
Toggling (ease-in-out): symmetric: equal energy on departure
|
|
382
|
+
and arrival. Appropriate for accordions, sliders, state flips
|
|
383
|
+
where no direction is conceptually "upward".
|
|
384
|
+
|
|
385
|
+
Spring (ease-spring): slight overshoot produces the delightful
|
|
386
|
+
"bounce" sensation reserved for micro-interactions where extra
|
|
387
|
+
delight is warranted (badge appear, success icon pop).
|
|
388
|
+
|
|
389
|
+
Subtle: minimal energy, for low-profile state fades (muted hover
|
|
390
|
+
on secondary elements).
|
|
391
|
+
|
|
392
|
+
Duration philosophy:
|
|
393
|
+
micro (80-150ms): state changes the eye tracks peripherally.
|
|
394
|
+
Used for: focus ring, status dot, badge colour.
|
|
395
|
+
component (200-350ms): elements in the focal region.
|
|
396
|
+
Used for: button hover, dropdown open, input focus.
|
|
397
|
+
layout (400-500ms): page-level spatial changes.
|
|
398
|
+
Used for: sidebar collapse, panel slide, route transition.
|
|
399
|
+
|
|
400
|
+
Stagger: 40ms between list items. Below 30ms the items appear
|
|
401
|
+
simultaneous; above 60ms the animation feels mechanical.
|
|
402
|
+
================================================================ */
|
|
403
|
+
|
|
404
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* spring-like enter */
|
|
405
|
+
--ease-in: cubic-bezier(0.4, 0, 1, 1); /* momentum exit */
|
|
406
|
+
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* symmetric toggle */
|
|
407
|
+
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* overshoot delight */
|
|
408
|
+
--ease-subtle: cubic-bezier(0.25, 0, 0, 1); /* low-profile fade */
|
|
409
|
+
|
|
410
|
+
--duration-instant: 80ms; /* state dot, focus ring, icon swap */
|
|
411
|
+
--duration-fast: 120ms; /* badge appear, status change */
|
|
412
|
+
--duration-normal: 200ms; /* button hover, input focus border */
|
|
413
|
+
--duration-medium: 300ms; /* dropdown open, tooltip appear */
|
|
414
|
+
--duration-slow: 400ms; /* sidebar collapse, panel open */
|
|
415
|
+
--duration-layout: 500ms; /* page-level spatial transitions */
|
|
416
|
+
|
|
417
|
+
--stagger-base: 40ms; /* per-item stagger delay in list animations */
|
|
418
|
+
|
|
419
|
+
/* ================================================================
|
|
420
|
+
Z-INDEX SCALE
|
|
421
|
+
A named scale prevents the "z-index arms race" where components
|
|
422
|
+
competing for stacking order escalate to z-index:9999.
|
|
423
|
+
Each level owns a clearly-described stacking context.
|
|
424
|
+
================================================================ */
|
|
425
|
+
|
|
426
|
+
--z-sidebar: 30;
|
|
427
|
+
--z-topbar: 40;
|
|
428
|
+
--z-dropdown: 50;
|
|
429
|
+
--z-tooltip: 60;
|
|
430
|
+
--z-modal: 70;
|
|
431
|
+
--z-toast: 80;
|
|
432
|
+
--z-command: 90;
|
|
433
|
+
|
|
434
|
+
/* ================================================================
|
|
435
|
+
COMPONENT-LEVEL TOKENS (LAYER 3)
|
|
436
|
+
Derived from semantic tokens. Allow centralised component
|
|
437
|
+
defaults: change one token here, all instances update.
|
|
438
|
+
================================================================ */
|
|
439
|
+
|
|
440
|
+
/* Button */
|
|
441
|
+
--btn-h-sm: 32px;
|
|
442
|
+
--btn-h-md: 40px;
|
|
443
|
+
--btn-h-lg: 48px;
|
|
444
|
+
--btn-px-sm: var(--space-3);
|
|
445
|
+
--btn-px-md: var(--space-4);
|
|
446
|
+
--btn-px-lg: var(--space-6);
|
|
447
|
+
--btn-radius: var(--radius-default);
|
|
448
|
+
|
|
449
|
+
/* Input */
|
|
450
|
+
--input-h: 40px;
|
|
451
|
+
--input-radius: var(--radius-default);
|
|
452
|
+
--input-pl: var(--spacing-input-pad-h); /* overridden when iconLeft present */
|
|
453
|
+
|
|
454
|
+
/* Table */
|
|
455
|
+
--table-row-h: var(--space-11); /* 44px -- WCAG touch target minimum */
|
|
456
|
+
--table-header-h: 40px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/* ================================================================
|
|
460
|
+
LIGHT THEME OVERRIDES
|
|
461
|
+
Applied when <html data-theme="light"> is set.
|
|
462
|
+
ONLY Layer 2 semantic tokens are overridden here.
|
|
463
|
+
Raw scales (Layer 1) and component tokens (Layer 3) are
|
|
464
|
+
intentionally NOT overridden -- they derive their values from
|
|
465
|
+
the semantic tokens, so they update automatically.
|
|
466
|
+
================================================================ */
|
|
467
|
+
|
|
468
|
+
[data-theme='light'] {
|
|
469
|
+
color-scheme: light;
|
|
470
|
+
|
|
471
|
+
--color-bg-base: var(--color-neutral-50);
|
|
472
|
+
--color-bg-raised: var(--color-neutral-0);
|
|
473
|
+
--color-bg-raised-hover: var(--color-neutral-100);
|
|
474
|
+
--color-bg-raised-active: var(--color-neutral-200);
|
|
475
|
+
--color-bg-overlay: var(--color-neutral-100);
|
|
476
|
+
--color-bg-sunken: var(--color-neutral-100);
|
|
477
|
+
--color-bg-highlight: #ede9ff;
|
|
478
|
+
|
|
479
|
+
--color-text-primary: var(--color-neutral-900);
|
|
480
|
+
--color-text-secondary: var(--color-neutral-600);
|
|
481
|
+
--color-text-muted: var(--color-neutral-500);
|
|
482
|
+
--color-text-accent: var(--color-accent);
|
|
483
|
+
|
|
484
|
+
--color-border-default: var(--color-neutral-200);
|
|
485
|
+
--color-border-emphasis: var(--color-neutral-300);
|
|
486
|
+
|
|
487
|
+
--color-accent-subtle: #ede9ff;
|
|
488
|
+
--color-accent-muted: #a78bfa;
|
|
489
|
+
|
|
490
|
+
--color-success-subtle: #f0fdf4;
|
|
491
|
+
--color-warning-subtle: #fffbeb;
|
|
492
|
+
--color-danger-subtle: #fef2f2;
|
|
493
|
+
--color-overlay-scrim: rgba(15, 20, 30, 0.45);
|
|
494
|
+
--color-info-subtle: #f0f9ff;
|
|
495
|
+
|
|
496
|
+
/* Light mode shadows: softer, less opaque -- dark shadows on
|
|
497
|
+
light backgrounds need lower opacity to avoid over-saturation. */
|
|
498
|
+
--shadow-1:
|
|
499
|
+
inset 0 1px 0 rgba(255,255,255,0.80),
|
|
500
|
+
0 1px 3px rgba(0,0,0,0.08),
|
|
501
|
+
0 2px 8px rgba(0,0,0,0.06);
|
|
502
|
+
|
|
503
|
+
--shadow-2:
|
|
504
|
+
0 4px 12px rgba(0,0,0,0.10),
|
|
505
|
+
0 8px 24px rgba(0,0,0,0.07),
|
|
506
|
+
0 0 0 1px rgba(0,0,0,0.05);
|
|
507
|
+
|
|
508
|
+
--shadow-3:
|
|
509
|
+
0 8px 32px rgba(0,0,0,0.15),
|
|
510
|
+
0 24px 64px rgba(0,0,0,0.10),
|
|
511
|
+
0 0 0 1px rgba(0,0,0,0.06);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* ================================================================
|
|
515
|
+
TAILWIND v4 @theme BLOCK
|
|
516
|
+
Registers all tokens as Tailwind utility classes so components
|
|
517
|
+
can use class-based notation (bg-bg-base, text-text-primary)
|
|
518
|
+
without arbitrary value syntax.
|
|
519
|
+
|
|
520
|
+
The xl breakpoint override (75rem instead of default 80rem /
|
|
521
|
+
1280px) eliminates the "MacBook Air dead zone": laptop viewports
|
|
522
|
+
between 1200-1279px were stuck at lg: utilities despite having
|
|
523
|
+
enough horizontal space for the xl: layout tier.
|
|
524
|
+
================================================================ */
|
|
525
|
+
|
|
526
|
+
@theme {
|
|
527
|
+
/* Breakpoint override */
|
|
528
|
+
--breakpoint-xl: 75rem; /* 1200px instead of Tailwind default 1280px */
|
|
529
|
+
|
|
530
|
+
/* Font families */
|
|
531
|
+
--font-sans: var(--font-sans);
|
|
532
|
+
--font-mono: var(--font-mono);
|
|
533
|
+
|
|
534
|
+
/* Accent colors for Tailwind utilities */
|
|
535
|
+
--color-accent: {{ACCENT_HEX}};
|
|
536
|
+
--color-accent-hover: {{ACCENT_HOVER_HEX}};
|
|
537
|
+
--color-accent-subtle: #1e1640;
|
|
538
|
+
--color-accent-muted: #4a3490;
|
|
539
|
+
|
|
540
|
+
/* Neutral scale */
|
|
541
|
+
--color-neutral-0: #ffffff;
|
|
542
|
+
--color-neutral-50: #f7f8fa;
|
|
543
|
+
--color-neutral-100: #eef0f4;
|
|
544
|
+
--color-neutral-200: #dde1e9;
|
|
545
|
+
--color-neutral-300: #c4cad6;
|
|
546
|
+
--color-neutral-400: #9aa3b4;
|
|
547
|
+
--color-neutral-500: #6b7689;
|
|
548
|
+
--color-neutral-600: #4e5769;
|
|
549
|
+
--color-neutral-700: #343d4f;
|
|
550
|
+
--color-neutral-800: #1f2635;
|
|
551
|
+
--color-neutral-850: #171d2b;
|
|
552
|
+
--color-neutral-900: #0f1420;
|
|
553
|
+
--color-neutral-950: #080c14;
|
|
554
|
+
|
|
555
|
+
/* Status colors */
|
|
556
|
+
--color-success: #22c55e;
|
|
557
|
+
--color-warning: #f59e0b;
|
|
558
|
+
--color-danger: #ef4444;
|
|
559
|
+
--color-info: #38bdf8;
|
|
560
|
+
|
|
561
|
+
/* Viz series */
|
|
562
|
+
--color-viz-1: #7c5af8;
|
|
563
|
+
--color-viz-2: #38bdf8;
|
|
564
|
+
--color-viz-3: #22c55e;
|
|
565
|
+
--color-viz-4: #f59e0b;
|
|
566
|
+
--color-viz-5: #f472b6;
|
|
567
|
+
--color-viz-6: #fb923c;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/* ================================================================
|
|
571
|
+
BASE STYLES
|
|
572
|
+
Semantic HTML defaults. Applied inside @layer base so Tailwind
|
|
573
|
+
utilities in components can override via normal cascade order.
|
|
574
|
+
================================================================ */
|
|
575
|
+
|
|
576
|
+
@layer base {
|
|
577
|
+
*,
|
|
578
|
+
*::before,
|
|
579
|
+
*::after {
|
|
580
|
+
box-sizing: border-box;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
html {
|
|
584
|
+
/* scroll-behavior:smooth is NOT set here; it breaks browser
|
|
585
|
+
native scroll-jump (anchor links) in Firefox and conflicts
|
|
586
|
+
with framer-motion AnimatePresence page transitions.
|
|
587
|
+
Individual smooth-scroll sections set it on their container. */
|
|
588
|
+
scroll-behavior: auto;
|
|
589
|
+
/* Prevent horizontal overflow from miscalculated widths */
|
|
590
|
+
overflow-x: hidden;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
body {
|
|
594
|
+
background-color: var(--color-bg-base);
|
|
595
|
+
color: var(--color-text-primary);
|
|
596
|
+
font-family: var(--font-sans);
|
|
597
|
+
font-size: var(--type-body);
|
|
598
|
+
line-height: var(--type-body-lh);
|
|
599
|
+
letter-spacing: var(--type-body-ls);
|
|
600
|
+
-webkit-font-smoothing: antialiased;
|
|
601
|
+
-moz-osx-font-smoothing: grayscale;
|
|
602
|
+
font-feature-settings: 'calt' 1, 'cv02' 1, 'cv03' 1, 'cv04' 1;
|
|
603
|
+
/* Transitioning background-color here enables smooth theme toggle.
|
|
604
|
+
Only background-color -- not color or other properties. */
|
|
605
|
+
transition:
|
|
606
|
+
background-color var(--duration-medium) var(--ease-in-out),
|
|
607
|
+
color var(--duration-medium) var(--ease-in-out);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/* Heading hierarchy with role-differentiated type scale */
|
|
611
|
+
h1 {
|
|
612
|
+
font-family: var(--font-sans);
|
|
613
|
+
font-size: var(--type-h1);
|
|
614
|
+
font-weight: var(--font-weight-bold);
|
|
615
|
+
line-height: var(--type-h1-lh);
|
|
616
|
+
letter-spacing: var(--type-h1-ls);
|
|
617
|
+
color: var(--color-text-primary);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
h2 {
|
|
621
|
+
font-family: var(--font-sans);
|
|
622
|
+
font-size: var(--type-h2);
|
|
623
|
+
font-weight: var(--font-weight-bold);
|
|
624
|
+
line-height: var(--type-h2-lh);
|
|
625
|
+
letter-spacing: var(--type-h2-ls);
|
|
626
|
+
color: var(--color-text-primary);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
h3 {
|
|
630
|
+
font-family: var(--font-sans);
|
|
631
|
+
font-size: var(--type-h3);
|
|
632
|
+
font-weight: var(--font-weight-semibold);
|
|
633
|
+
line-height: var(--type-h3-lh);
|
|
634
|
+
letter-spacing: var(--type-h3-ls);
|
|
635
|
+
color: var(--color-text-primary);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
h4 {
|
|
639
|
+
font-family: var(--font-sans);
|
|
640
|
+
font-size: var(--type-h4);
|
|
641
|
+
font-weight: var(--font-weight-semibold);
|
|
642
|
+
line-height: var(--type-h4-lh);
|
|
643
|
+
letter-spacing: var(--type-h4-ls);
|
|
644
|
+
color: var(--color-text-primary);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/* Focus ring: global :focus-visible rule.
|
|
648
|
+
Components with complex inner structure (button with icon children)
|
|
649
|
+
add --shadow-focus via box-shadow in addition to this outline.
|
|
650
|
+
NEVER suppress focus-visible for accessibility. */
|
|
651
|
+
:focus-visible {
|
|
652
|
+
outline: 2px solid var(--color-focus-ring);
|
|
653
|
+
outline-offset: 2px;
|
|
654
|
+
border-radius: var(--radius-sm);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* Scroll margin for anchor-linked headings: accounts for sticky topbar */
|
|
658
|
+
[id] {
|
|
659
|
+
scroll-margin-top: calc(var(--spacing-topbar-h) + var(--space-4));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* Code elements use mono font and sunken background */
|
|
663
|
+
code, pre, kbd, samp {
|
|
664
|
+
font-family: var(--font-mono);
|
|
665
|
+
font-size: var(--type-mono);
|
|
666
|
+
line-height: var(--type-mono-lh);
|
|
667
|
+
letter-spacing: var(--type-mono-ls);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* Reduce motion for SVG animations: browsers do not always honour
|
|
671
|
+
the @media block for inline SVG animations */
|
|
672
|
+
@media (prefers-reduced-motion: reduce) {
|
|
673
|
+
svg * {
|
|
674
|
+
animation-duration: 0.01ms !important;
|
|
675
|
+
transition-duration: 0.01ms !important;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* ================================================================
|
|
681
|
+
SKELETON SHIMMER ANIMATION
|
|
682
|
+
Used by SkeletonBlock component. Defined globally because @keyframes
|
|
683
|
+
inside a component CSS module would duplicate across instances.
|
|
684
|
+
The animation uses transform:translateX only (no layout-affecting
|
|
685
|
+
properties) to stay on the GPU compositor thread.
|
|
686
|
+
================================================================ */
|
|
687
|
+
|
|
688
|
+
@keyframes skeleton-shimmer {
|
|
689
|
+
0% { transform: translateX(-100%); }
|
|
690
|
+
100% { transform: translateX(100%); }
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* Stat card loading pulse: opacity-only fade (no geometry change) */
|
|
694
|
+
@keyframes pulse-opacity {
|
|
695
|
+
0%, 100% { opacity: 1; }
|
|
696
|
+
50% { opacity: 0.4; }
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/* Toast/badge entrance from below */
|
|
700
|
+
@keyframes slide-up {
|
|
701
|
+
from { transform: translateY(8px); opacity: 0; }
|
|
702
|
+
to { transform: translateY(0); opacity: 1; }
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* ================================================================
|
|
706
|
+
REDUCED MOTION OVERRIDE
|
|
707
|
+
This block is the single source of truth for reduced-motion
|
|
708
|
+
behaviour. It intentionally uses !important to override any
|
|
709
|
+
inline or component-level transition declaration.
|
|
710
|
+
|
|
711
|
+
Opacity fades are preserved at 80ms (--duration-instant) because
|
|
712
|
+
users with vestibular disorders can still perceive colour/opacity
|
|
713
|
+
changes without experiencing motion sickness from transforms.
|
|
714
|
+
|
|
715
|
+
Transform-based animations are removed entirely (set to none).
|
|
716
|
+
Framer-motion variants should check useReducedMotion() and skip
|
|
717
|
+
x/y/scale variants, keeping only opacity.
|
|
718
|
+
================================================================ */
|
|
719
|
+
|
|
720
|
+
@media (prefers-reduced-motion: reduce) {
|
|
721
|
+
*,
|
|
722
|
+
*::before,
|
|
723
|
+
*::after {
|
|
724
|
+
animation-duration: 0.01ms !important;
|
|
725
|
+
animation-iteration-count: 1 !important;
|
|
726
|
+
transition-duration: 0.01ms !important;
|
|
727
|
+
scroll-behavior: auto !important;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/* Restore opacity transitions for non-motion feedback */
|
|
731
|
+
[data-motion-safe] {
|
|
732
|
+
transition-duration: 80ms !important;
|
|
733
|
+
animation-duration: 80ms !important;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/* Skeleton shimmer: show static muted colour instead of animating */
|
|
737
|
+
.skeleton-shimmer::after {
|
|
738
|
+
animation: none !important;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/* DataTable row hover (the table sets per-cell styles inline; the row hover is
|
|
743
|
+
a global rule so non-selected rows get a token-driven hover surface). */
|
|
744
|
+
.data-table-row { transition: background-color var(--duration-fast, 150ms) var(--ease-out, ease-out); }
|
|
745
|
+
.data-table-row:hover { background-color: var(--color-bg-overlay); }
|
|
746
|
+
@media (prefers-reduced-motion: reduce) { .data-table-row { transition: none; } }
|