Public: anyone can read this; the crew can change it.

Design: tokenise src/web/style/content.ts #2704

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/2697-design-tokenise-src-web-style-content-ts → main 1 commit fork

  • Merged. mason/2697-design-tokenise-src-web-style-content-ts was merged into main as 36cea04.

Review

Sign in to review this change.
6 files changed +80 −45
docs/DESIGN.md +1
@@ -55,6 +55,7 @@ variables, so a theme is data. Six ship today. What each role _means_:
5555 | `--add-*` / `--del-*` / `--wd-*` | the diff. Tints stay ≤14% so syntax color survives on top. |
5656 | `--tok-*` | syntax, in two layers: eleven colour classes every consumer knows, plus finer kinds the web paints over them (§6c). |
5757 | `--shadow-1` / `--shadow-2` | a hint and an overlay. Near-transparent in dark palettes, so one rule serves every theme. |
58 | `--series-0…7` / `--qr-ground` | the same in every theme (`FIXED_TOKENS`): chart series (Okabe–Ito, colour-vision safe) and a QR code's white ground. |
5859
5960 **Status surfaces are tinted, never saturated**: `color-mix(in srgb, var(--x)
6061 12%, transparent)` for the fill, the pure token for the text, a 34% mix for the
src/boards/chart.ts +1 −1
@@ -58,7 +58,7 @@ const tip = (label: string, v: number) => `<title>${t(label, 40)}: ${escapeXml(f
5858
5959 /** The `<svg>` open tag and its title. */
6060 const open = (title: string) => [
61 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${CHART_WIDTH} ${CHART_HEIGHT}" width="${CHART_WIDTH}" height="${CHART_HEIGHT}" role="img" aria-label="${t(title, 120)}" class="chart" preserveAspectRatio="xMidYMid meet" font-size="11" font-family="inherit" fill="currentColor">`,
61 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${CHART_WIDTH} ${CHART_HEIGHT}" width="${CHART_WIDTH}" height="${CHART_HEIGHT}" role="img" aria-label="${t(title, 120)}" class="chart" preserveAspectRatio="xMidYMid meet" font-size="10" font-family="inherit" fill="currentColor">`,
6262 `<title>${t(title, 120)}</title>`,
6363 ];
6464 const nothing = (out: string[]) => {
src/design/themes.ts +20
@@ -383,6 +383,25 @@ export function isThemeName(value: string): value is ThemeName {
383383 return Object.hasOwn(THEMES, value);
384384 }
385385
386 /**
387 * Colours every theme shares, because their meaning does not depend on the
388 * surface: the chart series are the Okabe–Ito palette (told apart with the
389 * common colour-vision deficiencies; the folded "others" series is grey),
390 * and a QR code keeps a white ground because a scanner reads dark modules on
391 * light and refuses the inverse.
392 */
393 export const FIXED_TOKENS = {
394 "series-0": "#0072b2",
395 "series-1": "#e69f00",
396 "series-2": "#009e73",
397 "series-3": "#cc79a7",
398 "series-4": "#56b4e9",
399 "series-5": "#d55e00",
400 "series-6": "#c9b800",
401 "series-7": "#8a8a8a",
402 "qr-ground": "#ffffff",
403 } as const;
404
386405 function vars(theme: Theme): string {
387406 const lines = TOKEN_KEYS.map((key) => ` --${key}: ${theme.tokens[key]};`);
388407 lines.push(` color-scheme: ${theme.appearance};`);
@@ -394,6 +413,7 @@ function vars(theme: Theme): string {
394413 * `data-theme`; with no attribute the default pair follows the OS.
395414 */
396415 export const themeCss = [
416 `:root {\n${Object.entries(FIXED_TOKENS).map(([key, value]) => ` --${key}: ${value};`).join("\n")}\n}`,
397417 `:root {\n${vars(THEMES.light)}\n}`,
398418 `@media (prefers-color-scheme: dark) {\n:root:not([data-theme]) {\n${vars(THEMES.dark)}\n}\n}`,
399419 ...THEME_NAMES.map(
src/web/fleet-graph.ts +4 −1
@@ -9,7 +9,9 @@
99 * is the smallest 1, 2, 2.5 or 5 × 10ⁿ at or above the tallest stack
1010 * (`niceCeiling`). Colours are the Okabe–Ito palette, chosen to be told
1111 * apart with the common colour-vision deficiencies; the folded "others"
12 * series is grey. They are classes (`fg-s0`…), set in the stylesheet.
12 * series is grey. They are classes (`fg-s0`…), set in the stylesheet from
13 * the `--series-N` tokens in `src/design/themes.ts`. Text is sized here in
14 * viewBox units, beside the geometry it has to fit, not in the stylesheet.
1315 *
1416 * `fleetGraphSection` carries `data-src`: the client script below fetches
1517 * it every 30 s while the tab is visible, and at once on a
@@ -107,6 +109,7 @@ export function windowSvg(graph: FleetGraph, w: FleetWindow): Html {
107109 class="fleet-svg"
108110 viewBox="0 0 ${SVG_W} ${SVG_H}"
109111 preserveAspectRatio="xMinYMin meet"
112 font-size="10"
110113 role="img"
111114 aria-label="${label}"
112115 data-window="${w.name}"
src/web/style/content.ts +42 −42
@@ -21,7 +21,7 @@ export const content = /* css */ `
2121 .repo-head { margin: 18px 0 0; view-transition-name: repo-head; }
2222 .repo-title {
2323 display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
24 font-size: 18px; font-weight: 620; letter-spacing: -0.02em; line-height: 1.3;
24 font-size: 20px; font-weight: 620; letter-spacing: -0.02em; line-height: 1.3;
2525 }
2626 .repo-title .owner { font-weight: 400; color: var(--muted-fg); }
2727 .repo-title .slash { color: var(--faint); font-weight: 400; }
@@ -35,7 +35,7 @@ a.repo-audience:hover { color: var(--fg); }
3535 .repo-audience { flex-basis: 100%; white-space: normal; text-overflow: clip; }
3636 a.repo-audience { display: block; padding: 12px 0; min-height: 44px; box-sizing: border-box; }
3737 }
38 .repo-desc { margin-top: 4px; color: var(--muted-fg); font-size: 13.5px; text-wrap: pretty; }
38 .repo-desc { margin-top: 4px; color: var(--muted-fg); font-size: 13px; text-wrap: pretty; }
3939 .repo-lineage { margin-top: 3px; font-size: 12.5px; color: var(--faint); }
4040 .repo-head .tabs { margin-top: 12px; }
4141
@@ -69,7 +69,7 @@ a.where-place:hover, button.where-place:hover {
6969 .where-place .ico { color: var(--faint); flex: none; }
7070 /* A thumb needs more than a 12px word to land on. */
7171 @media (pointer: coarse) { .where-place { min-height: 32px; padding: 4px 8px; } }
72 .where-dot { font-size: 10px; line-height: 1; }
72 .where-dot { font-size: 11.5px; line-height: 1; }
7373 .where-note { color: var(--faint); }
7474 /* Accent is interaction everywhere else in the product; here it is the one
7575 place a page is *reading from*, which is the page's own subject. */
@@ -116,7 +116,7 @@ a.where-place:hover, button.where-place:hover {
116116 .stages { list-style: none; margin: 0; padding: 0; }
117117 .stages li {
118118 display: flex; align-items: center; gap: 10px;
119 padding: 8px 0; font-size: 13.5px; color: var(--faint);
119 padding: 8px 0; font-size: 13px; color: var(--faint);
120120 }
121121 .stages li + li { border-top: 1px solid var(--border); }
122122 .stages li[data-state="done"] { color: var(--muted-fg); }
@@ -224,7 +224,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
224224 .handoff { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 16px; margin-top: 12px; }
225225 .handoff > svg {
226226 flex: none; width: 168px; height: 168px;
227 background: #fff; border: 1px solid var(--border); border-radius: var(--radius-sm);
227 background: var(--qr-ground); border: 1px solid var(--border); border-radius: var(--radius-sm);
228228 }
229229 .handoff-links { flex: 1 1 16rem; min-width: 0; }
230230 .handoff-links .mono { user-select: all; overflow-wrap: anywhere; }
@@ -312,7 +312,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
312312 /* A fork's relationship to what it follows: one sentence, one action. */
313313 .following { margin-top: 12px; }
314314 .following-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
315 .following-eyebrow { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 11px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted-fg); }
315 .following-eyebrow { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 11.5px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted-fg); }
316316 .following-eyebrow a { color: var(--fg); text-transform: none; letter-spacing: 0; font-family: var(--font); font-size: 13px; font-weight: 600; }
317317 .following-sentence { margin: 8px 0 0; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; text-wrap: balance; }
318318 .following-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
@@ -329,14 +329,14 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
329329 .audit-app { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; color: var(--fg); margin: 0; font-size: 14px; }
330330 .audit-app .ico { color: var(--faint); }
331331 .audit-trail { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
332 .audit-trail code { font-size: 11px; }
332 .audit-trail code { font-size: 11.5px; }
333333 .audit th[scope="rowgroup"] { vertical-align: top; font-family: var(--mono); font-weight: 600; }
334334 .audit th[scope="row"] { font-weight: 400; color: var(--muted-fg); }
335335 .audit th[scope="rowgroup"], .audit th[scope="row"] { width: 1%; white-space: nowrap; padding-right: 24px; }
336336 .audit thead th:nth-child(n + 3), .audit td { text-align: center; }
337337 .audit-answer {
338338 display: inline-block; min-width: 3.5ch; padding: 1px 6px; border-radius: 999px;
339 font-family: var(--mono); font-size: 11px; line-height: 18px;
339 font-family: var(--mono); font-size: 11.5px; line-height: 18px;
340340 }
341341 .audit-answer[data-answer="yes"] { background: color-mix(in srgb, var(--attn) 18%, transparent); color: var(--attn); }
342342 .audit-answer[data-answer="own"] { background: var(--muted); color: var(--fg); }
@@ -345,7 +345,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
345345 .audit-wide { flex-basis: 100%; }
346346 .audit-said { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
347347 .audit-alarm { color: var(--danger); }
348 .wall-cells { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; }
348 .wall-cells { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12.5px; }
349349 .grants th, .grants td { padding: 8px 12px; border-top: 1px solid var(--border); }
350350 .grants thead th { border-top: 0; font-weight: 600; }
351351 .grants th[scope="col"], .grants td { text-align: center; }
@@ -395,15 +395,15 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
395395 .fleet-window figcaption { font-size: 13px; font-weight: 600; margin: 0 0 4px; display: flex; gap: 8px; align-items: baseline; }
396396 .chart-box { color: var(--accent); aspect-ratio: 720 / 260; max-width: 720px; }
397397 .chart-box .chart { display: block; width: 100%; height: auto; }
398 .chart-box text { fill: var(--muted-fg); font-family: var(--mono); font-size: 10px; }
398 .chart-box text { fill: var(--muted-fg); font-family: var(--mono); }
399399 .chart-box circle { fill: var(--accent); }
400 .chart-box .big { fill: var(--fg); font-family: inherit; font-size: 72px; }
400 .chart-box .big { fill: var(--fg); font-family: inherit; }
401401 .chart-box.is-table { aspect-ratio: auto; height: 260px; overflow: auto; }
402402 .bp-h2 .chart-box.is-table { height: 400px; }
403403 .bp-h3 .chart-box.is-table { height: 540px; }
404404 .bform { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; margin-bottom: 8px; }
405405 .bform .qf { display: flex; flex-direction: column; gap: 4px; flex: 1 1 140px; min-width: 0; }
406 .bform .qf > span { font-size: 12px; font-weight: 600; color: var(--fg); }
406 .bform .qf > span { font-size: 12.5px; font-weight: 600; color: var(--fg); }
407407 .bform .btn { flex: none; }
408408 .boards-result .table { margin-top: 12px; }
409409 .sys-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; margin-bottom: 16px; }
@@ -419,25 +419,25 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
419419 @media (max-width: 720px) { .bgrid { grid-template-columns: minmax(0, 1fr); } .bp-w2, .bp-w3 { grid-column: auto; } }
420420 .fleet-svg { display: block; width: 100%; height: auto; aspect-ratio: 600 / 230; }
421421 .fleet-grid line { stroke: var(--border); stroke-width: 1; }
422 .fleet-ticks text { fill: var(--muted-fg); font-size: 10px; text-anchor: end; font-family: var(--mono); }
423 .fleet-axis text { fill: var(--muted-fg); font-size: 10px; text-anchor: middle; font-family: var(--mono); }
424 .fleet-panel-name text { fill: var(--faint); font-size: 10px; }
422 .fleet-ticks text { fill: var(--muted-fg); text-anchor: end; font-family: var(--mono); }
423 .fleet-axis text { fill: var(--muted-fg); text-anchor: middle; font-family: var(--mono); }
424 .fleet-panel-name text { fill: var(--faint); }
425425 .fleet-table .num { text-align: right; font-variant-numeric: tabular-nums; }
426426 .fleet-table th[scope="row"] { font-weight: 500; white-space: nowrap; text-align: left; }
427427 .fleet-assume { margin-top: 8px; }
428 .fg-s0 { fill: #0072b2; color: #0072b2; }
429 .fg-s1 { fill: #e69f00; color: #e69f00; }
430 .fg-s2 { fill: #009e73; color: #009e73; }
431 .fg-s3 { fill: #cc79a7; color: #cc79a7; }
432 .fg-s4 { fill: #56b4e9; color: #56b4e9; }
433 .fg-s5 { fill: #d55e00; color: #d55e00; }
434 .fg-s6 { fill: #c9b800; color: #c9b800; }
435 .fg-s7 { fill: #8a8a8a; color: #8a8a8a; }
428 .fg-s0 { fill: var(--series-0); color: var(--series-0); }
429 .fg-s1 { fill: var(--series-1); color: var(--series-1); }
430 .fg-s2 { fill: var(--series-2); color: var(--series-2); }
431 .fg-s3 { fill: var(--series-3); color: var(--series-3); }
432 .fg-s4 { fill: var(--series-4); color: var(--series-4); }
433 .fg-s5 { fill: var(--series-5); color: var(--series-5); }
434 .fg-s6 { fill: var(--series-6); color: var(--series-6); }
435 .fg-s7 { fill: var(--series-7); color: var(--series-7); }
436436 @media print { .fleet-pick { display: none; } }
437437 .backups-error { display: flex; align-items: center; gap: 8px; color: var(--danger); }
438 .backups-restore { margin: 8px 0 0; padding: 8px 10px; font-size: 12px; white-space: pre-wrap; word-break: break-all; background: var(--muted); border-radius: var(--radius); }
438 .backups-restore { margin: 8px 0 0; padding: 8px 10px; font-size: 12.5px; white-space: pre-wrap; word-break: break-all; background: var(--muted); border-radius: var(--radius); }
439439 /* The records a domain waits on: type, name, value — as a zone file reads. */
440 .domain-records { margin: 6px 0 2px; padding: 8px 10px; font-size: 12px; line-height: 1.6; white-space: pre; overflow-x: auto; scrollbar-width: thin; background: var(--muted); border-radius: var(--radius); }
440 .domain-records { margin: 6px 0 2px; padding: 8px 10px; font-size: 12.5px; line-height: 1.6; white-space: pre; overflow-x: auto; scrollbar-width: thin; background: var(--muted); border-radius: var(--radius); }
441441 .policy-line {
442442 display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
443443 margin: 12px 0 0; font-size: 13px; color: var(--muted-fg);
@@ -447,7 +447,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
447447 .policy-line[data-tone="mismatch"] .ico { color: var(--danger); }
448448 .policy-line[data-tone="ok"] .ico { color: var(--success); }
449449 .audit-h2 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; margin: 22px 0 10px; }
450 .audit-cells { margin-top: 6px; font-family: var(--mono); font-size: 12px; }
450 .audit-cells { margin-top: 6px; font-family: var(--mono); font-size: 12.5px; }
451451 .audit-cells li[data-way="wider"] { color: var(--attn); }
452452 .audit-cells li[data-way="narrower"] { color: var(--success); }
453453 .audit-change .row-main { min-width: 0; }
@@ -463,7 +463,7 @@ form.inline { display: inline; margin-left: auto; }
463463 }
464464 .lineage-children .row { padding-left: 12px; }
465465 .lineage-here {
466 margin-left: 6px; font-family: var(--mono); font-size: 11px; font-weight: 500;
466 margin-left: 6px; font-family: var(--mono); font-size: 11.5px; font-weight: 500;
467467 letter-spacing: 0.04em; color: var(--accent);
468468 }
469469 .lineage .row[aria-current="page"] { background: var(--muted); }
@@ -506,7 +506,7 @@ form.inline { display: inline; margin-left: auto; }
506506 @media (max-width: 600px) {
507507 .explore-filters {
508508 flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
509 mask-image: linear-gradient(to right, #000 calc(100% - var(--chip-fade)), transparent);
509 mask-image: linear-gradient(to right, var(--fg) calc(100% - var(--chip-fade)), transparent);
510510 }
511511 @supports (animation-timeline: scroll()) {
512512 .explore-filters { animation: chip-fade linear both; animation-timeline: scroll(self inline); }
@@ -537,7 +537,7 @@ form.inline { display: inline; margin-left: auto; }
537537 width: 8px; height: 8px; border-radius: 50%; background: var(--success);
538538 animation: run-pulse 1.6s ease-in-out infinite;
539539 }
540 .front-issues { display: grid; gap: 4px; font-size: 13.5px; list-style: none; padding: 0; margin: 0; }
540 .front-issues { display: grid; gap: 4px; font-size: 14px; list-style: none; padding: 0; margin: 0; }
541541 .front-issues li { min-width: 0; }
542542 .front-issues a { color: inherit; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
543543 .front-more { text-align: center; }
@@ -638,14 +638,14 @@ form.inline { display: inline; margin-left: auto; }
638638 /* A rendered Markdown blob wears the same frame its source does: one card,
639639 one path header, and the switch between the two views in it. */
640640 .blob-view { margin-left: 8px; }
641 .blob-view .seg { font-size: 12px; }
641 .blob-view .seg { font-size: 12.5px; }
642642
643643 /* ---- prose (markdown) ---- */
644644 .prose { padding: 16px 20px; font-size: 15px; line-height: 1.65; overflow-wrap: break-word; }
645645 .prose > * { max-width: var(--measure); }
646646 .prose h1, .prose h2, .prose h3, .prose h4 { margin: 24px 0 8px; }
647 .prose h1 { font-size: 22px; }
648 .prose h2 { font-size: 18px; }
647 .prose h1 { font-size: 20px; }
648 .prose h2 { font-size: 16px; }
649649 .prose h3 { font-size: 15px; }
650650 .prose h1:first-child, .prose h2:first-child, .prose h3:first-child { margin-top: 0; }
651651 .prose h1, .prose h2 { padding-bottom: 6px; border-bottom: 1px solid var(--border); }
@@ -708,7 +708,7 @@ form.inline { display: inline; margin-left: auto; }
708708 font-size: 12.5px; font-family: var(--mono);
709709 }
710710 .diff-stat {
711 font-family: var(--mono); font-size: 12px; white-space: nowrap;
711 font-family: var(--mono); font-size: 12.5px; white-space: nowrap;
712712 font-variant-numeric: tabular-nums; display: inline-flex; gap: 6px;
713713 }
714714 .diff-stat .add { color: var(--add-fg); }
@@ -718,7 +718,7 @@ form.inline { display: inline; margin-left: auto; }
718718 .diff-stub .diff-stat { display: inline-flex; }
719719 .diff-viewed {
720720 display: inline-flex; align-items: center; gap: 5px; flex: none;
721 font-size: 12px; color: var(--muted-fg); cursor: pointer; margin: 0;
721 font-size: 12.5px; color: var(--muted-fg); cursor: pointer; margin: 0;
722722 }
723723 .diff-viewed input { accent-color: var(--accent); margin: 0; }
724724 /* "Viewed" is the authority: a checked file stays collapsed and steps back,
@@ -773,7 +773,7 @@ form.inline { display: inline; margin-left: auto; }
773773 }
774774 .rc { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 12px; margin-bottom: 8px; }
775775 .rc-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; font-size: 13px; }
776 .rc-body { font-size: 13.5px; padding: 0; }
776 .rc-body { font-size: 14px; padding: 0; }
777777 .rc-body p:first-child { margin-top: 0; }
778778 .rc-body p:last-child { margin-bottom: 0; }
779779 .rc-outdated { color: var(--del-fg); border-color: color-mix(in srgb, var(--del-fg) 40%, var(--border)); }
@@ -808,7 +808,7 @@ form.inline { display: inline; margin-left: auto; }
808808 .review-body {
809809 flex-basis: 100%; margin-top: 4px; padding: 8px 12px;
810810 background: var(--card); border: 1px solid var(--border);
811 border-radius: var(--radius); font-size: 13.5px; color: var(--fg);
811 border-radius: var(--radius); font-size: 14px; color: var(--fg);
812812 }
813813 .review-body p:first-child { margin-top: 0; }
814814 .review-body p:last-child { margin-bottom: 0; }
@@ -901,17 +901,17 @@ form.inline { display: inline; margin-left: auto; }
901901 .run-log {
902902 margin: 0; padding: 10px 14px 8px; max-height: calc(100dvh - 300px); min-height: 12em; overflow: auto;
903903 background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius);
904 font-size: 13.5px; line-height: 1.5; overflow-wrap: anywhere; tab-size: 4;
904 font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; tab-size: 4;
905905 }
906906 .run-live:has(.run-steer) .run-log { border-radius: var(--radius) var(--radius) 0 0; }
907907 .run-log .ln { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 10px; padding: 2px 0; }
908 .run-log .who { color: var(--faint); font-size: 12px; font-weight: 600; padding-top: 2px; user-select: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
908 .run-log .who { color: var(--faint); font-size: 12.5px; font-weight: 600; padding-top: 2px; user-select: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
909909 .run-log .txt { white-space: pre-wrap; }
910910 .run-log .ln-say .who { color: var(--fg); }
911911 .run-log .ln-say + .ln-say > .who, .run-log .ln-say + .ln-tool > .who, .run-log .ln-tool + .ln-tool > .who,
912912 .run-log .ln-tool + .ln-say > .who, .run-log .ln-forge + .ln-forge > .who, .run-log .ln-say + .ln-pulse > .who,
913913 .run-log .ln-tool + .ln-pulse > .who { visibility: hidden; }
914 .run-log .ln-tool .txt { font-family: var(--mono); font-size: 12px; color: var(--faint); }
914 .run-log .ln-tool .txt { font-family: var(--mono); font-size: 12.5px; color: var(--faint); }
915915 .run-log .ln-tool b { color: var(--fg); font-weight: 600; }
916916 .run-log .ln-forge .txt { color: var(--faint); font-style: italic; }
917917 .run-log .ln-you { margin: 6px 0; }
@@ -923,8 +923,8 @@ form.inline { display: inline; margin-left: auto; }
923923 .run-log .ln-pulse i:nth-child(3) { animation-delay: .4s; }
924924 @keyframes run-pulse { 0%, 80%, 100% { opacity: .25; } 40% { opacity: 1; } }
925925 .run-jump {
926 position: sticky; bottom: 6px; float: right; margin-top: 6px; padding: 4px 10px; font: inherit; font-size: 12px;
927 color: var(--bg); background: var(--fg); border: 0; border-radius: 999px; cursor: pointer; box-shadow: var(--shadow, 0 2px 8px rgba(0,0,0,.15));
926 position: sticky; bottom: 6px; float: right; margin-top: 6px; padding: 4px 10px; font: inherit; font-size: 12.5px;
927 color: var(--bg); background: var(--fg); border: 0; border-radius: 999px; cursor: pointer; box-shadow: var(--shadow-2);
928928 }
929929 .run-steer {
930930 position: sticky; bottom: 0; display: grid; gap: 6px; padding: 10px 12px; background: var(--bg);
@@ -989,7 +989,7 @@ form.inline { display: inline; margin-left: auto; }
989989 }
990990 .explorer-toggle::-webkit-details-marker { display: none; }
991991 .explorer-toggle .ico { color: var(--faint); }
992 .explorer-toggle .ex-ref { color: var(--muted-fg); font-size: 12px; }
992 .explorer-toggle .ex-ref { color: var(--muted-fg); font-size: 12.5px; }
993993 .explorer-panel[open] > .explorer-toggle { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
994994
995995 .explorer {
test/themes.test.ts +12 −1
@@ -1,5 +1,6 @@
11 import { describe, expect, test } from "bun:test";
2 import { bootCss, THEME_NAMES, THEMES } from "../src/design/themes.ts";
2 import { bootCss, FIXED_TOKENS, THEME_NAMES, THEMES } from "../src/design/themes.ts";
3 import { css } from "../src/web/style.ts";
34
45 describe("bootCss: the ground before the stylesheet arrives (#1240)", () => {
56 test("with no data-theme, the system pair follows the OS", () => {
@@ -27,3 +28,13 @@ describe("bootCss: the ground before the stylesheet arrives (#1240)", () => {
2728 }
2829 });
2930 });
31
32 describe("fixed tokens: colours every theme shares (#2697)", () => {
33 test("the stylesheet defines each one, and every var(--series-N|qr-ground) it reads is defined", () => {
34 for (const [key, value] of Object.entries(FIXED_TOKENS)) expect(css).toContain(`--${key}: ${value};`);
35 for (const m of css.matchAll(/var\(--((?:series-\d+)|qr-ground)\)/g)) {
36 expect(Object.keys(FIXED_TOKENS)).toContain(m[1]!);
37 }
38 expect(css).toContain(".fg-s0 { fill: var(--series-0); color: var(--series-0); }");
39 });
40 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs