@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/README.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
# `@digithings-ai/design/site` — shared CSS foundation
|
|
2
|
+
|
|
3
|
+
`site.css` is imported directly by `apps/digithings-web/app/globals.css`
|
|
4
|
+
and `apps/digiquant-web/app/globals.css` — the live Next.js marketing
|
|
5
|
+
sites (digithings.ai, digiquant.io). It supplies the primitives those apps'
|
|
6
|
+
React components still reach for by class name: `.wrap`, `.brand*`, buttons,
|
|
7
|
+
`.kicker`/`.prompt`, the standalone `.hero-title`, the terminal block
|
|
8
|
+
(`.term*`/`.tl-*`, consumed by `packages/ui/src/components/Terminal.tsx`),
|
|
9
|
+
sections, **ProductFrame**, **BentoGrid**, **TrustStrip**, **reveal-up**,
|
|
10
|
+
**StatCounter**, **ChangelogBand**, **CodeSampleBand**, **CapabilityCard**,
|
|
11
|
+
**HorizontalScrollBand**, **ClosingCtaBand**, **FaqAccordion**, **PricingMatrix**,
|
|
12
|
+
**HeroFeaturePicker**, **AnnouncementBar**, **CaseStudyCard**, `.principles`, and `.footer*`. Terminal-CLI /
|
|
13
|
+
utilitarian aesthetic, light **and** dark, reduced-motion safe. Consumes the
|
|
14
|
+
`[data-theme]` semantic tokens in [`../tokens.css`](../tokens.css).
|
|
15
|
+
|
|
16
|
+
Nav shell, hero layout, cards, pills/stage, the connected graph, and scroll
|
|
17
|
+
reveal are React components in `@digithings-ai/ui` (`chrome.tsx`, `DigiNav.tsx`
|
|
18
|
+
/`DqNav.tsx`, `graph.tsx`, Framer-Motion `Reveal`) — the vanilla-JS/CSS
|
|
19
|
+
equivalents that used to live here (`theme.js`, `ui.js`, `reveal.js`,
|
|
20
|
+
`terminal.js`, `graph.js`, plus their `.site-nav`/`.hero-grid`/`.card`/
|
|
21
|
+
`.pills`/`.stage`/`.gnode`/`.reveal` selectors) were removed in #1240 once an
|
|
22
|
+
import-graph audit confirmed neither live app referenced them.
|
|
23
|
+
|
|
24
|
+
## Theme contract
|
|
25
|
+
|
|
26
|
+
Pages opt in by setting `data-theme="light|dark"` on `<html>` **before paint**
|
|
27
|
+
(a pre-paint snippet in `<head>` avoids a flash), then loading `site.css`:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<html data-theme="dark">
|
|
31
|
+
<head>
|
|
32
|
+
<link rel="stylesheet" href="../design/tokens.css">
|
|
33
|
+
<link rel="stylesheet" href="../design/site/site.css">
|
|
34
|
+
<script>try{var s=localStorage.getItem('dt-theme');
|
|
35
|
+
document.documentElement.setAttribute('data-theme',
|
|
36
|
+
s||(matchMedia('(prefers-color-scheme: light)').matches?'light':'dark'))}catch(e){}</script>
|
|
37
|
+
</head>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`localStorage('dt-theme')` is the shared key — the digiquant dashboard mirrors it
|
|
41
|
+
on the same origin, so a chosen theme follows the user across surfaces. In the
|
|
42
|
+
Next.js apps this is handled by `ThemeProvider.tsx`/`ThemeToggle` in
|
|
43
|
+
`@digithings-ai/ui`, which reads/writes the same key.
|
|
44
|
+
|
|
45
|
+
## `ProductFrame` (CSS-only, EVOLUTION.md Phase B)
|
|
46
|
+
|
|
47
|
+
CQ-scaled ~800px UI embed for marketing pages — Graphite artboard / Cursor
|
|
48
|
+
product-screenshot pattern. No JS: markup two nested elements and let the
|
|
49
|
+
container query handle scaling.
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<div class="product-frame">
|
|
53
|
+
<div class="product-frame__surface">
|
|
54
|
+
<!-- screenshot <img>, terminal snippet, or arbitrary UI markup -->
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<p class="product-frame__caption">Fig. 1 — caption text</p>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Class | Role |
|
|
61
|
+
|-------|------|
|
|
62
|
+
| `.product-frame` | Sizing wrapper — `max-width: var(--product-frame-w)` (800px), establishes a `container-type: inline-size` query container. |
|
|
63
|
+
| `.product-frame__surface` | Flat panel — `--surface` background, 1px `--hair` border, `--r-lg` radius. Font size scales in `cqi` (container-query inline units), clamped, so content shrinks with the *frame's* width rather than the viewport. |
|
|
64
|
+
| `.product-frame__caption` | Optional mono caption below the frame. |
|
|
65
|
+
|
|
66
|
+
**Atmosphere rule:** no mesh/glow/grain inside `.product-frame__surface` —
|
|
67
|
+
those effects belong to the page background around the frame, never on
|
|
68
|
+
the simulated UI itself (EVOLUTION.md §7, "atmospheric outside, surgical
|
|
69
|
+
inside").
|
|
70
|
+
|
|
71
|
+
Works unscoped in both `[data-theme="light"]` and `[data-theme="dark"]`. The React
|
|
72
|
+
wrapper now ships in `@digithings-ai/ui` (exported as `ProductFrame`); the CSS classes
|
|
73
|
+
remain usable directly from any JSX/TSX.
|
|
74
|
+
|
|
75
|
+
## `BentoGrid` (CSS-only, EVOLUTION.md Phase B)
|
|
76
|
+
|
|
77
|
+
Cursor-style linked feature cells. Mobile-first: single column, 2×2 from
|
|
78
|
+
768px. Flat `--surface` panels — no glass morphism (anti-pattern #8).
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<div class="bento">
|
|
82
|
+
<a class="bento__cell" href="/modules/digigraph">
|
|
83
|
+
<div class="bento__kicker">// orchestration</div>
|
|
84
|
+
<div class="bento__title">Supervisor graph</div>
|
|
85
|
+
<p class="bento__body">One line of what this module does.</p>
|
|
86
|
+
<span class="bento__cta">Learn more <span aria-hidden="true">→</span></span>
|
|
87
|
+
</a>
|
|
88
|
+
<div class="bento__cell bento__cell--static bento__cell--span-2">
|
|
89
|
+
<!-- non-linked, wide cell -->
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Class | Role |
|
|
95
|
+
|-------|------|
|
|
96
|
+
| `.bento` | Grid container — 1 column below 768px, `repeat(2, 1fr)` at `min-width: 768px`; `max-width: var(--wrap-wide)`. |
|
|
97
|
+
| `.bento__cell` | Cell surface — `<a>` for a linked cell (hover lift via `--duration-hover`/`--ease-glide`) or any element with `.bento__cell--static` for a non-interactive cell (no hover, no cursor pointer). |
|
|
98
|
+
| `.bento__cell--span-2` | Optional wide cell spanning both columns. |
|
|
99
|
+
| `.bento__kicker` / `.bento__title` / `.bento__body` | Mono eyebrow, heading, body copy. |
|
|
100
|
+
| `.bento__thumb` | Optional image/thumbnail slot — rounds to `--r-md`, clips overflow. |
|
|
101
|
+
| `.bento__cta` | Arrow-suffix link text (`Learn more →`); the `span[aria-hidden]` arrow translates on `.bento__cell:hover`, matching the kit Button hover idiom. |
|
|
102
|
+
|
|
103
|
+
Works unscoped in both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
104
|
+
|
|
105
|
+
## `TrustStrip` (CSS-only, EVOLUTION.md Phase B)
|
|
106
|
+
|
|
107
|
+
Cursor-style hero trust line — a muted row of proof items, text or logos.
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<div class="trust-strip">
|
|
111
|
+
<span class="trust-strip__item">open core · self-hosted</span>
|
|
112
|
+
<img class="trust-strip__item" src="/logos/partner.svg" alt="Partner" />
|
|
113
|
+
</div>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Class | Role |
|
|
117
|
+
|-------|------|
|
|
118
|
+
| `.trust-strip` | Centered, wrapping flex row. |
|
|
119
|
+
| `.trust-strip__item` | Text item (mono, `--ink-mute`) or `<img>` logo (28px height, grayscale + reduced opacity for visual parity across mixed-brand logos). |
|
|
120
|
+
|
|
121
|
+
### `--logos` integration variant (EVOLUTION.md Phase E, #1229)
|
|
122
|
+
|
|
123
|
+
`.trust-strip--logos` turns the strip into an x.ai-style **integration mark row**
|
|
124
|
+
for the real stack (NautilusTrader · LangGraph · LiteLLM · Polars). Marks are
|
|
125
|
+
muted/grayscale by default and lift to full color on hover; it composes on top of
|
|
126
|
+
the base `.trust-strip` and coexists with the text friction variant. **Real
|
|
127
|
+
integrations only — no stock/generic or fabricated customer logos.**
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<div class="trust-strip trust-strip--logos">
|
|
131
|
+
<span class="trust-strip__item"><img src="/logos/nautilustrader.svg" alt="NautilusTrader" /></span>
|
|
132
|
+
<span class="trust-strip__item"><img src="/logos/langgraph.svg" alt="LangGraph" /></span>
|
|
133
|
+
<!-- decorative-only marks use alt="" -->
|
|
134
|
+
</div>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Wrap each logo as `<span class="trust-strip__item"><img alt="…"></span>` so the
|
|
138
|
+
base `.trust-strip__item img` sizing (28px) applies; give every logo real `alt`
|
|
139
|
+
text (or `alt=""` when purely decorative). The smoke page demos the layout with
|
|
140
|
+
the four integration **names as text wordmarks** — production drops in the real
|
|
141
|
+
logo assets. Contrast holds in both themes (grayscale marks over `--surface`).
|
|
142
|
+
|
|
143
|
+
## `reveal-up` (CSS-only utility, EVOLUTION.md Phase B)
|
|
144
|
+
|
|
145
|
+
Opacity + translate enter animation. **This is not the old `site/reveal.js`**
|
|
146
|
+
(removed as dead code in #1240) — `.reveal-up` only owns the two visual
|
|
147
|
+
states (`opacity`/`transform`/`transition`); something external toggles the
|
|
148
|
+
visible class:
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<div class="reveal-up">Revealed on scroll or on mount.</div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// Option A — vanilla pages: packages/design/scroll-trigger.js
|
|
156
|
+
import { initScrollTrigger } from '../scroll-trigger.js';
|
|
157
|
+
initScrollTrigger({ activateSelector: '.reveal-up', activationLineRatio: 0.8 });
|
|
158
|
+
// toggles .active on .reveal-up elements as they cross the scroll line
|
|
159
|
+
|
|
160
|
+
// Option B — React: packages/ui/src/motion/primitives.tsx's <Reveal>
|
|
161
|
+
// applies its own visibility state via className; pass className="reveal-up"
|
|
162
|
+
// and toggle `is-visible` there instead of `.active`, or wire Reveal to add
|
|
163
|
+
// `.active` for a single shared contract — either satisfies the CSS below.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| Class | Role |
|
|
167
|
+
|-------|------|
|
|
168
|
+
| `.reveal-up` | Initial state — `opacity: 0`, `translateY(1rem)`. |
|
|
169
|
+
| `.reveal-up.is-visible` / `.reveal-up.active` | Visible state — both class names are wired to the same rule so either trigger mechanism (scroll-trigger's `.active` or a `.is-visible` convention) works without duplicating CSS. |
|
|
170
|
+
|
|
171
|
+
`prefers-reduced-motion: reduce` shows the element immediately (no transition), consolidated in site.css's shared reduced-motion block.
|
|
172
|
+
|
|
173
|
+
## `StatCounter` (CSS + `stat-counter.js`, EVOLUTION.md Phase B)
|
|
174
|
+
|
|
175
|
+
xAI-style scroll-triggered metrics strip. **No-fake-data policy** (EVOLUTION.md
|
|
176
|
+
§10, anti-pattern #2): placeholder demo values must be clearly labeled as such;
|
|
177
|
+
real wiring is just setting `data-target` from real numbers, nothing invented.
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<div class="stat-counter-row">
|
|
181
|
+
<div class="stat-counter" data-target="128" data-suffix="ms">
|
|
182
|
+
<span class="stat-counter__value">0</span>
|
|
183
|
+
<span class="stat-counter__label">p50 latency</span>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { initStatCounter } from '../stat-counter.js';
|
|
190
|
+
initStatCounter(); // defaults: selector '.stat-counter', 1200ms ease-out-cubic
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Class / attribute | Role |
|
|
194
|
+
|-------|------|
|
|
195
|
+
| `.stat-counter-row` | Centered, wrapping flex row of metrics. |
|
|
196
|
+
| `.stat-counter` | One metric — JS-observed root. `data-target` (required), `data-prefix`/`data-suffix`/`data-decimals` (optional formatting). |
|
|
197
|
+
| `.stat-counter__value` | The animated number — `tabular-nums`, Geist Mono. |
|
|
198
|
+
| `.stat-counter__label` | Mono, uppercase, tracked label. |
|
|
199
|
+
|
|
200
|
+
`stat-counter.js`'s `initStatCounter()` uses `IntersectionObserver` to count
|
|
201
|
+
each `.stat-counter__value` from 0 to `data-target` once, the first time it
|
|
202
|
+
scrolls into view (a one-shot count, distinct from `scroll-trigger.js`'s
|
|
203
|
+
continuous `--scroll` progress model — different job, separate module).
|
|
204
|
+
`prefers-reduced-motion: reduce` (or no `IntersectionObserver` support) shows
|
|
205
|
+
the final value immediately, no animation loop.
|
|
206
|
+
|
|
207
|
+
## `ChangelogBand` (CSS-only + data shape, EVOLUTION.md Phase B)
|
|
208
|
+
|
|
209
|
+
Cursor-style dated release rows. Mobile: stacked. Desktop (`min-width: 640px`):
|
|
210
|
+
fixed date column + title row. CSS-only — rendering the data shape into
|
|
211
|
+
markup is left to the consumer (vanilla template string or React `.map()`),
|
|
212
|
+
same division of responsibility as TrustStrip.
|
|
213
|
+
|
|
214
|
+
**Data shape** (`{ date, version?, title, href, tag? }[]`) — see
|
|
215
|
+
[`../changelog-example.json`](../changelog-example.json) for a worked
|
|
216
|
+
example and [`../releases.json`](../releases.json) for the live tagged
|
|
217
|
+
digichat / digiskills feed the marketing `/changelog` pages consume.
|
|
218
|
+
The vanilla smoke page that used to render this band was removed.
|
|
219
|
+
This primitive doesn't fetch or own data, only the markup/CSS contract.
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<div class="changelog-band">
|
|
223
|
+
<div class="changelog-row">
|
|
224
|
+
<div class="changelog-row__date">2026-06-29 · v7.2</div>
|
|
225
|
+
<div class="changelog-row__title">
|
|
226
|
+
<a href="/releases/v7.2">Shared design primitives shipped</a>
|
|
227
|
+
<span class="changelog-row__tag">release</span>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
<p class="changelog-band__footer"><a href="/releases">View all releases →</a></p>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
| Class | Role |
|
|
235
|
+
|-------|------|
|
|
236
|
+
| `.changelog-band` | Column container, `max-width: var(--wrap-wide)`. |
|
|
237
|
+
| `.changelog-row` | One entry — 1 column below 640px, `8rem 1fr` grid (date / title) at `min-width: 640px`. Hairline divider between rows. |
|
|
238
|
+
| `.changelog-row__date` | Mono date (+ optional version). |
|
|
239
|
+
| `.changelog-row__title` | Title, linked; hover tints `--accent`. |
|
|
240
|
+
| `.changelog-row__tag` | Optional hairline tag (`release`, `fix`, etc.) — zero radius, not a pill. |
|
|
241
|
+
| `.changelog-band__footer` | "View all releases →" link pattern. |
|
|
242
|
+
|
|
243
|
+
## `CodeSampleBand` (CSS + `code-sample-band.js`, EVOLUTION.md Phase B)
|
|
244
|
+
|
|
245
|
+
xAI/Cursor-style tabbed SDK snippets (`curl` / Python / TypeScript) with
|
|
246
|
+
copy-to-clipboard. Always dark, reusing the terminal block's `--term-*`
|
|
247
|
+
tokens regardless of page theme. Follows the WAI-ARIA "Tabs with Automatic
|
|
248
|
+
Activation" pattern — keyboard-navigable, `aria-selected`, focus ring.
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<div class="code-sample-band">
|
|
252
|
+
<div class="code-sample-band__bar">
|
|
253
|
+
<div class="code-sample-band__tabs" role="tablist" aria-label="Install command">
|
|
254
|
+
<button class="code-sample-band__tab" role="tab" aria-selected="true" aria-controls="panel-curl" id="tab-curl">curl</button>
|
|
255
|
+
<button class="code-sample-band__tab" role="tab" aria-selected="false" aria-controls="panel-py" id="tab-py" tabindex="-1">Python</button>
|
|
256
|
+
</div>
|
|
257
|
+
<button class="code-sample-band__copy" type="button" aria-label="Copy code">copy</button>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="code-sample-band__panels">
|
|
260
|
+
<pre class="code-sample-band__panel" role="tabpanel" id="panel-curl" aria-labelledby="tab-curl"><code>curl ...</code></pre>
|
|
261
|
+
<pre class="code-sample-band__panel" role="tabpanel" id="panel-py" aria-labelledby="tab-py" hidden><code>import digithings ...</code></pre>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The copy button sits **outside** `role="tablist"` (inside `.code-sample-band__bar`
|
|
267
|
+
alongside it) — a tablist owns only `role="tab"` children, so a non-tab button
|
|
268
|
+
inside it is an ARIA structure violation.
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
import { initCodeSampleBand } from '../code-sample-band.js';
|
|
272
|
+
initCodeSampleBand(); // wires every .code-sample-band on the page
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
| Class | Role |
|
|
276
|
+
|-------|------|
|
|
277
|
+
| `.code-sample-band` | Dark panel, `--term-bg`/`--term-hair`. |
|
|
278
|
+
| `.code-sample-band__bar` | Flex header row holding the tablist + the copy button as siblings (padding/fill/border). |
|
|
279
|
+
| `.code-sample-band__tabs` | `role="tablist"` row — owns only `role="tab"` children. |
|
|
280
|
+
| `.code-sample-band__tab` | `role="tab"` button; `[aria-selected="true"]` gets the active surface treatment; `:focus-visible` ring. |
|
|
281
|
+
| `.code-sample-band__copy` | Copies the active panel's `textContent` via `navigator.clipboard`; `.is-ok` after a successful copy (2s, matches `.tl-ok`'s `--up` color). |
|
|
282
|
+
| `.code-sample-band__panel` | `role="tabpanel"` `<pre><code>` block; Geist Mono, wraps long lines. |
|
|
283
|
+
|
|
284
|
+
`code-sample-band.js`'s `initCodeSampleBand()` wires click + keyboard
|
|
285
|
+
(`ArrowLeft`/`ArrowRight`/`Home`/`End`, roving `tabindex`) on every
|
|
286
|
+
`[role="tab"]` inside the matched root, and toggles the matching
|
|
287
|
+
`[role="tabpanel"]`'s `hidden` attribute — no network calls, pure DOM.
|
|
288
|
+
|
|
289
|
+
## `CapabilityCard` (CSS-only, EVOLUTION.md Phase B)
|
|
290
|
+
|
|
291
|
+
xAI-style mini-UI preview + "Explore →" link. Flat `--surface` panel, hairline
|
|
292
|
+
border — no decorative eyebrow pills without an action (anti-pattern #3).
|
|
293
|
+
Composes standalone in a `.capability-grid`, or drop a single `.capability-card`
|
|
294
|
+
inside a `.bento__cell`.
|
|
295
|
+
|
|
296
|
+
```html
|
|
297
|
+
<div class="capability-grid">
|
|
298
|
+
<div class="capability-card">
|
|
299
|
+
<div class="capability-card__preview">
|
|
300
|
+
<!-- <img>, a .product-frame child, or a .term snippet -->
|
|
301
|
+
</div>
|
|
302
|
+
<div class="capability-card__title">Orchestration</div>
|
|
303
|
+
<p class="capability-card__body">One line of what this capability does.</p>
|
|
304
|
+
<a class="capability-card__cta" href="/modules/digigraph">Explore <span aria-hidden="true">→</span></a>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
| Class | Role |
|
|
310
|
+
|-------|------|
|
|
311
|
+
| `.capability-grid` | Grid container — 1 column below 768px, `repeat(2, 1fr)` at `min-width: 768px`; `max-width: var(--wrap-wide)`. |
|
|
312
|
+
| `.capability-card` | Flat card surface with hover lift (`--duration-hover`/`--ease-glide`). |
|
|
313
|
+
| `.capability-card__preview` | Optional media slot — takes an `<img>`, a `.product-frame` child, or a `.term` snippet; rounds to `--r-md`, clips overflow. |
|
|
314
|
+
| `.capability-card__title` / `.capability-card__body` | Heading + body copy. |
|
|
315
|
+
| `.capability-card__cta` | `Explore →` arrow link; the `span[aria-hidden]` arrow translates on card hover, matching the kit Button/`.bento__cta`. |
|
|
316
|
+
|
|
317
|
+
Works unscoped in both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
318
|
+
|
|
319
|
+
## `HorizontalScrollBand` (CSS-only, EVOLUTION.md Phase E)
|
|
320
|
+
|
|
321
|
+
Cursor-style horizontal snap row for changelog cards, testimonial rows, and
|
|
322
|
+
mobile overflow bands. CSS-only — no `horizontal-scroll.js` was needed:
|
|
323
|
+
keyboard access comes from making `.h-scroll__track` a focusable
|
|
324
|
+
(`tabindex="0"`) native scroll container, so arrow keys scroll it; wrap it in
|
|
325
|
+
`role="group"` with an `aria-label` so the row is announced. `prefers-reduced-motion:
|
|
326
|
+
reduce` collapses the row to a vertical stack, so every card stays reachable
|
|
327
|
+
without a horizontal gesture.
|
|
328
|
+
|
|
329
|
+
```html
|
|
330
|
+
<div class="h-scroll">
|
|
331
|
+
<div class="h-scroll__track" tabindex="0" role="group" aria-label="Recent releases">
|
|
332
|
+
<article class="h-scroll__card"><!-- card content --></article>
|
|
333
|
+
<article class="h-scroll__card"><!-- card content --></article>
|
|
334
|
+
<article class="h-scroll__card"><!-- card content --></article>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
| Class | Role |
|
|
340
|
+
|-------|------|
|
|
341
|
+
| `.h-scroll` | Positioning wrapper; applies an edge-fade `mask-image` on both inline edges (removed under reduced motion). |
|
|
342
|
+
| `.h-scroll__track` | Focusable (`tabindex="0"`) flex scroll container — `scroll-snap-type: x mandatory`, hidden scrollbar, `:focus-visible` ring. Under `prefers-reduced-motion: reduce` it becomes a vertical column with no snap. |
|
|
343
|
+
| `.h-scroll__card` | Snap child — fixed `262px` (Cursor changelog card width), flat `--surface` panel. Full-width when stacked under reduced motion. |
|
|
344
|
+
|
|
345
|
+
CSS-only, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
346
|
+
Content wiring (real changelog/testimonial data) is out of scope here — this
|
|
347
|
+
primitive owns only the scroll/snap/masking contract, same division as
|
|
348
|
+
TrustStrip and ChangelogBand.
|
|
349
|
+
|
|
350
|
+
## `ClosingCtaBand` (CSS-only, EVOLUTION.md Phase E)
|
|
351
|
+
|
|
352
|
+
Graphite/Cursor **pre-footer conversion band** — a full-width centered section
|
|
353
|
+
placed directly above the footer: one literal headline, one primary kit
|
|
354
|
+
`Button` (React), and an optional mono secondary link. Compose with `.reveal-up` (above) for a
|
|
355
|
+
scroll-in enter; reduced motion is already honored by the shared block. Copy
|
|
356
|
+
tone follows [`COPY_GUIDE.md`](../COPY_GUIDE.md)
|
|
357
|
+
(literal verbs, no hype). Landing-page wiring is [#1227](https://github.com/digithings-ai/digithings/issues/1227) — this primitive owns only the markup/CSS contract.
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
<section class="closing-cta reveal-up">
|
|
361
|
+
<div class="closing-cta__inner">
|
|
362
|
+
<h2 class="closing-cta__title">Build your agent stack in the open.</h2>
|
|
363
|
+
<p class="closing-cta__sub">Open-core orchestration, quant, RAG, and chat.</p>
|
|
364
|
+
<div class="closing-cta__actions">
|
|
365
|
+
<!-- primary action: the kit <Button variant="default"> in React -->
|
|
366
|
+
<a class="closing-cta__secondary" href="/architecture">Read the architecture <span aria-hidden="true">→</span></a>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</section>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Copy slots** (fill from the consuming app; keep labels literal):
|
|
373
|
+
|
|
374
|
+
| Slot | Content | Notes |
|
|
375
|
+
|------|---------|-------|
|
|
376
|
+
| `.closing-cta__title` | Headline | Literal, reads best ≤ ~20ch. |
|
|
377
|
+
| `.closing-cta__sub` | Optional one-line support | ≤ ~48ch; omit for a bare title. |
|
|
378
|
+
| `.closing-cta__actions` → kit `Button variant="default"` | Primary label + `href` | The single, literal action (e.g. "Start building", "Open dashboard"). |
|
|
379
|
+
| `.closing-cta__secondary` | Optional secondary label + `href` | Mono, arrow-suffix; the `span[aria-hidden]` translates on hover, matching the kit Button/`.bento__cta`. |
|
|
380
|
+
|
|
381
|
+
**Copy variants:**
|
|
382
|
+
|
|
383
|
+
| Surface | Title | Primary | Secondary |
|
|
384
|
+
|---------|-------|---------|-----------|
|
|
385
|
+
| digithings.ai | "Build your agent stack in the open." | Start building | Read the architecture → |
|
|
386
|
+
| digiquant.io | "One graph, research to execution." | Open dashboard | Browse strategies → |
|
|
387
|
+
|
|
388
|
+
| Class | Role |
|
|
389
|
+
|-------|------|
|
|
390
|
+
| `.closing-cta` | Full-width band — `padding-block: var(--section-y)`, centered text. |
|
|
391
|
+
| `.closing-cta__inner` | `max-width: var(--wrap-wide)` centered column (title / sub / actions), gap-stacked. |
|
|
392
|
+
| `.closing-cta__title` | Clamped display headline, `max-width: 20ch`. |
|
|
393
|
+
| `.closing-cta__sub` | Muted one-line support, `max-width: 48ch`. |
|
|
394
|
+
| `.closing-cta__actions` | Wrapping, centered row of the primary action + optional secondary. |
|
|
395
|
+
| `.closing-cta__secondary` | Mono arrow-suffix link; hover tints `--ink` and nudges the arrow. |
|
|
396
|
+
|
|
397
|
+
CSS-only, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
398
|
+
|
|
399
|
+
## `FaqAccordion` (CSS-only, EVOLUTION.md Phase E)
|
|
400
|
+
|
|
401
|
+
Graphite/Cursor pricing-page Q&A built on **native `<details>`/`<summary>`** — no
|
|
402
|
+
JS. Give every `.faq__item` the **same `name`** to get a native "one open at a
|
|
403
|
+
time" exclusive accordion (a modern-browser feature); omit `name` and each item
|
|
404
|
+
toggles independently. The disclosure chevron rotates on `[open]`; its transition
|
|
405
|
+
is dropped under `prefers-reduced-motion: reduce` (shared block). Keyboard access
|
|
406
|
+
is native (`Tab` to the summary, `Enter`/`Space` to toggle).
|
|
407
|
+
|
|
408
|
+
**Content shape** for a data-driven render (per site): `{ q, a }[]`.
|
|
409
|
+
|
|
410
|
+
```html
|
|
411
|
+
<div class="faq">
|
|
412
|
+
<details class="faq__item" name="pricing-faq" open>
|
|
413
|
+
<summary class="faq__q">Is digithings really open source?</summary>
|
|
414
|
+
<p class="faq__a">Yes — the core stack is MIT-licensed and self-hostable.</p>
|
|
415
|
+
</details>
|
|
416
|
+
<details class="faq__item" name="pricing-faq">
|
|
417
|
+
<summary class="faq__q">Do I need to bring my own model keys?</summary>
|
|
418
|
+
<p class="faq__a">For self-hosting, yes — any LiteLLM provider or a local model.</p>
|
|
419
|
+
</details>
|
|
420
|
+
</div>
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
| Class | Role |
|
|
424
|
+
|-------|------|
|
|
425
|
+
| `.faq` | Column container, `max-width: 760px` for readable line length. |
|
|
426
|
+
| `.faq__item` | One `<details>`; hairline divider below. Same `name` across items → exclusive accordion. |
|
|
427
|
+
| `.faq__q` | The `<summary>` — flex row (label + chevron), default marker removed, `:focus-visible` ring. Rotating CSS chevron via `::after`. |
|
|
428
|
+
| `.faq__a` | Answer body, `max-width: 60ch`, muted. |
|
|
429
|
+
|
|
430
|
+
CSS-only, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
431
|
+
|
|
432
|
+
## `PricingMatrix` (CSS-only, EVOLUTION.md Phase E)
|
|
433
|
+
|
|
434
|
+
Open-core pricing tiers + optional comparison table. **Honest-copy policy**
|
|
435
|
+
(EVOLUTION.md §10, anti-pattern #2): no invented usage caps or fake "limited AI
|
|
436
|
+
requests" — the free self-hosted tier is genuinely the full MIT stack. Three
|
|
437
|
+
tiers: **Self-hosted (MIT)** · **Managed (future)** · **Enterprise (contact)**.
|
|
438
|
+
`.pricing__tier--featured` lifts one card with an `--accent` ring.
|
|
439
|
+
|
|
440
|
+
**Content shape** (per site): `{ name, price, cadence?, desc, features: string[], cta: { label, href }, featured?: boolean }[]`.
|
|
441
|
+
|
|
442
|
+
```html
|
|
443
|
+
<div class="pricing">
|
|
444
|
+
<div class="pricing__tier">
|
|
445
|
+
<div class="pricing__name">Self-hosted</div>
|
|
446
|
+
<div class="pricing__price">Free <small>· MIT</small></div>
|
|
447
|
+
<p class="pricing__desc">Run the full stack on your own infrastructure.</p>
|
|
448
|
+
<ul class="pricing__features"><li>All core services</li><li>Bring your own key</li></ul>
|
|
449
|
+
<div class="pricing__cta"><a class="closing-cta__secondary" href="/docs">Read the docs</a></div>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="pricing__tier pricing__tier--featured"><!-- Managed --></div>
|
|
452
|
+
<div class="pricing__tier"><!-- Enterprise --></div>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<!-- optional feature × tier comparison -->
|
|
456
|
+
<table class="pricing-table">
|
|
457
|
+
<thead><tr><th>Feature</th><th>Self-hosted</th><th>Managed</th><th>Enterprise</th></tr></thead>
|
|
458
|
+
<tbody>
|
|
459
|
+
<tr><th scope="row">Core services</th><td class="is-yes">✓</td><td class="is-yes">✓</td><td class="is-yes">✓</td></tr>
|
|
460
|
+
<tr><th scope="row">Managed upgrades</th><td class="is-no">—</td><td class="is-yes">✓</td><td class="is-yes">✓</td></tr>
|
|
461
|
+
</tbody>
|
|
462
|
+
</table>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
| Class | Role |
|
|
466
|
+
|-------|------|
|
|
467
|
+
| `.pricing` | Grid — 1 column below 768px, `repeat(3, 1fr)` at `min-width: 768px`; `max-width: var(--wrap-wide)`. |
|
|
468
|
+
| `.pricing__tier` | Flat `--surface` card; `.pricing__cta` pins the app-supplied CTA (kit `Button`) to the bottom. |
|
|
469
|
+
| `.pricing__tier--featured` | Highlighted tier — `--accent` border + ring. |
|
|
470
|
+
| `.pricing__name` / `.pricing__price` / `.pricing__desc` | Mono tier label, display price (`<small>` for cadence/licence), muted blurb. |
|
|
471
|
+
| `.pricing__features` | Check-bulleted list — `✓` in `--up` via `::before`. |
|
|
472
|
+
| `.pricing-table` | Optional comparison grid; `.is-yes` (`--up`) / `.is-no` (`--ink-mute`) cells; first column left-aligned, tier columns centered. |
|
|
473
|
+
|
|
474
|
+
CSS-only, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
475
|
+
|
|
476
|
+
## `HeroFeaturePicker` (CSS + `hero-picker.js`, EVOLUTION.md Phase E)
|
|
477
|
+
|
|
478
|
+
Graphite-style **icon-tab row** below the hero that swaps which `ProductFrame`
|
|
479
|
+
(#1202) preview shows — e.g. dashboard / Strategies / Pipeline on digiquant.io.
|
|
480
|
+
**Static UI crops only** (no video swap — lighter weight per the design spec).
|
|
481
|
+
Follows the WAI-ARIA "Tabs" pattern; each panel wraps a `.product-frame`, so it
|
|
482
|
+
inherits the container-query sizing and never clips at browser zoom. Tabs are
|
|
483
|
+
icon-only (`~53×53px`, Graphite reference) — give each an `aria-label`.
|
|
484
|
+
|
|
485
|
+
```html
|
|
486
|
+
<div class="hero-picker">
|
|
487
|
+
<div class="hero-picker__tabs" role="tablist" aria-label="Preview context">
|
|
488
|
+
<button class="hero-picker__tab" role="tab" id="hp-tab-dashboard"
|
|
489
|
+
aria-controls="hp-panel-dashboard" aria-selected="true" aria-label="dashboard">
|
|
490
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><!-- icon --></svg>
|
|
491
|
+
</button>
|
|
492
|
+
<button class="hero-picker__tab" role="tab" id="hp-tab-strategies"
|
|
493
|
+
aria-controls="hp-panel-strategies" aria-selected="false" tabindex="-1" aria-label="Strategies">
|
|
494
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><!-- icon --></svg>
|
|
495
|
+
</button>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="hero-picker__panels">
|
|
498
|
+
<div class="hero-picker__panel" role="tabpanel" id="hp-panel-dashboard" aria-labelledby="hp-tab-dashboard">
|
|
499
|
+
<div class="product-frame"><div class="product-frame__surface"><!-- dashboard crop --></div></div>
|
|
500
|
+
</div>
|
|
501
|
+
<div class="hero-picker__panel" role="tabpanel" id="hp-panel-strategies" aria-labelledby="hp-tab-strategies" hidden>
|
|
502
|
+
<div class="product-frame"><div class="product-frame__surface"><!-- Strategies crop --></div></div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
```js
|
|
509
|
+
import { initHeroPicker } from '../hero-picker.js';
|
|
510
|
+
initHeroPicker(); // wires every .hero-picker on the page
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
| Class | Role |
|
|
514
|
+
|-------|------|
|
|
515
|
+
| `.hero-picker` | Centered column — icon tablist above, swapped panels below. |
|
|
516
|
+
| `.hero-picker__tabs` | `role="tablist"` row of icon buttons. |
|
|
517
|
+
| `.hero-picker__tab` | `role="tab"` icon button, `53×53px`; `[aria-selected="true"]` tints `--accent`; `:focus-visible` ring. Icon-only → needs `aria-label`. |
|
|
518
|
+
| `.hero-picker__panels` / `.hero-picker__panel` | `role="tabpanel"` regions; inactive ones carry the `hidden` attribute. Each wraps a `.product-frame`. |
|
|
519
|
+
|
|
520
|
+
`hero-picker.js`'s `initHeroPicker()` wires click + keyboard (`ArrowLeft`/`ArrowRight`/
|
|
521
|
+
`Home`/`End`, roving `tabindex`) on every `[role="tab"]`, toggling the matching
|
|
522
|
+
`[role="tabpanel"]`'s `hidden` — the same tab model as `code-sample-band.js`. It
|
|
523
|
+
normalizes the initial state from the `aria-selected` tab without stealing focus
|
|
524
|
+
on load. No network calls, pure DOM. `prefers-reduced-motion` needs no special
|
|
525
|
+
handling — the swap is an instant `hidden` toggle, not an animation.
|
|
526
|
+
|
|
527
|
+
CSS-only styling, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
528
|
+
|
|
529
|
+
## `AnnouncementBar` (CSS + `announcement.js`, EVOLUTION.md Phase E)
|
|
530
|
+
|
|
531
|
+
Graphite-style **48px full-width notice above the nav**, **content-gated**.
|
|
532
|
+
`announcement.js` renders it only when handed enabled content, so the bar is
|
|
533
|
+
**off by default** — an empty config, `{ enabled: false }`, or missing `text`
|
|
534
|
+
renders nothing. When `href` is set the whole bar is the link (stretched-link
|
|
535
|
+
overlay); the optional dismiss button persists a per-`id` dismissal in
|
|
536
|
+
`localStorage`. No slide-in animation, so `prefers-reduced-motion` needs no
|
|
537
|
+
special handling. `href` is scheme-guarded (relative / `#` / `http(s)` / `mailto`
|
|
538
|
+
only) and all text is escaped before insertion.
|
|
539
|
+
|
|
540
|
+
**Content shape** (`announcement.json` or inline; see
|
|
541
|
+
[`../announcement-example.json`](../announcement-example.json)):
|
|
542
|
+
|
|
543
|
+
```json
|
|
544
|
+
{ "enabled": true, "id": "2026-07-launch", "text": "…", "href": "/blog/launch", "linkLabel": "Read more" }
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
```html
|
|
548
|
+
<div id="announcement" hidden></div>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
```js
|
|
552
|
+
import { initAnnouncement } from '../announcement.js';
|
|
553
|
+
// inline config, or fetch('/announcement.json').then(r => r.json())
|
|
554
|
+
initAnnouncement({ selector: '#announcement', data: cfg });
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**Enable procedure (per site):** add `announcement.json` with `enabled: true` and
|
|
558
|
+
`text` (and optionally `href`/`linkLabel`/`id`), then call `initAnnouncement`
|
|
559
|
+
with it. To disable, set `enabled: false` or remove the content — nothing
|
|
560
|
+
renders. **Shipped default is disabled** (no content wired), so neither
|
|
561
|
+
digithings.ai nor digiquant.io shows a bar until content is added. Bump `id` to
|
|
562
|
+
re-show the bar after users have dismissed a previous one.
|
|
563
|
+
|
|
564
|
+
| Class | Role |
|
|
565
|
+
|-------|------|
|
|
566
|
+
| `.announcement` | 48px flat `--surface` bar, hairline bottom border, centered text. |
|
|
567
|
+
| `.announcement--link` | Set when `href` is present — enables the stretched-link overlay (whole bar clickable). |
|
|
568
|
+
| `.announcement__text` | Message text; wrap literals in `<strong>` if needed. |
|
|
569
|
+
| `.announcement__link` | The link (`--accent`, arrow-suffix); its `::after` stretches over the bar. |
|
|
570
|
+
| `.announcement__dismiss` | Optional close button — sits above the overlay (`z-index`), persists dismissal by `id`. |
|
|
571
|
+
|
|
572
|
+
CSS + `announcement.js`, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
573
|
+
|
|
574
|
+
## `CaseStudyCard` (CSS-only, EVOLUTION.md Phase E · P3)
|
|
575
|
+
|
|
576
|
+
Graphite `{org} × product` social-proof card — a flat `--surface` panel (same
|
|
577
|
+
sizing family as `.bento__cell`/`.capability-card`) with a mono `{org} × digithings`
|
|
578
|
+
label, a quote, and an attribution row with an optional logo slot. Drop it into a
|
|
579
|
+
`.bento`, `.capability-grid`, or an `.h-scroll` track (for a horizontal row).
|
|
580
|
+
|
|
581
|
+
**Content-gated (anti-pattern #2 — no fabricated testimonials).** Ship dormant:
|
|
582
|
+
no card renders until a real quote exists. **Production requires real attribution**
|
|
583
|
+
— a name, title, company, or a public GitHub/OSS reference. Use
|
|
584
|
+
`.case-study--example` for a watermarked placeholder in demos/docs **only**.
|
|
585
|
+
|
|
586
|
+
**Content shape** (per site): `{ org, quote, name, title?, href?, logo? }`.
|
|
587
|
+
|
|
588
|
+
```html
|
|
589
|
+
<article class="case-study">
|
|
590
|
+
<div class="case-study__label">Acme × digithings</div>
|
|
591
|
+
<blockquote class="case-study__quote">A real, attributable quote.</blockquote>
|
|
592
|
+
<div class="case-study__attr">
|
|
593
|
+
<img class="case-study__logo" src="/logos/acme.svg" alt="Acme" />
|
|
594
|
+
<span><strong>Jane Dev</strong> · Staff Engineer, Acme</span>
|
|
595
|
+
</div>
|
|
596
|
+
</article>
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
| Class | Role |
|
|
600
|
+
|-------|------|
|
|
601
|
+
| `.case-study` | Flat `--surface` card, `--hair` border, `--r-lg`; column layout, quote grows to push attribution to the bottom. |
|
|
602
|
+
| `.case-study__label` | Mono `{org} × digithings` eyebrow. |
|
|
603
|
+
| `.case-study__quote` | The quote (`<blockquote>`), `--ink`. |
|
|
604
|
+
| `.case-study__attr` | Attribution row — name (`<strong>`) + title/company, optional `.case-study__logo`. |
|
|
605
|
+
| `.case-study__logo` | Optional 24px grayscale logo (real assets only, with `alt`). |
|
|
606
|
+
| `.case-study--example` | Adds an "EXAMPLE" watermark — demos/docs only, never production. |
|
|
607
|
+
|
|
608
|
+
CSS-only, both themes. The React wrapper ships in `@digithings-ai/ui`.
|
|
609
|
+
**P3** — defer content until real quotes or OSS adopter stories exist.
|