@digithings-ai/design 0.0.0-stage → 0.1.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 +343 -2
- package/app-shell-terminal/index.js +300 -0
- package/app-shell-terminal/slash-commands.js +59 -0
- package/app-shell-terminal/styles.css +234 -0
- package/fonts.ts +15 -0
- package/html-escape.js +19 -0
- package/motion.json +6 -0
- package/package.json +48 -4
- package/palettes.css +1942 -0
- package/quant-native/styles.css +99 -0
- package/quant-native/ticker.js +100 -0
- package/releases.json +122 -0
- package/site/README.md +609 -0
- package/site/site.css +475 -0
- package/tokens.css +268 -0
- package/tokens.ts +508 -0
package/site/site.css
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
digithings site foundation — shared component layer for the redesign.
|
|
3
|
+
Consumes the [data-theme] semantic tokens in ../tokens.css. Terminal-CLI,
|
|
4
|
+
utilitarian, spacious; light + dark; reduced-motion safe.
|
|
5
|
+
Page-specific overrides live in each site's local style.css.
|
|
6
|
+
|
|
7
|
+
Nav shell, hero layout, cards, pills/stage, connected graph, and scroll
|
|
8
|
+
reveal were reimplemented as React components in @digithings-ai/ui after
|
|
9
|
+
the Dec 2025 migration (commit 681273a) — those selectors were removed
|
|
10
|
+
here (#1240). What remains is what digithings-web / digiquant-web still
|
|
11
|
+
import directly: brand mark, theme toggle, buttons, kicker/prompt, the
|
|
12
|
+
standalone hero title, the terminal block, sections, ProductFrame,
|
|
13
|
+
principles, and footer.
|
|
14
|
+
========================================================================== */
|
|
15
|
+
|
|
16
|
+
/* ── reset ──────────────────────────────────────────────────────────── */
|
|
17
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
18
|
+
* { margin: 0; }
|
|
19
|
+
html { scroll-behavior: auto; -webkit-text-size-adjust: 100%; background: var(--bg); }
|
|
20
|
+
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
font-family: var(--font-sans);
|
|
24
|
+
background: var(--bg);
|
|
25
|
+
color: var(--ink);
|
|
26
|
+
line-height: 1.65;
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
letter-spacing: -0.011em;
|
|
29
|
+
-webkit-font-smoothing: antialiased;
|
|
30
|
+
text-rendering: optimizeLegibility;
|
|
31
|
+
overflow-x: hidden;
|
|
32
|
+
transition: background 0.4s var(--ease), color 0.4s var(--ease);
|
|
33
|
+
}
|
|
34
|
+
img, svg { max-width: 100%; display: block; }
|
|
35
|
+
a { color: inherit; text-decoration: none; }
|
|
36
|
+
::selection { background: var(--accent); color: var(--on-accent); }
|
|
37
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 0; }
|
|
38
|
+
|
|
39
|
+
/* ── atmosphere ─────────────────────────────────────────────────────── */
|
|
40
|
+
.grain {
|
|
41
|
+
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
|
42
|
+
opacity: var(--grain-opacity, 0.03); mix-blend-mode: var(--grain-blend, screen);
|
|
43
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
44
|
+
}
|
|
45
|
+
.glow {
|
|
46
|
+
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
|
47
|
+
background: radial-gradient(48rem 30rem at 72% -8%, var(--accent-weak), transparent 60%);
|
|
48
|
+
animation: glow-drift 18s var(--ease) infinite alternate;
|
|
49
|
+
}
|
|
50
|
+
@keyframes glow-drift { to { transform: translate(-5%, 2%); } }
|
|
51
|
+
|
|
52
|
+
/* ── layout ─────────────────────────────────────────────────────────── */
|
|
53
|
+
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); position: relative; z-index: 1; }
|
|
54
|
+
|
|
55
|
+
/* ── brand mark + theme toggle (nav shell lives in React DigiNav/DqNav) ─ */
|
|
56
|
+
/* Brand lockup. The wordmark is MONO at 400, matching the terminal mark it
|
|
57
|
+
alternates with — it used to inherit body's sans at .brand's weight 600, which
|
|
58
|
+
put a Geist Sans 600 word 0.6rem from a Geist Mono 400 mark: two typefaces and
|
|
59
|
+
a 200-weight gap side by side.
|
|
60
|
+
|
|
61
|
+
The two never render together, but that swap is NOT here: the brand anchor is
|
|
62
|
+
NavShell's own `.nav-shell-brand`, not `.brand`, so a `.brand .terminal-mark`
|
|
63
|
+
rule would never match. The responsive swap lives on the call sites in each
|
|
64
|
+
app's _nav.tsx as `max-[880px]:` utilities — 880px is NavShell's own
|
|
65
|
+
breakpoint, where its hamburger appears. */
|
|
66
|
+
.brand { --brand-mark: 1.625rem; --brand-gap: 0.6rem; --brand-word: 1.05rem; display: inline-flex; align-items: center; gap: var(--brand-gap); font-weight: 400; letter-spacing: -0.02em; }
|
|
67
|
+
.brand-mark { width: var(--brand-mark); height: var(--brand-mark); border-radius: 0; }
|
|
68
|
+
.brand-word { font-family: var(--font-mono); font-size: var(--brand-word); font-weight: 400; letter-spacing: 0; }
|
|
69
|
+
.brand-word b { color: var(--accent); font-weight: 400; }
|
|
70
|
+
|
|
71
|
+
.theme-toggle { --toggle-size: 2.375rem; --toggle-icon: 1.0625rem; display: inline-grid; place-items: center; width: var(--toggle-size); height: var(--toggle-size); border: 1px solid var(--hair); border-radius: 0; background: var(--surface); color: var(--ink-soft); cursor: pointer; transition: color 0.18s var(--ease), border-color 0.18s var(--ease), transform 0.18s var(--ease); }
|
|
72
|
+
.theme-toggle:hover { color: var(--ink); border-color: var(--hair-2); transform: translateY(-1px); }
|
|
73
|
+
.theme-toggle svg { width: var(--toggle-icon); height: var(--toggle-icon); }
|
|
74
|
+
|
|
75
|
+
/* ── buttons ───────────────────────────────────────────────────────────
|
|
76
|
+
Retired in the wave-3 close-out (Task 6). The kit `buttonVariants`
|
|
77
|
+
(@digithings-ai/ui/ui) is the canonical button dress; the last live consumer
|
|
78
|
+
(digithings-web DtNav) moved to it in T2, and the only remaining `.btn*`
|
|
79
|
+
carriers were the dead `chrome.tsx` `Nav` export and AuthCard's inert
|
|
80
|
+
`.btn-primary` (rendered by apps that never import this sheet) — both
|
|
81
|
+
cleaned in T6. Static `.closing-cta__secondary` links remain for the
|
|
82
|
+
markup-only site pages; use the kit `Button` anywhere React renders. */
|
|
83
|
+
|
|
84
|
+
/* ── kicker / prompt ────────────────────────────────────────────────── */
|
|
85
|
+
.prompt { color: var(--accent); font-weight: 600; font-family: var(--font-mono); }
|
|
86
|
+
.kicker { font-family: var(--font-mono); font-size: 0.8rem; letter-spacing: 0.02em; color: var(--accent); }
|
|
87
|
+
|
|
88
|
+
/* ── hero title (standalone, e.g. subsystem/module detail pages) ─────── */
|
|
89
|
+
.hero-title { font-family: var(--font-display); font-weight: 400; font-size: clamp(2.8rem, 6.4vw, 5rem); line-height: 1.02; letter-spacing: -0.02em; margin: 1.4rem 0 1.2rem; max-width: 16ch; }
|
|
90
|
+
.hero-title em { font-style: italic; color: var(--accent); }
|
|
91
|
+
|
|
92
|
+
/* ── terminal block (always dark) ───────────────────────────────────── */
|
|
93
|
+
.term { --term-dot: var(--text-xs); --term-bar-gap: var(--space-2); width: 100%; background: var(--term-bg); border: 1px solid var(--term-hair); border-radius: 0; overflow: hidden; box-shadow: 0 44px 100px -50px rgba(0,0,0,0.45); }
|
|
94
|
+
.term-bar { display: flex; align-items: center; gap: var(--term-bar-gap); padding: 0.7rem 0.9rem; background: var(--term-fill); border-bottom: 1px solid var(--term-hair); }
|
|
95
|
+
.term-bar i { width: var(--term-dot); height: var(--term-dot); border-radius: 50%; background: #3a342c; display: inline-block; }
|
|
96
|
+
.term-bar i:nth-child(1) { background: #ec6a5e; } .term-bar i:nth-child(2) { background: #f4bf4f; } .term-bar i:nth-child(3) { background: #61c554; }
|
|
97
|
+
.term-title { margin-left: auto; font-family: var(--font-mono); font-size: 0.72rem; color: var(--term-mute); }
|
|
98
|
+
.term-body { font-family: var(--font-mono); font-size: clamp(0.76rem, 1.2vw, 0.88rem); line-height: 1.95; color: var(--term-ink); padding: 1.2rem 1.3rem 1.4rem; margin: 0; min-height: 17em; white-space: pre-wrap; }
|
|
99
|
+
.tl-cmd { color: var(--term-ink); } .tl-cmd::before { content: "$ "; color: var(--accent); }
|
|
100
|
+
.tl-out { color: var(--term-mute); }
|
|
101
|
+
.tl-ok::before { content: "✓ "; color: var(--up); } .tl-ok { color: var(--term-mute); } .tl-ok b { color: var(--term-ink); font-weight: 500; }
|
|
102
|
+
.tl-mod { color: var(--term-ink); } .tl-mod b { color: var(--accent); font-weight: 500; }
|
|
103
|
+
.tl-install { color: var(--up); }
|
|
104
|
+
.tl-arrow { color: var(--accent); }
|
|
105
|
+
.term-cursor { display: inline-block; width: 0.6em; height: 1.05em; background: var(--accent); vertical-align: -0.16em; animation: blink 1.05s steps(1) infinite; }
|
|
106
|
+
@keyframes blink { 50% { opacity: 0; } }
|
|
107
|
+
|
|
108
|
+
/* inline install/clone command chip */
|
|
109
|
+
.cmdline { display: inline-flex; align-items: center; gap: 0.7rem; background: var(--term-bg); border: 1px solid var(--term-hair); border-radius: 0; padding: 0.55rem 0.65rem 0.55rem 1rem; font-family: var(--font-mono); font-size: 0.82rem; color: var(--term-ink); max-width: 100%; overflow-x: auto; }
|
|
110
|
+
.cmdline .prompt { color: var(--accent); }
|
|
111
|
+
|
|
112
|
+
/* ── sections ───────────────────────────────────────────────────────── */
|
|
113
|
+
.section { --site-section-y: clamp(4rem, 9vw, 7.5rem); padding: var(--site-section-y) 0; }
|
|
114
|
+
.section-alt { background: var(--surface-2); border-block: 1px solid var(--hair); }
|
|
115
|
+
.section-head { max-width: 56ch; margin-bottom: clamp(2.5rem, 5vw, 4rem); }
|
|
116
|
+
.section-head h2 { font-size: clamp(1.9rem, 4vw, 3rem); font-weight: 600; letter-spacing: -0.03em; line-height: 1.08; margin: 0.7rem 0 0.9rem; }
|
|
117
|
+
.section-head.center { margin-inline: auto; text-align: center; }
|
|
118
|
+
.section-head p { color: var(--ink-soft); font-size: clamp(1rem, 1.4vw, 1.12rem); }
|
|
119
|
+
.grid { display: grid; gap: var(--space-3); }
|
|
120
|
+
|
|
121
|
+
/* ── product frame (Graphite artboard / Cursor product screenshot) ────
|
|
122
|
+
Flat, CQ-scaled UI embed for marketing pages. "Atmospheric outside,
|
|
123
|
+
surgical inside" (EVOLUTION.md §7) — no mesh/glow/grain on the surface
|
|
124
|
+
itself; those live on the page background around the frame.
|
|
125
|
+
CQ scaling: `container-type: inline-size` on .product-frame turns its
|
|
126
|
+
own box into a query container, and .product-frame__surface sizes its
|
|
127
|
+
type in `cqi` (container-query inline-size units) clamped between a
|
|
128
|
+
floor and ceiling. That means UI text inside the frame shrinks with
|
|
129
|
+
the *frame's* width (e.g. a narrower column on mobile), not the
|
|
130
|
+
viewport — a screenshot-like embed stays legible without overflowing
|
|
131
|
+
at any breakpoint. ────────────────────────────────────────────────── */
|
|
132
|
+
.product-frame { width: 100%; max-width: var(--product-frame-w); margin-inline: auto; container-type: inline-size; }
|
|
133
|
+
.product-frame__surface { background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-lg); overflow: hidden; padding: clamp(0.85rem, 4cqi, 1.6rem); font-size: clamp(0.72rem, 2.4cqi, 1rem); line-height: 1.5; }
|
|
134
|
+
.product-frame__caption { margin-top: 0.75rem; font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink-mute); text-align: center; }
|
|
135
|
+
|
|
136
|
+
/* ── bento grid (Cursor-style linked feature cells) ───────────────────
|
|
137
|
+
Mobile-first: single column, 2×2 from 768px. Flat --surface panels,
|
|
138
|
+
no glass morphism (EVOLUTION.md §10 anti-pattern #8). ─────────────── */
|
|
139
|
+
.bento { display: grid; grid-template-columns: 1fr; gap: 1.25rem; width: 100%; max-width: var(--wrap-wide); margin-inline: auto; }
|
|
140
|
+
.bento__cell { display: flex; flex-direction: column; gap: var(--space-2); background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-lg); padding: var(--space-4); transition: transform var(--duration-hover) var(--ease-glide), border-color var(--duration-hover) var(--ease-glide); }
|
|
141
|
+
.bento__cell:not(.bento__cell--static):hover { transform: translateY(-3px); border-color: var(--hair-2); }
|
|
142
|
+
.bento__cell--static { cursor: default; }
|
|
143
|
+
.bento__cell--span-2 { grid-column: span 2; }
|
|
144
|
+
.bento__thumb { border-radius: var(--r-md); overflow: hidden; }
|
|
145
|
+
.bento__kicker { font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.02em; color: var(--accent); }
|
|
146
|
+
.bento__title { font-size: 1.1rem; font-weight: 600; letter-spacing: -0.02em; }
|
|
147
|
+
.bento__body { font-size: 0.9rem; color: var(--ink-soft); line-height: 1.5; }
|
|
148
|
+
.bento__cta { margin-top: auto; padding-top: var(--space-2); display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--accent); font-weight: 500; }
|
|
149
|
+
.bento__cta span[aria-hidden] { transition: transform var(--duration-hover) var(--ease-glide); }
|
|
150
|
+
.bento__cell:hover .bento__cta span[aria-hidden] { transform: translateX(3px); }
|
|
151
|
+
@media (min-width: 768px) {
|
|
152
|
+
.bento { grid-template-columns: repeat(2, 1fr); }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ── trust strip (proof / logo row) ───────────────────────────────────── */
|
|
156
|
+
.trust-strip { --trust-logo-h: 1.75rem; --trust-logo-max-w: 8.75rem; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: clamp(1.25rem, 4vw, 2.5rem); }
|
|
157
|
+
.trust-strip__item { display: inline-flex; align-items: center; font-family: var(--font-mono); font-size: 0.85rem; color: var(--ink-mute); }
|
|
158
|
+
.trust-strip__item img { height: var(--trust-logo-h); width: auto; max-width: var(--trust-logo-max-w); filter: grayscale(1); opacity: 0.7; }
|
|
159
|
+
/* --logos: integration-mark row (x.ai partner-mark pattern, #1229). Real
|
|
160
|
+
integrations only — no stock/generic logos. Marks are muted/grayscale by
|
|
161
|
+
default and lift to full color on hover; wrap each logo as
|
|
162
|
+
<span class="trust-strip__item"><img alt="Name"></span> (decorative-only
|
|
163
|
+
logos use alt=""). Works alongside the base text friction variant. */
|
|
164
|
+
.trust-strip--logos { gap: clamp(1.5rem, 4vw, 2.75rem); }
|
|
165
|
+
.trust-strip--logos .trust-strip__item { font-size: 0.95rem; color: var(--ink-soft); }
|
|
166
|
+
.trust-strip--logos .trust-strip__item img { transition: filter var(--duration-hover) var(--ease-glide), opacity var(--duration-hover) var(--ease-glide); }
|
|
167
|
+
.trust-strip--logos .trust-strip__item:hover { color: var(--ink); }
|
|
168
|
+
.trust-strip--logos .trust-strip__item:hover img { filter: grayscale(0); opacity: 1; }
|
|
169
|
+
|
|
170
|
+
/* ── reveal-up (scroll-triggered enter) ────────────────────────────────
|
|
171
|
+
CSS-only: owns the two visual states only. Trigger it either with
|
|
172
|
+
../scroll-trigger.js's `activateSelector` (toggles `.active` when the
|
|
173
|
+
element crosses the scroll line) or from the React `Reveal` component's
|
|
174
|
+
className (packages/ui/src/motion/primitives.tsx) — both are valid
|
|
175
|
+
drivers, so both class names are wired below. ───────────────────────── */
|
|
176
|
+
.reveal-up { opacity: 0; transform: translateY(1rem); transition: opacity var(--duration-reveal) var(--ease-glide), transform var(--duration-reveal) var(--ease-glide); }
|
|
177
|
+
.reveal-up.is-visible, .reveal-up.active { opacity: 1; transform: none; }
|
|
178
|
+
/* No-JS fallback: the visible state is only ever added by a JS trigger, so
|
|
179
|
+
without scripting .reveal-up content would be permanently hidden. Show it. */
|
|
180
|
+
@media (scripting: none) {
|
|
181
|
+
.reveal-up { opacity: 1; transform: none; }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ── stat counter (xAI-style scroll-triggered metrics strip) ───────────
|
|
185
|
+
No-fake-data policy (EVOLUTION.md §10 anti-pattern #2): stat-counter.js
|
|
186
|
+
only counts up to whatever data-target says — placeholders must be
|
|
187
|
+
labeled as such, real wiring is just setting data-target. ────────────── */
|
|
188
|
+
.stat-counter-row { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(1.5rem, 5vw, 3.5rem); }
|
|
189
|
+
.stat-counter { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; text-align: center; }
|
|
190
|
+
.stat-counter__value { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: clamp(1.7rem, 4vw, 2.5rem); font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
|
|
191
|
+
.stat-counter__label { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-mute); }
|
|
192
|
+
|
|
193
|
+
/* ── changelog band (Cursor-style dated release rows) ───────────────────
|
|
194
|
+
Mobile: stacked (date above title). Desktop: fixed date column + title
|
|
195
|
+
row, from 640px. Data shape: { date, version?, title, href, tag? }[] —
|
|
196
|
+
see site/README.md for the documented contract and rendering is left to
|
|
197
|
+
the consumer (vanilla or React), same as TrustStrip. ─────────────────── */
|
|
198
|
+
.changelog-band { display: flex; flex-direction: column; max-width: var(--wrap-wide); margin-inline: auto; }
|
|
199
|
+
.changelog-row { display: grid; grid-template-columns: 1fr; gap: 0.3rem; padding: 1.1rem 0; border-bottom: 1px solid var(--hair); }
|
|
200
|
+
.changelog-row:last-child { border-bottom: none; }
|
|
201
|
+
.changelog-row__date { font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink-mute); }
|
|
202
|
+
.changelog-row__title { font-size: 0.98rem; color: var(--ink); }
|
|
203
|
+
.changelog-row__title a { color: inherit; }
|
|
204
|
+
.changelog-row__title a:hover { color: var(--accent); }
|
|
205
|
+
.changelog-row__tag { display: inline-flex; align-items: center; margin-left: 0.6rem; padding: 0.15rem var(--space-2); border-radius: 0; border: 1px solid var(--hair-2); font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
|
|
206
|
+
.changelog-band__footer { margin-top: 1.2rem; text-align: center; }
|
|
207
|
+
.changelog-band__footer a { font-family: var(--font-mono); font-size: 0.85rem; color: var(--accent); }
|
|
208
|
+
@media (min-width: 640px) {
|
|
209
|
+
.changelog-row { grid-template-columns: 8rem 1fr; align-items: baseline; gap: var(--space-4); }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── code sample band (tabbed SDK / curl snippets) ──────────────────────
|
|
213
|
+
WAI-ARIA tabs pattern, keyboard-navigable (../code-sample-band.js).
|
|
214
|
+
Always dark, reusing the terminal block's --term-* tokens regardless of
|
|
215
|
+
page theme (matches .term). ───────────────────────────────────────────── */
|
|
216
|
+
.code-sample-band { background: var(--term-bg); border: 1px solid var(--term-hair); border-radius: 0; overflow: hidden; }
|
|
217
|
+
/* __bar holds the tablist + copy button as siblings — the copy button must NOT
|
|
218
|
+
live inside role="tablist" (a tablist owns only role="tab" children). */
|
|
219
|
+
.code-sample-band__bar { display: flex; align-items: center; gap: var(--space-1); padding: 0.5rem 0.6rem; background: var(--term-fill); border-bottom: 1px solid var(--term-hair); }
|
|
220
|
+
.code-sample-band__tabs { display: flex; align-items: center; gap: var(--space-1); }
|
|
221
|
+
.code-sample-band__tab { font-family: var(--font-mono); font-size: 0.8rem; color: var(--term-mute); background: transparent; border: 1px solid transparent; border-radius: 0; padding: 0.35rem 0.7rem; cursor: pointer; }
|
|
222
|
+
.code-sample-band__tab[aria-selected="true"] { color: var(--term-ink); background: var(--term-bg); border-color: var(--term-hair); }
|
|
223
|
+
.code-sample-band__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
224
|
+
.code-sample-band__copy { margin-left: auto; font-family: var(--font-mono); font-size: 0.7rem; color: var(--term-mute); background: var(--term-fill); border: 1px solid var(--term-hair); border-radius: 0; padding: 0.3rem 0.6rem; cursor: pointer; }
|
|
225
|
+
.code-sample-band__copy:hover { color: var(--term-ink); }
|
|
226
|
+
.code-sample-band__copy.is-ok { color: var(--up); }
|
|
227
|
+
.code-sample-band__panel { margin: 0; padding: 1.1rem 1.2rem; font-family: var(--font-mono); font-size: clamp(0.76rem, 1.2vw, 0.86rem); line-height: 1.7; color: var(--term-ink); white-space: pre-wrap; overflow-x: auto; }
|
|
228
|
+
|
|
229
|
+
/* ── capability card (xAI-style mini-UI preview + "Explore →") ──────────
|
|
230
|
+
Flat --surface panel, hairline border — no decorative eyebrow pills
|
|
231
|
+
(EVOLUTION.md §10 anti-pattern #3). Composes standalone (.capability-grid),
|
|
232
|
+
or drop a .capability-card inside a .bento__cell. The __preview slot takes
|
|
233
|
+
an <img>, a .product-frame child, or a .term snippet. ─────────────────── */
|
|
234
|
+
.capability-grid { display: grid; grid-template-columns: 1fr; gap: 1.25rem; width: 100%; max-width: var(--wrap-wide); margin-inline: auto; }
|
|
235
|
+
.capability-card { display: flex; flex-direction: column; gap: 0.6rem; background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-lg); padding: var(--space-4); transition: transform var(--duration-hover) var(--ease-glide), border-color var(--duration-hover) var(--ease-glide); }
|
|
236
|
+
.capability-card:hover { transform: translateY(-3px); border-color: var(--hair-2); }
|
|
237
|
+
.capability-card__preview { border-radius: var(--r-md); overflow: hidden; margin-bottom: 0.4rem; }
|
|
238
|
+
.capability-card__title { font-size: 1.1rem; font-weight: 600; letter-spacing: -0.02em; }
|
|
239
|
+
.capability-card__body { font-size: 0.9rem; color: var(--ink-soft); line-height: 1.5; }
|
|
240
|
+
.capability-card__cta { margin-top: auto; padding-top: var(--space-2); display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--accent); font-weight: 500; }
|
|
241
|
+
.capability-card__cta span[aria-hidden] { transition: transform var(--duration-hover) var(--ease-glide); }
|
|
242
|
+
.capability-card:hover .capability-card__cta span[aria-hidden] { transform: translateX(3px); }
|
|
243
|
+
@media (min-width: 768px) {
|
|
244
|
+
.capability-grid { grid-template-columns: repeat(2, 1fr); }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* ── horizontal scroll band (Cursor-style snap row) ─────────────────────
|
|
248
|
+
Overflow-x snap row for changelog cards, testimonial rows, and mobile
|
|
249
|
+
overflow bands. .h-scroll masks the edges; .h-scroll__track is the
|
|
250
|
+
focusable scroll container; .h-scroll__card are ~262px snap children
|
|
251
|
+
(Cursor changelog card width). CSS-only: keyboard access comes from the
|
|
252
|
+
track being a focusable (tabindex="0") native scroll container — arrow
|
|
253
|
+
keys scroll it — so no horizontal-scroll.js is needed (see site/README.md).
|
|
254
|
+
prefers-reduced-motion: reduce → cards stack vertically (no horizontal
|
|
255
|
+
scroll), so all content is reachable without horizontal gestures. ────── */
|
|
256
|
+
.h-scroll { --h-scroll-card-w: 16.375rem; position: relative; -webkit-mask-image: linear-gradient(to right, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent); mask-image: linear-gradient(to right, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent); }
|
|
257
|
+
.h-scroll__track { display: flex; gap: 1.25rem; overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding-inline: 1.25rem; padding: 0.25rem 1.25rem; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
|
|
258
|
+
.h-scroll__track::-webkit-scrollbar { display: none; }
|
|
259
|
+
.h-scroll__track:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-md); }
|
|
260
|
+
.h-scroll__card { flex: 0 0 auto; min-width: var(--h-scroll-card-w); max-width: var(--h-scroll-card-w); scroll-snap-align: start; display: flex; flex-direction: column; gap: var(--space-2); background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-lg); padding: var(--space-4); }
|
|
261
|
+
@media (prefers-reduced-motion: reduce) {
|
|
262
|
+
.h-scroll { -webkit-mask-image: none; mask-image: none; }
|
|
263
|
+
.h-scroll__track { flex-direction: column; overflow-x: visible; scroll-snap-type: none; padding-inline: 0; }
|
|
264
|
+
.h-scroll__card { min-width: 0; max-width: none; }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* ── closing CTA band (Graphite/Cursor pre-footer conversion) ───────────
|
|
268
|
+
Full-width centered band placed directly above the footer: one headline,
|
|
269
|
+
one primary CTA (kit `Button` in React), and an optional mono secondary
|
|
270
|
+
link. Compose
|
|
271
|
+
with .reveal-up (above) for a scroll-in enter — reduced-motion is already
|
|
272
|
+
honored there. Copy slots (see site/README.md for digithings + digiquant
|
|
273
|
+
variants; tone lives in COPY_GUIDE.md):
|
|
274
|
+
.closing-cta__title headline — literal, ≤ ~20ch reads best
|
|
275
|
+
.closing-cta__sub optional one-line support — ≤ ~48ch
|
|
276
|
+
.closing-cta__actions primary: a kit `Button` (React) or a link
|
|
277
|
+
.closing-cta__secondary optional: <a href>label <span aria-hidden>→</span></a>
|
|
278
|
+
───────────────────────────────────────────────────────────────────── */
|
|
279
|
+
.closing-cta { padding-block: var(--section-y); text-align: center; }
|
|
280
|
+
.closing-cta__inner { max-width: var(--wrap-wide); margin-inline: auto; display: flex; flex-direction: column; align-items: center; gap: 1.1rem; }
|
|
281
|
+
.closing-cta__title { font-size: clamp(1.8rem, 4vw, 2.75rem); font-weight: 600; letter-spacing: -0.03em; line-height: 1.1; max-width: 20ch; }
|
|
282
|
+
.closing-cta__sub { font-size: var(--type-body); color: var(--ink-soft); max-width: 48ch; line-height: 1.6; }
|
|
283
|
+
.closing-cta__actions { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--space-3); margin-top: 0.4rem; }
|
|
284
|
+
.closing-cta__secondary { display: inline-flex; align-items: center; gap: 0.4rem; font-family: var(--font-mono); font-size: 0.88rem; color: var(--ink-soft); }
|
|
285
|
+
.closing-cta__secondary:hover { color: var(--ink); }
|
|
286
|
+
.closing-cta__secondary span[aria-hidden] { transition: transform var(--duration-hover) var(--ease-glide); }
|
|
287
|
+
.closing-cta__secondary:hover span[aria-hidden] { transform: translateX(3px); }
|
|
288
|
+
|
|
289
|
+
/* ── FAQ accordion (Graphite/Cursor pricing-page Q&A) ───────────────────
|
|
290
|
+
Native <details>/<summary> — no JS. For an exclusive "one open at a time"
|
|
291
|
+
accordion, give every .faq__item the same name attribute
|
|
292
|
+
(<details class="faq__item" name="pricing-faq">) — a modern-browser native
|
|
293
|
+
exclusive-accordion feature; omit name and each item toggles independently.
|
|
294
|
+
The disclosure chevron rotates on [open]; its transition is dropped under
|
|
295
|
+
reduced motion (shared block below). Data-driven content shape (per site):
|
|
296
|
+
{ q, a }[] — see site/README.md.
|
|
297
|
+
───────────────────────────────────────────────────────────────────── */
|
|
298
|
+
.faq { --faq-w: 47.5rem; max-width: var(--faq-w); width: 100%; margin-inline: auto; }
|
|
299
|
+
.faq__item { border-bottom: 1px solid var(--hair); }
|
|
300
|
+
.faq__q { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 1.15rem 0; font-size: var(--type-body); font-weight: 500; color: var(--ink); cursor: pointer; list-style: none; }
|
|
301
|
+
.faq__q::-webkit-details-marker { display: none; }
|
|
302
|
+
.faq__q::after { content: ""; flex: 0 0 auto; width: 0.55rem; height: 0.55rem; border-right: 1.5px solid var(--ink-mute); border-bottom: 1.5px solid var(--ink-mute); transform: rotate(45deg); transition: transform var(--duration-hover) var(--ease-glide); }
|
|
303
|
+
.faq__item[open] .faq__q::after { transform: rotate(-135deg); }
|
|
304
|
+
.faq__q:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm); }
|
|
305
|
+
.faq__a { max-width: 60ch; margin: 0; padding: 0 0 1.25rem; color: var(--ink-soft); font-size: 0.95rem; line-height: 1.6; }
|
|
306
|
+
|
|
307
|
+
/* ── pricing matrix (open-core tiers + optional comparison table) ───────
|
|
308
|
+
3 honest open-core tiers — no invented usage caps or fake "limited AI
|
|
309
|
+
requests" (EVOLUTION.md §10, anti-pattern #2). Tier content shape (per
|
|
310
|
+
site): { name, price, cadence?, desc, features: string[], cta: {label, href},
|
|
311
|
+
featured?: bool }[] — see site/README.md. .pricing__tier--featured lifts one
|
|
312
|
+
card with an --accent ring. The optional .pricing-table gives a feature ×
|
|
313
|
+
tier comparison grid with ✓ (--up) / — (--ink-mute) cells.
|
|
314
|
+
───────────────────────────────────────────────────────────────────── */
|
|
315
|
+
.pricing { display: grid; grid-template-columns: 1fr; gap: 1.25rem; width: 100%; max-width: var(--wrap-wide); margin-inline: auto; }
|
|
316
|
+
.pricing__tier { display: flex; flex-direction: column; gap: 0.9rem; background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-lg); padding: 1.75rem; }
|
|
317
|
+
.pricing__tier--featured { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
318
|
+
.pricing__name { font-family: var(--font-mono); font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-mute); }
|
|
319
|
+
.pricing__price { font-size: clamp(1.6rem, 3vw, 2.1rem); font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; }
|
|
320
|
+
.pricing__price small { font-size: 0.9rem; font-weight: 400; color: var(--ink-mute); letter-spacing: 0; }
|
|
321
|
+
.pricing__desc { font-size: 0.9rem; color: var(--ink-soft); line-height: 1.55; }
|
|
322
|
+
.pricing__features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.55rem; font-size: 0.9rem; color: var(--ink-soft); }
|
|
323
|
+
.pricing__features li { display: flex; align-items: flex-start; gap: 0.55rem; }
|
|
324
|
+
.pricing__features li::before { content: "✓"; flex: 0 0 auto; color: var(--up); font-weight: 600; line-height: 1.4; }
|
|
325
|
+
.pricing__cta { margin-top: auto; }
|
|
326
|
+
@media (min-width: 768px) {
|
|
327
|
+
.pricing { grid-template-columns: repeat(3, 1fr); }
|
|
328
|
+
}
|
|
329
|
+
.pricing-table { width: 100%; max-width: var(--wrap-wide); margin-inline: auto; border-collapse: collapse; font-size: 0.9rem; }
|
|
330
|
+
.pricing-table th, .pricing-table td { padding: 0.8rem var(--space-3); text-align: left; border-bottom: 1px solid var(--hair); }
|
|
331
|
+
.pricing-table thead th { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-mute); font-weight: 500; }
|
|
332
|
+
.pricing-table th + th, .pricing-table td + td { text-align: center; }
|
|
333
|
+
.pricing-table tbody th { font-weight: 500; color: var(--ink); }
|
|
334
|
+
.pricing-table .is-yes { color: var(--up); font-weight: 600; }
|
|
335
|
+
.pricing-table .is-no { color: var(--ink-mute); }
|
|
336
|
+
|
|
337
|
+
/* ── hero feature picker (Graphite icon-tab row swapping a ProductFrame) ─
|
|
338
|
+
Icon tablist below the hero that swaps which ProductFrame preview shows
|
|
339
|
+
(e.g. dashboard / Strategies / Pipeline). WAI-ARIA tabs — wire with
|
|
340
|
+
hero-picker.js (roving tabindex, ArrowLeft/Right/Home/End). Static UI crops
|
|
341
|
+
only (no video). Each panel wraps a .product-frame, so it inherits the
|
|
342
|
+
CQ-scaled sizing and never clips at browser zoom. Icon buttons are ~53×53px
|
|
343
|
+
(Graphite reference); use aria-label per button since they are icon-only.
|
|
344
|
+
───────────────────────────────────────────────────────────────────── */
|
|
345
|
+
.hero-picker { --picker-tab: 3.3125rem; --picker-icon: 1.375rem; display: flex; flex-direction: column; align-items: center; gap: var(--space-4); width: 100%; }
|
|
346
|
+
.hero-picker__tabs { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; }
|
|
347
|
+
.hero-picker__tab { display: inline-flex; align-items: center; justify-content: center; width: var(--picker-tab); height: var(--picker-tab); padding: 0; background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-md); color: var(--ink-soft); cursor: pointer; transition: color var(--duration-hover) var(--ease-glide), border-color var(--duration-hover) var(--ease-glide); }
|
|
348
|
+
.hero-picker__tab:hover { color: var(--ink); border-color: var(--hair-2); }
|
|
349
|
+
.hero-picker__tab[aria-selected="true"] { color: var(--accent); border-color: var(--accent); }
|
|
350
|
+
.hero-picker__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
351
|
+
.hero-picker__tab svg { width: var(--picker-icon); height: var(--picker-icon); }
|
|
352
|
+
.hero-picker__panels { width: 100%; display: flex; justify-content: center; }
|
|
353
|
+
.hero-picker__panel[hidden] { display: none; }
|
|
354
|
+
|
|
355
|
+
/* ── announcement bar (Graphite full-width clickable notice above nav) ──
|
|
356
|
+
48px full-width bar, content-gated by announcement.js: it renders only when
|
|
357
|
+
handed enabled content, so the bar is OFF by default (empty / {enabled:false}
|
|
358
|
+
→ nothing). When href is set the whole bar is the link (stretched-link
|
|
359
|
+
overlay); the optional dismiss button sits above the overlay and can persist
|
|
360
|
+
a per-id dismissal in localStorage. No slide-in animation (reduced-motion
|
|
361
|
+
friendly by design). Content shape: { enabled, id?, text, href?, linkLabel? }
|
|
362
|
+
— see site/README.md and ../announcement-example.json.
|
|
363
|
+
───────────────────────────────────────────────────────────────────── */
|
|
364
|
+
.announcement { --announce-min-h: 3rem; --announce-pad-inline: 2.75rem; --announce-dismiss: 1.75rem; position: relative; display: flex; align-items: center; justify-content: center; gap: 0.6rem; min-height: var(--announce-min-h); padding: var(--space-2) var(--announce-pad-inline); background: var(--surface); border-bottom: 1px solid var(--hair); color: var(--ink-soft); font-size: 0.88rem; text-align: center; }
|
|
365
|
+
.announcement[hidden] { display: none; }
|
|
366
|
+
.announcement__text { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-2); }
|
|
367
|
+
.announcement__link { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--accent); font-weight: 500; }
|
|
368
|
+
.announcement__link span[aria-hidden] { transition: transform var(--duration-hover) var(--ease-glide); }
|
|
369
|
+
.announcement--link .announcement__link::after { content: ""; position: absolute; inset: 0; }
|
|
370
|
+
.announcement--link:hover .announcement__link span[aria-hidden] { transform: translateX(3px); }
|
|
371
|
+
.announcement__dismiss { position: absolute; right: 0.6rem; top: 50%; transform: translateY(-50%); z-index: 1; display: inline-flex; align-items: center; justify-content: center; width: var(--announce-dismiss); height: var(--announce-dismiss); padding: 0; border: 0; border-radius: var(--r-sm); background: transparent; color: var(--ink-mute); cursor: pointer; }
|
|
372
|
+
.announcement__dismiss:hover { color: var(--ink); }
|
|
373
|
+
.announcement__dismiss:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
374
|
+
|
|
375
|
+
/* ── case-study card (Graphite {org} × product social proof) ────────────
|
|
376
|
+
Flat --surface card (same sizing family as .bento__cell / .capability-card):
|
|
377
|
+
a mono "{org} × digithings" label, a quote block, and an attribution row with
|
|
378
|
+
an optional logo slot. Drop into a .bento, .capability-grid, or an .h-scroll
|
|
379
|
+
track for a horizontal row. CONTENT-GATED (anti-pattern #2): ship dormant —
|
|
380
|
+
no fabricated testimonials. Real attribution required in production (name,
|
|
381
|
+
title, company, or a public GitHub/OSS reference). Add .case-study--example
|
|
382
|
+
for a watermarked placeholder in demos/docs only. Content shape (per site):
|
|
383
|
+
{ org, quote, name, title?, href?, logo? } — see site/README.md.
|
|
384
|
+
───────────────────────────────────────────────────────────────────── */
|
|
385
|
+
.case-study { --case-logo-h: 1.5rem; --case-logo-max-w: 7.5rem; position: relative; display: flex; flex-direction: column; gap: var(--space-3); background: var(--surface); border: 1px solid var(--hair); border-radius: var(--r-lg); padding: 1.75rem; }
|
|
386
|
+
.case-study__label { font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-mute); }
|
|
387
|
+
.case-study__quote { margin: 0; font-size: 1.05rem; line-height: 1.55; color: var(--ink); }
|
|
388
|
+
.case-study__attr { display: flex; align-items: center; gap: 0.6rem; margin-top: auto; font-size: 0.85rem; color: var(--ink-soft); }
|
|
389
|
+
.case-study__attr strong { color: var(--ink); font-weight: 600; }
|
|
390
|
+
.case-study__logo { height: var(--case-logo-h); width: auto; max-width: var(--case-logo-max-w); filter: grayscale(1); opacity: 0.8; }
|
|
391
|
+
.case-study--example::after { content: "EXAMPLE"; position: absolute; top: 0.85rem; right: 0.85rem; font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.12em; color: var(--ink-mute); border: 1px solid var(--hair-2); border-radius: var(--r-sm); padding: 0.15rem 0.4rem; }
|
|
392
|
+
|
|
393
|
+
/* ── principles ─────────────────────────────────────────────────────── */
|
|
394
|
+
.principles { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); }
|
|
395
|
+
.principle { padding-top: 1.4rem; border-top: 1px solid var(--hair-2); }
|
|
396
|
+
.principle-num { font-family: var(--font-mono); font-size: 0.78rem; color: var(--accent); }
|
|
397
|
+
.principle h3 { font-size: 1.1rem; font-weight: 600; letter-spacing: -0.02em; margin: 0.7rem 0 0.5rem; }
|
|
398
|
+
.principle p { font-size: 0.9rem; color: var(--ink-soft); }
|
|
399
|
+
|
|
400
|
+
/* ── footer ─────────────────────────────────────────────────────────── */
|
|
401
|
+
.footer { border-top: 1px solid var(--hair); padding: 2.5rem 0; }
|
|
402
|
+
.footer-inner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
|
|
403
|
+
.footer-links { display: flex; gap: var(--space-4); flex-wrap: wrap; }
|
|
404
|
+
.footer-links a { font-size: 0.9rem; color: var(--ink-soft); transition: color 0.18s var(--ease); }
|
|
405
|
+
.footer-links a:hover { color: var(--ink); }
|
|
406
|
+
.footer-meta { font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink-mute); }
|
|
407
|
+
|
|
408
|
+
/* ── colophon — the page's last word (canon §08) ────────────────────────
|
|
409
|
+
The module's name at giant scale, 1px hairline outline by default.
|
|
410
|
+
Scroll-scrubbed with zero JS: CSS scroll-driven animation behind
|
|
411
|
+
@supports; reduced-motion / no-support renders the name standing. The
|
|
412
|
+
<b> suffix reads var(--accent) — livery on module sites, ink on the
|
|
413
|
+
umbrella (which collapses --accent to ink). An opt-in glow sweep
|
|
414
|
+
(Colophon `sweep` prop, .colo-sweep below) is the one sanctioned
|
|
415
|
+
exception to outline-only. */
|
|
416
|
+
/* overflow-x: clip, NOT overflow: hidden — the clip has to come from a box
|
|
417
|
+
that is not a scroll container. `hidden` makes .colophon the nearest
|
|
418
|
+
scrollport for its own child .colo-word, so the child's `view()` timeline
|
|
419
|
+
below resolves against a box that never scrolls: it saturates (rise stuck
|
|
420
|
+
at 1, fill frozen) and the scroll-scrubbed rise silently never runs — the
|
|
421
|
+
wordmark just sits there, fully risen. `clip` clips the nowrap lockup
|
|
422
|
+
identically (and, unlike `hidden`, leaves overflow-y: visible without
|
|
423
|
+
computing to `auto`), so the view() timeline binds to the viewport again. */
|
|
424
|
+
.colophon { overflow-x: clip; overflow-y: visible; padding: 5rem 0 0; user-select: none; pointer-events: none; }
|
|
425
|
+
/* fallback stroke first: without color-mix() support the refined declaration
|
|
426
|
+
drops, and transparent fill + no stroke would render nothing.
|
|
427
|
+
position: relative anchors the optional .colo-sweep overlay. */
|
|
428
|
+
.colo-word { position: relative; display: block; font-family: var(--font-mono); font-weight: 500; font-size: clamp(3rem, 15.5vw, 15rem); line-height: 0.82; letter-spacing: -0.05em; white-space: nowrap; text-align: center; color: transparent; -webkit-text-stroke: 1px var(--ink); -webkit-text-stroke: 1px color-mix(in srgb, var(--ink) 30%, transparent); }
|
|
429
|
+
.colo-word b { font-weight: 500; color: transparent; -webkit-text-stroke: 1px var(--accent); -webkit-text-stroke: 1px color-mix(in srgb, var(--accent) 70%, transparent); }
|
|
430
|
+
/* Opt-in glow sweep (Colophon `sweep` prop) — a light passing left-to-right
|
|
431
|
+
across the sign, ported from the reference footer specimen. A duplicate
|
|
432
|
+
overlay span carries an accent gradient clipped to its own glyphs; the
|
|
433
|
+
component (Motion) scrubs background-position from off-left to off-right
|
|
434
|
+
against the colophon's scroll progress. Parked off-screen by default
|
|
435
|
+
(132% ≈ band clear of the left edge), so no-JS and reduced-motion both
|
|
436
|
+
show just the static outlined wordmark, no glow. */
|
|
437
|
+
.colo-sweep { display: block; position: absolute; inset: 0; -webkit-text-stroke-width: 0; color: transparent; background: linear-gradient(100deg, transparent 38%, color-mix(in srgb, var(--accent) 72%, transparent) 50%, transparent 62%); background-size: 250% 100%; background-repeat: no-repeat; background-position: 132% 0; -webkit-background-clip: text; background-clip: text; }
|
|
438
|
+
@supports (animation-timeline: view()) {
|
|
439
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
440
|
+
/* entry-based ranges: the colophon sits at the document's end, so
|
|
441
|
+
cover-based ranges can never complete — entry 100% (fully visible)
|
|
442
|
+
is reachable at max scroll. */
|
|
443
|
+
.colo-word { opacity: 0; transform: translateY(34%); animation: colo-rise linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
|
|
444
|
+
.colo-word b { animation: colo-fill linear both; animation-timeline: view(); animation-range: entry 55% entry 100%; }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
@keyframes colo-rise { to { opacity: 1; transform: none; } }
|
|
448
|
+
@keyframes colo-fill { to { color: color-mix(in srgb, var(--accent) 16%, transparent); } }
|
|
449
|
+
|
|
450
|
+
/* ── responsive ─────────────────────────────────────────────────────── */
|
|
451
|
+
@media (max-width: 940px) {
|
|
452
|
+
.principles { grid-template-columns: repeat(2, 1fr); }
|
|
453
|
+
}
|
|
454
|
+
@media (max-width: 560px) {
|
|
455
|
+
.principles { grid-template-columns: 1fr; }
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* ── no-JS content-first (law 06) ───────────────────────────────────────
|
|
459
|
+
SSR ships html.no-js (removed pre-paint by an inline script in each app
|
|
460
|
+
layout). Motion primitives SSR their hidden initial styles inline, and
|
|
461
|
+
the [data-motion] children of a Stagger inherit variants — neutralize
|
|
462
|
+
all of it when scripts never run. */
|
|
463
|
+
html.no-js [data-motion],
|
|
464
|
+
html.no-js [data-motion] * {
|
|
465
|
+
opacity: 1 !important;
|
|
466
|
+
transform: none !important;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
@media (prefers-reduced-motion: reduce) {
|
|
470
|
+
.glow { animation: none; }
|
|
471
|
+
.term-cursor { animation: none; }
|
|
472
|
+
.reveal-up { opacity: 1 !important; transform: none !important; transition: none !important; }
|
|
473
|
+
.faq__q::after { transition: none; }
|
|
474
|
+
* { scroll-behavior: auto !important; }
|
|
475
|
+
}
|