@theme-registry/refract 0.1.10 → 0.1.12

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/dist/cli.js CHANGED
@@ -6114,7 +6114,7 @@ const SECTIONS = [
6114
6114
  { id: "type", title: "Type scale", eyebrow: "Typography" },
6115
6115
  { id: "space", title: "Spacing and size", eyebrow: "Space",
6116
6116
  note: "There is no separate <code>padding</code> token — spacing <em>is</em> the padding scale, so it is shown both as a measure and as an applied inset." },
6117
- { id: "shape", title: "Borders, radius and elevation", eyebrow: "Shape &amp; depth" },
6117
+ { id: "shape", title: "Borders, radius and elevation", eyebrow: "Shape & depth" },
6118
6118
  { id: "motion", title: "Transitions", eyebrow: "Motion" },
6119
6119
  { id: "layout", title: "Breakpoints", eyebrow: "Layout",
6120
6120
  note: "These drive the Width control at the top — pick one to reflow the whole specimen at that viewport." },
@@ -6155,6 +6155,64 @@ function lightnessOf(value) {
6155
6155
  return undefined;
6156
6156
  }
6157
6157
  }
6158
+ /** WCAG relative luminance of a hex colour, or `undefined` when it isn't one. */
6159
+ function luminance(value) {
6160
+ const hex = value.trim();
6161
+ if (!/^#[0-9a-f]{6}$/i.test(hex))
6162
+ return undefined;
6163
+ const n = parseInt(hex.slice(1), 16);
6164
+ const ch = (c) => {
6165
+ const v = c / 255;
6166
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
6167
+ };
6168
+ return 0.2126 * ch((n >> 16) & 255) + 0.7152 * ch((n >> 8) & 255) + 0.0722 * ch(n & 255);
6169
+ }
6170
+ /** WCAG contrast ratio between two hex colours, or `undefined` if either isn't parseable. */
6171
+ function contrastRatio(a, b) {
6172
+ const la = luminance(a);
6173
+ const lb = luminance(b);
6174
+ if (la === undefined || lb === undefined)
6175
+ return undefined;
6176
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
6177
+ }
6178
+ function colorProvenance(model) {
6179
+ var _a, _b, _c, _d;
6180
+ const out = new Map();
6181
+ const properties = (_b = (_a = model.subsystems.colors) === null || _a === void 0 ? void 0 : _a.properties) !== null && _b !== void 0 ? _b : {};
6182
+ const mark = (path, ref) => {
6183
+ var _a, _b;
6184
+ if (!ref)
6185
+ return;
6186
+ const derived = ref.ref !== undefined || ref.fn !== undefined || ((_b = (_a = ref.modifiers) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
6187
+ out.set(path, derived ? "gen" : "src");
6188
+ };
6189
+ for (const [family, property] of Object.entries(properties)) {
6190
+ mark(`colors.${family}`, property.base);
6191
+ for (const [extra, ref] of Object.entries((_c = property.extras) !== null && _c !== void 0 ? _c : {}))
6192
+ mark(`colors.${family}.${extra}`, ref);
6193
+ for (const [variant, model2] of Object.entries((_d = property.variants) !== null && _d !== void 0 ? _d : {})) {
6194
+ mark(`colors.${family}.${variant}`, model2.base);
6195
+ }
6196
+ }
6197
+ return out;
6198
+ }
6199
+ /**
6200
+ * The masthead wears the theme's own first palette — the one place the chrome takes a hue, and it
6201
+ * takes the theme's rather than asserting one. Falls back to ink when no colour token parses.
6202
+ */
6203
+ function mastheadColor(leaves) {
6204
+ var _a;
6205
+ const usable = (l) => l.type === "color" && /^#[0-9a-f]{6}$/i.test(String(l.value).trim());
6206
+ // A family's `base` — NOT merely the first colour leaf, which is a ladder rung (`50`) and comes
6207
+ // out near-white. Fall back to any colour only when no family declares a base.
6208
+ const first = (_a = leaves.find(l => usable(l) && l.path[l.path.length - 1] === "base")) !== null && _a !== void 0 ? _a : leaves.find(usable);
6209
+ if (!first)
6210
+ return undefined;
6211
+ const bg = String(first.value).trim();
6212
+ const L = lightnessOf(bg);
6213
+ // A light brand needs dark type on it; anything else takes white.
6214
+ return { bg, fg: L !== undefined && L > 62 ? "#14171c" : "#ffffff" };
6215
+ }
6158
6216
  /**
6159
6217
  * Which rung the family's `base` LANDS on — never which rung it *is*.
6160
6218
  *
@@ -6181,10 +6239,20 @@ function pxOf(value) {
6181
6239
  return undefined;
6182
6240
  return match[2] === "rem" || match[2] === "em" ? n * 16 : n;
6183
6241
  }
6184
- // ── Colour ─────────────────────────────────────────────────────────────────
6185
- function renderPalette(leaves, tokenName) {
6242
+ /**
6243
+ * Colour, as one CARD per family.
6244
+ *
6245
+ * The flat row-per-token list this replaces gave every family the same weight and let them run
6246
+ * together; a card gives each family an edge, a large base swatch to judge against, and room to
6247
+ * separate what the author WROTE from what refract derived.
6248
+ */
6249
+ function renderPalette(leaves, model, tokenName) {
6186
6250
  var _a, _b, _c;
6187
- // family member → hex. `color.<family>.<member>`; a family may also be a bare leaf.
6251
+ const provenance = colorProvenance(model);
6252
+ const tagFor = (path) => {
6253
+ const kind = provenance.get(path);
6254
+ return kind ? `<span class="rfp-tag rfp-${kind}">${kind}</span>` : "";
6255
+ };
6188
6256
  const families = new Map();
6189
6257
  for (const leaf of leaves) {
6190
6258
  const family = (_a = leaf.path[1]) !== null && _a !== void 0 ? _a : "color";
@@ -6195,63 +6263,80 @@ function renderPalette(leaves, tokenName) {
6195
6263
  else
6196
6264
  families.set(family, [[member, String(leaf.value)]]);
6197
6265
  }
6198
- const ladders = [];
6199
- const pairs = [];
6266
+ const cards = [];
6267
+ const semantic = [];
6268
+ let total = 0;
6269
+ let passing = 0;
6200
6270
  for (const [family, members] of families) {
6201
- const rungs = members.filter(([name]) => isRung(name));
6271
+ const rungs = members.filter(([name]) => isRung(name)).sort((a, b) => Number(a[0]) - Number(b[0]));
6202
6272
  const base = (_b = members.find(([name]) => name === "base")) === null || _b === void 0 ? void 0 : _b[1];
6273
+ const text = (_c = members.find(([name]) => name === "text")) === null || _c === void 0 ? void 0 : _c[1];
6274
+ const path = `colors.${family}`;
6275
+ // The one contrast pairing the author actually declared.
6276
+ if (base && text) {
6277
+ const ratio = contrastRatio(base, text);
6278
+ if (ratio !== undefined) {
6279
+ total += 1;
6280
+ if (ratio >= 4.5)
6281
+ passing += 1;
6282
+ }
6283
+ }
6203
6284
  if (rungs.length >= 3) {
6204
- // A ladder: contiguous rungs read as a ramp in a way a row-per-token list never does.
6205
- rungs.sort((a, b) => Number(a[0]) - Number(b[0]));
6206
6285
  const lands = baseLandsOn(base, rungs);
6207
6286
  const strip = rungs
6208
6287
  .map(([step, hex]) => {
6209
6288
  const isLanding = lands !== undefined && step === lands;
6210
- return (`<div class="rfp-rung"${isLanding ? ' data-lands="true"' : ""}` +
6289
+ return (`<div class="rfp-rung"${isLanding ? " data-lands" : ""}` +
6211
6290
  ` title="colors.${esc(family)}.${esc(step)} · ${esc(hex)}${isLanding ? " · base lands here" : ""}">` +
6212
- `<div class="rfp-rung-chip" data-hex="${esc(hex)}" style="background:${cssValue(hex)}"></div>` +
6291
+ `<div class="rfp-sw" style="background:${cssValue(hex)}"></div>` +
6213
6292
  `<div class="rfp-rung-foot">${esc(step)}</div></div>`);
6214
6293
  })
6215
6294
  .join("");
6216
- ladders.push(`<div class="rfp-ladder"><div><div class="rfp-ladder-name">${esc(family)}</div>` +
6217
- (base
6218
- ? `<div class="rfp-ladder-base">base · ${esc(base)}` +
6219
- (lands ? `<br>lands &asymp; ${esc(lands)}` : "") +
6220
- `</div>`
6221
- : "") +
6222
- idButton(`colors.${family}`, tokenName === null || tokenName === void 0 ? void 0 : tokenName(`colors.${family}`)) +
6223
- `</div><div class="rfp-rungs">${strip}</div></div>`);
6295
+ // Anything that isn't a rung is a declared member — show it with its provenance.
6296
+ const chips = members
6297
+ .filter(([name]) => !isRung(name) && name !== "base")
6298
+ .map(([member, hex]) => chipOf(`${path}.${member}`, member, hex, tagFor(`${path}.${member}`), tokenName))
6299
+ .join("");
6300
+ cards.push(`<section class="rfp-card"><div class="rfp-pal-top">` +
6301
+ `<div class="rfp-pal-base" style="background:${cssValue(base !== null && base !== void 0 ? base : rungs[Math.floor(rungs.length / 2)][1])}"></div>` +
6302
+ `<h3 class="rfp-pal-name">${esc(family)}` +
6303
+ `<small>${base ? `base ${esc(base)}` : ""}${lands ? ` · lands &asymp; ${esc(lands)}` : ""} · ${rungs.length} rungs</small>` +
6304
+ idButton(path, tokenName === null || tokenName === void 0 ? void 0 : tokenName(path)) +
6305
+ `</h3></div>` +
6306
+ `<div class="rfp-row-label">Lightness ladder ${tagFor(`${path}.${rungs[0][0]}`) || '<span class="rfp-tag rfp-gen">gen</span>'}</div>` +
6307
+ `<div class="rfp-rungs">${strip}</div>` +
6308
+ (chips ? `<div class="rfp-row-label">Declared members</div><div class="rfp-chips">${chips}</div>` : "") +
6309
+ `</section>`);
6224
6310
  continue;
6225
6311
  }
6226
- // Not a ladder — render each member as a swatch card.
6227
- //
6228
- // The contrast readout goes ONLY on the member that genuinely declares the pairing: the family
6229
- // base against its own `text`. Derived tints (`brand.dark`, `surface.lighter`, …) were never
6230
- // meant to carry that text, so scoring them produces "fail" badges that read as a defect in the
6231
- // user's theme when nothing is wrong. On a page whose whole job is to tell the truth about a
6232
- // theme, inventing a pairing to score is the worst kind of noise.
6233
- const text = (_c = members.find(([name]) => name === "text")) === null || _c === void 0 ? void 0 : _c[1];
6312
+ // No ladder — the family is a semantic colour (or a small set), so it joins the swatch grid.
6234
6313
  for (const [member, hex] of members) {
6235
6314
  if (member === "text")
6236
6315
  continue;
6237
6316
  const isBase = member === "base";
6238
- const paired = isBase ? text : undefined;
6239
- const path = isBase ? `colors.${family}` : `colors.${family}.${member}`;
6240
- pairs.push(`<div class="rfp-pair"><div class="rfp-pair-swatch" data-bg="${esc(hex)}"${paired ? ` data-fg="${esc(paired)}"` : ""}` +
6241
- ` style="background:${cssValue(hex)}${paired ? `;color:${cssValue(paired)}` : ""}">` +
6242
- (paired ? `<span class="rfp-pair-sample">Aa</span><span class="rfp-pair-ratio"></span>` : "") +
6243
- `</div><div class="rfp-pair-foot">${idButton(path, tokenName === null || tokenName === void 0 ? void 0 : tokenName(path))}` +
6244
- `<span class="rfp-hex">${esc(hex)}${paired ? ` on ${esc(paired)}` : ""}</span></div></div>`);
6317
+ const memberPath = isBase ? path : `${path}.${member}`;
6318
+ semantic.push(chipOf(memberPath, isBase ? family : member, hex, tagFor(memberPath), tokenName, isBase ? text : undefined));
6245
6319
  }
6246
6320
  }
6247
- let html = "";
6248
- if (ladders.length) {
6249
- html += plate("Families", `${ladders.length} · ${leaves.length} tokens`, ladders.join(""));
6250
- }
6251
- if (pairs.length) {
6252
- html += plate("Swatches", "contrast scored only where a <code>text</code> pairing is declared — derived tints carry none", `<div class="rfp-pairs">${pairs.join("")}</div>`);
6321
+ let html = cards.join("");
6322
+ if (semantic.length) {
6323
+ html +=
6324
+ `<section class="rfp-card"><div class="rfp-card-head"><span class="rfp-card-name">Semantic</span>` +
6325
+ `<span class="rfp-card-sub">contrast scored only where a <code>text</code> pairing is declared</span></div>` +
6326
+ `<div class="rfp-chips">${semantic.join("")}</div></section>`;
6253
6327
  }
6254
- return html;
6328
+ return { html, pairings: { total, passing } };
6329
+ }
6330
+ /** One colour chip: swatch (+ live contrast when a pairing is declared), label, tag, value. */
6331
+ function chipOf(path, label, hex, tag, tokenName, pairedText) {
6332
+ return (`<div class="rfp-chip"><div class="rfp-sw rfp-chip-sw"` +
6333
+ (pairedText ? ` data-bg="${esc(hex)}" data-fg="${esc(pairedText)}"` : "") +
6334
+ ` style="background:${cssValue(hex)}${pairedText ? `;color:${cssValue(pairedText)}` : ""}">` +
6335
+ (pairedText ? `<span class="rfp-ratio"></span>` : "") +
6336
+ `</div><div class="rfp-cap"><div class="rfp-lbl">${esc(label)}${tag}</div>` +
6337
+ `<div class="rfp-val-sm">${esc(hex)}${pairedText ? ` on ${esc(pairedText)}` : ""}</div>` +
6338
+ idButton(path, tokenName === null || tokenName === void 0 ? void 0 : tokenName(path)) +
6339
+ `</div></div>`);
6255
6340
  }
6256
6341
  // ── Typography ─────────────────────────────────────────────────────────────
6257
6342
  function renderTypography(leaves, tokenName) {
@@ -6647,6 +6732,33 @@ function renderComposition(recipes, descriptor) {
6647
6732
  }
6648
6733
  return "";
6649
6734
  }
6735
+ /**
6736
+ * The contents cover. Built from the sections that actually rendered, so it shrinks with the theme
6737
+ * rather than advertising plates that aren't there. Accent bars borrow the theme's own colour.
6738
+ */
6739
+ function renderIndex(bodies, counts, accent) {
6740
+ const present = SECTIONS.filter(s => bodies.has(s.id));
6741
+ if (present.length < 2)
6742
+ return "";
6743
+ const cards = present
6744
+ .map((section, i) => {
6745
+ var _a;
6746
+ const n = String(i + 1).padStart(2, "0");
6747
+ const count = (_a = counts.get(section.id)) !== null && _a !== void 0 ? _a : 0;
6748
+ return (`<a class="rfp-idx" href="#rfp-${section.id}" style="text-decoration:none;color:inherit">` +
6749
+ `<span class="rfp-accent" style="background:${accent ? cssValue(accent) : "var(--rfp-spec)"}"></span>` +
6750
+ `<div class="rfp-no">${n} · ${esc(section.eyebrow.toUpperCase())}</div>` +
6751
+ `<h3>${section.title} <span>${count} token(s)</span></h3>` +
6752
+ (section.note ? `<p>${section.note}</p>` : "") +
6753
+ `</a>`);
6754
+ })
6755
+ .join("");
6756
+ return (`<section class="rfp-section" id="rfp-index"><div class="rfp-section-head"><h2>Index</h2>` +
6757
+ `<span class="rfp-count">${present.length} sections</span></div>` +
6758
+ `<p class="rfp-note">A section appears only when the theme has tokens of that kind, so this list ` +
6759
+ `is the shape of the theme rather than a fixed table of contents.</p>` +
6760
+ `<div class="rfp-index">${cards}</div></section>`);
6761
+ }
6650
6762
  // ---------------------------------------------------------------------------
6651
6763
  // Page chrome
6652
6764
  // ---------------------------------------------------------------------------
@@ -6661,159 +6773,183 @@ function renderComposition(recipes, descriptor) {
6661
6773
  * dedicated mid-tone reads at the same weight on both grounds.
6662
6774
  */
6663
6775
  const CHROME_CSS = `
6664
- .rfp{--rfp-ground:#fff;--rfp-panel:#f7f8fa;--rfp-panel-2:#eef0f4;--rfp-ink:#14171c;--rfp-ink-2:#4d5563;
6665
- --rfp-ink-3:#79818f;--rfp-rule:#e4e7ec;--rfp-rule-2:#d3d8e0;--rfp-focus:#3b4ea8;--rfp-live:#2f9e6d;
6666
- --rfp-spec:#97a1b0;--rfp-spec-2:#c2c9d4;--rfp-hatch:#d7dce4;--rfp-stage:#f2f4f7;--rfp-raised:#fff;
6667
- --rfp-accent:#6b7a99;
6776
+ .rfp{--rfp-paper:#f5f6f8;--rfp-card:#fff;--rfp-sunk:#f0f2f5;--rfp-ink:#14171c;--rfp-ink-2:#4d5563;
6777
+ --rfp-ink-3:#79818f;--rfp-line:#e3e6ea;--rfp-line-2:#d0d5dd;--rfp-focus:#3b4ea8;
6778
+ --rfp-spec:#97a1b0;--rfp-spec-2:#c2c9d4;--rfp-hatch:#dfe3e9;
6668
6779
  --rfp-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
6669
6780
  --rfp-ui:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
6670
6781
  font-family:var(--rfp-ui);font-size:14px;line-height:1.55;color:var(--rfp-ink);
6671
- background:var(--rfp-ground);-webkit-font-smoothing:antialiased;
6672
- display:grid;grid-template-columns:216px minmax(0,1fr);gap:56px;max-width:1260px;margin:0 auto;
6673
- padding:0 32px 96px;align-items:start;box-sizing:border-box}
6674
- @media (prefers-color-scheme:dark){.rfp{--rfp-ground:#0f1114;--rfp-panel:#1a1e24;--rfp-panel-2:#232830;
6675
- --rfp-ink:#e9ebef;--rfp-ink-2:#a8b0bd;--rfp-ink-3:#7b8492;--rfp-rule:#2b313a;--rfp-rule-2:#3b434f;
6676
- --rfp-focus:#8ea2ff;--rfp-live:#4ec48d;--rfp-spec:#8791a0;--rfp-spec-2:#4a5361;--rfp-hatch:#333b46;
6677
- --rfp-stage:#14181d;--rfp-raised:#232830;--rfp-accent:#8e9dbb}}
6782
+ background:var(--rfp-paper);-webkit-font-smoothing:antialiased;
6783
+ width:100%;min-height:100vh;box-sizing:border-box}
6784
+ /* Full-bleed geometry. :where() gives these ZERO specificity, so a theme's own globals rules still
6785
+ win - the chrome resets the UA gutter without ever outranking the theme it is displaying.
6786
+ color-scheme is pinned to light: this sheet does NOT follow the OS (see below). */
6787
+ :where(html){color-scheme:light}
6788
+ :where(body){margin:0}
6678
6789
  .rfp *{box-sizing:border-box}
6679
6790
  .rfp *:focus-visible{outline:2px solid var(--rfp-focus);outline-offset:2px;border-radius:3px}
6680
- .rfp-rail{position:sticky;top:0;padding:32px 0;max-height:100vh;overflow-y:auto}
6681
- .rfp-brand{font-family:var(--rfp-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
6682
- color:var(--rfp-ink-3);padding-bottom:14px;margin-bottom:14px;border-bottom:1px solid var(--rfp-rule)}
6791
+ /* ── Masthead: the theme wearing its own first palette ── */
6792
+ .rfp-masthead{padding:52px 40px 36px}
6793
+ .rfp-kicker{font-family:var(--rfp-mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;opacity:.75;margin:0 0 12px}
6794
+ .rfp-masthead h1{margin:0;font-size:38px;font-weight:680;letter-spacing:-.028em;text-wrap:balance}
6795
+ .rfp-lede{margin:12px 0 0;max-width:66ch;opacity:.88;font-size:15px}
6796
+ .rfp-metrics{margin-top:28px;display:flex;gap:34px;flex-wrap:wrap}
6797
+ .rfp-metric b{display:block;font-size:26px;font-weight:700;line-height:1.1;font-variant-numeric:tabular-nums}
6798
+ .rfp-metric span{font-size:12px;opacity:.72;font-family:var(--rfp-mono)}
6799
+ /* ── Shell ── */
6800
+ .rfp-shell{display:grid;grid-template-columns:204px minmax(0,1fr);gap:44px;padding:0 40px 96px;align-items:start}
6801
+ .rfp-rail{position:sticky;top:0;padding:28px 0;max-height:100vh;overflow-y:auto}
6802
+ .rfp-brand{font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
6803
+ color:var(--rfp-ink-3);padding-bottom:12px;margin-bottom:12px;border-bottom:1px solid var(--rfp-line)}
6683
6804
  .rfp-nav{display:flex;flex-direction:column;gap:1px}
6684
6805
  .rfp-nav a{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:5px 8px;
6685
- border-radius:4px;color:var(--rfp-ink-2);text-decoration:none;font-size:13px}
6686
- .rfp-nav a:hover{background:var(--rfp-panel);color:var(--rfp-ink)}
6687
- .rfp-nav a[aria-current="true"]{background:var(--rfp-ink);color:var(--rfp-ground)}
6806
+ border-radius:5px;color:var(--rfp-ink-2);text-decoration:none;font-size:13px}
6807
+ .rfp-nav a:hover{background:var(--rfp-card);color:var(--rfp-ink)}
6808
+ .rfp-nav a[aria-current="true"]{background:var(--rfp-ink);color:var(--rfp-paper)}
6688
6809
  .rfp-nav .rfp-n{font-family:var(--rfp-mono);font-size:11px;font-variant-numeric:tabular-nums;color:var(--rfp-ink-3)}
6689
- .rfp-nav a[aria-current="true"] .rfp-n{color:var(--rfp-ground);opacity:.65}
6690
- .rfp-main{padding-top:32px;min-width:0}
6691
- .rfp-masthead{padding-bottom:28px;border-bottom:1px solid var(--rfp-rule)}
6692
- .rfp-eyebrow{font-family:var(--rfp-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--rfp-ink-3)}
6693
- .rfp-masthead h1{font-size:34px;line-height:1.1;letter-spacing:-.025em;font-weight:640;margin:10px 0 0;text-wrap:balance}
6694
- .rfp-meta{display:flex;flex-wrap:wrap;gap:6px 10px;margin-top:16px;font-family:var(--rfp-mono);font-size:11.5px;color:var(--rfp-ink-2)}
6695
- .rfp-meta span{display:inline-flex;align-items:center;gap:6px}
6696
- .rfp-meta span+span::before{content:"";width:1px;height:11px;background:var(--rfp-rule-2)}
6697
- .rfp-meta b{font-weight:600;color:var(--rfp-ink)}
6698
- .rfp-dot{width:6px;height:6px;border-radius:50%;background:var(--rfp-live)}
6699
- .rfp-controls{display:flex;flex-wrap:wrap;gap:24px;padding:18px 0;border-bottom:1px solid var(--rfp-rule);
6700
- position:sticky;top:0;background:var(--rfp-ground);z-index:5}
6810
+ .rfp-nav a[aria-current="true"] .rfp-n{color:var(--rfp-paper);opacity:.6}
6811
+ .rfp-main{padding-top:28px;min-width:0}
6812
+ .rfp-controls{display:flex;flex-wrap:wrap;gap:22px;padding:14px 0 18px;position:sticky;top:0;background:var(--rfp-paper);z-index:5}
6701
6813
  .rfp-ctl{display:flex;align-items:center;gap:8px}
6702
6814
  .rfp-ctl-label{font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--rfp-ink-3)}
6703
- .rfp-seg{display:flex;border:1px solid var(--rfp-rule-2);border-radius:6px;overflow:hidden}
6704
- .rfp-seg button{font:inherit;font-family:var(--rfp-mono);font-size:11.5px;padding:4px 10px;border:0;
6705
- border-left:1px solid var(--rfp-rule-2);background:var(--rfp-ground);color:var(--rfp-ink-2);cursor:pointer}
6815
+ .rfp-seg{display:flex;border:1px solid var(--rfp-line-2);border-radius:7px;overflow:hidden;background:var(--rfp-card)}
6816
+ .rfp-seg button{font:inherit;font-family:var(--rfp-mono);font-size:11.5px;padding:4px 11px;border:0;
6817
+ border-left:1px solid var(--rfp-line-2);background:transparent;color:var(--rfp-ink-2);cursor:pointer}
6706
6818
  .rfp-seg button:first-child{border-left:0}
6707
- .rfp-seg button:hover{background:var(--rfp-panel);color:var(--rfp-ink)}
6708
- .rfp-seg button[aria-pressed="true"]{background:var(--rfp-ink);color:var(--rfp-ground)}
6709
- .rfp-section{padding-top:52px;scroll-margin-top:72px}
6710
- .rfp-section-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;flex-wrap:wrap}
6711
- .rfp-section-head h2{font-size:21px;letter-spacing:-.018em;font-weight:620;margin:6px 0 0}
6712
- .rfp-note{color:var(--rfp-ink-2);max-width:64ch;margin:8px 0 0;font-size:13.5px}
6819
+ .rfp-seg button:hover{background:var(--rfp-sunk);color:var(--rfp-ink)}
6820
+ .rfp-seg button[aria-pressed="true"]{background:var(--rfp-ink);color:var(--rfp-card)}
6821
+ /* ── Sections + cards ── */
6822
+ .rfp-section{padding-top:44px;scroll-margin-top:64px}
6823
+ .rfp-section-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
6824
+ .rfp-section-head h2{margin:0;font-size:21px;letter-spacing:-.018em;font-weight:640}
6825
+ .rfp-count{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-2);background:var(--rfp-card);
6826
+ border:1px solid var(--rfp-line);border-radius:999px;padding:2px 10px;white-space:nowrap}
6827
+ .rfp-note{margin:8px 0 20px;color:var(--rfp-ink-2);font-size:13.5px;max-width:72ch}
6713
6828
  .rfp-note-sm{color:var(--rfp-ink-3);font-size:12.5px;margin:10px 0 0;max-width:64ch}
6714
- .rfp-tag{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);border:1px solid var(--rfp-rule-2);border-radius:3px;padding:1px 6px}
6715
- .rfp-plate{margin-top:22px;border-top:1px solid var(--rfp-rule);padding-top:18px}
6716
- .rfp-plate-head{display:flex;align-items:baseline;gap:10px;margin-bottom:14px;flex-wrap:wrap}
6717
- .rfp-plate-name{font-family:var(--rfp-mono);font-size:12.5px;font-weight:600;color:var(--rfp-ink)}
6718
- .rfp-plate-sub{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3)}
6829
+ .rfp-card{background:var(--rfp-card);border:1px solid var(--rfp-line);border-radius:14px;padding:18px 20px;margin-top:14px}
6830
+ .rfp-card-head{display:flex;align-items:baseline;gap:10px;margin-bottom:14px;flex-wrap:wrap}
6831
+ .rfp-card-name{font-family:var(--rfp-mono);font-size:12.5px;font-weight:600}
6832
+ .rfp-card-sub{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3)}
6833
+ .rfp-row-label{font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
6834
+ color:var(--rfp-ink-3);margin:16px 0 9px;display:flex;align-items:center;gap:8px}
6835
+ .rfp-divider{display:flex;align-items:center;gap:14px;margin:52px 0 4px}
6836
+ .rfp-divider span{font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;
6837
+ color:var(--rfp-ink-3);white-space:nowrap}
6838
+ .rfp-divider::before,.rfp-divider::after{content:"";height:1px;background:var(--rfp-line);flex:1}
6839
+ /* ── Index cover ── */
6840
+ .rfp-index{display:grid;grid-template-columns:repeat(auto-fill,minmax(272px,1fr));gap:14px}
6841
+ .rfp-idx{position:relative;background:var(--rfp-card);border:1px solid var(--rfp-line);border-radius:14px;
6842
+ padding:18px 18px 15px 22px;overflow:hidden}
6843
+ .rfp-idx .rfp-accent{position:absolute;inset:0 auto 0 0;width:5px}
6844
+ .rfp-idx .rfp-no{font-family:var(--rfp-mono);font-size:10.5px;font-weight:700;letter-spacing:.12em;color:var(--rfp-ink-3)}
6845
+ .rfp-idx h3{margin:6px 0 2px;font-size:18px;letter-spacing:-.015em;font-weight:620}
6846
+ .rfp-idx h3 span{font-family:var(--rfp-mono);font-size:11px;font-weight:500;color:var(--rfp-ink-3)}
6847
+ .rfp-idx p{margin:7px 0 0;font-size:12.5px;color:var(--rfp-ink-2)}
6848
+ /* ── Identifiers ── */
6719
6849
  .rfp-id{font-family:var(--rfp-mono);font-size:11.5px;color:var(--rfp-ink-2);background:none;border:0;
6720
6850
  padding:1px 4px;margin-left:-4px;border-radius:3px;cursor:copy;text-align:left;display:inline-block}
6721
- .rfp-id:hover{background:var(--rfp-panel-2);color:var(--rfp-ink)}
6722
- .rfp-id.rfp-copied{background:var(--rfp-ink);color:var(--rfp-ground)}
6851
+ .rfp-id:hover{background:var(--rfp-sunk);color:var(--rfp-ink)}
6852
+ .rfp-id.rfp-copied{background:var(--rfp-ink);color:var(--rfp-card)}
6723
6853
  .rfp-var{font-family:var(--rfp-mono);font-size:10.5px;color:var(--rfp-ink-3);display:block;margin-top:1px}
6724
6854
  .rfp-hex{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);font-variant-numeric:tabular-nums;display:block;margin-top:1px}
6725
- .rfp-ladder{display:grid;grid-template-columns:150px minmax(0,1fr);gap:20px;align-items:start}
6726
- .rfp-ladder+.rfp-ladder{margin-top:18px}
6727
- .rfp-ladder-name{font-family:var(--rfp-mono);font-size:12.5px;font-weight:600}
6728
- .rfp-ladder-base{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);margin-top:2px}
6729
- .rfp-rungs{display:flex;border-radius:6px;overflow:hidden;border:1px solid var(--rfp-rule)}
6855
+ /* ── Provenance tags ── */
6856
+ .rfp-tag{font-family:var(--rfp-mono);font-size:9px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
6857
+ padding:1px 5px;border-radius:4px}
6858
+ .rfp-src{background:color-mix(in srgb,var(--rfp-focus) 12%,transparent);color:var(--rfp-focus)}
6859
+ .rfp-gen{background:var(--rfp-sunk);color:var(--rfp-ink-3);border:1px solid var(--rfp-line)}
6860
+ /* ── Palette ── */
6861
+ .rfp-pal-top{display:flex;align-items:center;gap:16px}
6862
+ .rfp-pal-base{width:84px;height:84px;border-radius:12px;flex:none;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1)}
6863
+ .rfp-pal-name{margin:0;font-size:18px;font-weight:680;letter-spacing:-.015em}
6864
+ .rfp-pal-name small{display:block;font-family:var(--rfp-mono);font-weight:400;color:var(--rfp-ink-3);font-size:11.5px;margin-top:4px}
6865
+ .rfp-rungs{display:flex;border-radius:9px;overflow:hidden;border:1px solid var(--rfp-line)}
6730
6866
  .rfp-rung{flex:1 1 0;min-width:0}
6731
- .rfp-rung-chip{height:52px}
6732
- .rfp-rung[data-lands="true"] .rfp-rung-foot{color:var(--rfp-ink);font-weight:600}
6733
- .rfp-rung[data-lands="true"] .rfp-rung-foot::before{content:"◆ "}
6734
- .rfp-rung-foot{padding:5px 2px 6px;text-align:center;background:var(--rfp-panel);border-top:1px solid var(--rfp-rule);
6867
+ .rfp-rung .rfp-sw{height:54px}
6868
+ .rfp-rung-foot{padding:6px 2px 7px;text-align:center;background:var(--rfp-sunk);border-top:1px solid var(--rfp-line);
6735
6869
  font-family:var(--rfp-mono);font-size:10px;font-variant-numeric:tabular-nums;color:var(--rfp-ink-2)}
6736
- .rfp-pairs{display:grid;grid-template-columns:repeat(auto-fill,minmax(184px,1fr));gap:12px}
6737
- .rfp-pair{border:1px solid var(--rfp-rule);border-radius:6px;overflow:hidden}
6738
- .rfp-pair-swatch{padding:16px 14px 14px;display:flex;flex-direction:column;gap:10px;min-height:92px}
6739
- .rfp-pair-sample{font-size:14px;font-weight:600}
6740
- .rfp-pair-ratio{align-self:flex-start;font-family:var(--rfp-mono);font-size:10.5px;font-variant-numeric:tabular-nums;
6741
- padding:1px 6px;border-radius:3px;border:1px solid currentColor}
6742
- .rfp-pair-foot{padding:8px 12px;background:var(--rfp-panel);border-top:1px solid var(--rfp-rule)}
6870
+ .rfp-rung[data-lands] .rfp-rung-foot{color:var(--rfp-ink);font-weight:700}
6871
+ .rfp-rung[data-lands] .rfp-rung-foot::before{content:"◆ "}
6872
+ .rfp-chips{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:10px}
6873
+ .rfp-chip{border:1px solid var(--rfp-line);border-radius:10px;overflow:hidden;background:var(--rfp-card)}
6874
+ .rfp-chip-sw{height:50px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.05);display:flex;align-items:flex-end;padding:6px}
6875
+ .rfp-cap{padding:8px 9px 9px}
6876
+ .rfp-lbl{font-size:12px;font-weight:600;display:flex;align-items:center;gap:6px;justify-content:space-between}
6877
+ .rfp-val-sm{font-family:var(--rfp-mono);font-size:10.5px;color:var(--rfp-ink-3);margin-top:3px;font-variant-numeric:tabular-nums}
6878
+ .rfp-ratio{font-family:var(--rfp-mono);font-size:10px;padding:1px 6px;border-radius:4px;border:1px solid currentColor}
6879
+ /* ── Rows ── */
6743
6880
  .rfp-rows{display:flex;flex-direction:column}
6744
- .rfp-row{display:grid;grid-template-columns:190px minmax(0,1fr) 108px;gap:20px;align-items:baseline;
6745
- padding:11px 0;border-top:1px solid var(--rfp-rule)}
6881
+ .rfp-row{display:grid;grid-template-columns:200px minmax(0,1fr) 104px;gap:20px;align-items:baseline;
6882
+ padding:11px 0;border-top:1px solid var(--rfp-line)}
6746
6883
  .rfp-row:first-child{border-top:0}
6747
6884
  .rfp-row.rfp-centred{align-items:center}
6748
6885
  .rfp-rowval{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);font-variant-numeric:tabular-nums;text-align:right;overflow-wrap:anywhere}
6749
6886
  .rfp-specimen{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.015em}
6750
6887
  .rfp-leading{font-size:13px;color:var(--rfp-ink-2);max-width:52ch}
6751
- .rfp-bar{height:12px;background:var(--rfp-spec);border-radius:2px;max-width:100%}
6888
+ .rfp-bar{height:13px;background:var(--rfp-spec);border-radius:3px;max-width:100%}
6752
6889
  .rfp-bar.rfp-ghost{background:var(--rfp-spec-2)}
6753
- .rfp-gap{display:flex;align-items:stretch;border:1px dashed var(--rfp-rule-2);border-radius:6px;padding:10px}
6890
+ .rfp-gap{display:flex;border:1px dashed var(--rfp-line-2);border-radius:8px;padding:10px;background:var(--rfp-sunk)}
6754
6891
  .rfp-gap>i{flex:1 1 0;height:30px;background:var(--rfp-spec-2);border-radius:3px}
6755
- .rfp-inset{border:1px solid var(--rfp-rule);border-radius:6px;
6892
+ .rfp-inset{border:1px solid var(--rfp-line-2);border-radius:10px;
6756
6893
  background:repeating-linear-gradient(-45deg,var(--rfp-hatch) 0 4px,transparent 4px 8px)}
6757
- .rfp-inset-core{background:var(--rfp-spec);color:var(--rfp-ground);border-radius:3px;font-family:var(--rfp-mono);
6758
- font-size:10.5px;font-weight:600;text-align:center;padding:8px 4px}
6759
- .rfp-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px}
6760
- .rfp-tile{display:flex;flex-direction:column;gap:10px}
6761
- .rfp-stage{height:82px;display:grid;place-items:center;background:var(--rfp-stage);border-radius:6px;
6762
- border:1px solid var(--rfp-rule);color:var(--rfp-spec)}
6763
- .rfp-obj{width:62px;height:62px;background:var(--rfp-spec)}
6894
+ .rfp-inset-core{background:var(--rfp-spec);color:var(--rfp-card);border-radius:4px;font-family:var(--rfp-mono);
6895
+ font-size:10.5px;font-weight:700;text-align:center;padding:9px 4px}
6896
+ /* ── Tiles ── */
6897
+ .rfp-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(146px,1fr));gap:12px}
6898
+ .rfp-tile{border:1px solid var(--rfp-line);border-radius:12px;padding:14px;background:var(--rfp-card)}
6899
+ .rfp-stage{height:76px;display:grid;place-items:center;background:var(--rfp-sunk);border-radius:8px;
6900
+ margin-bottom:11px;color:var(--rfp-spec)}
6901
+ .rfp-obj{width:60px;height:60px;background:var(--rfp-spec)}
6764
6902
  .rfp-obj.rfp-outlined{background:transparent}
6765
- .rfp-obj.rfp-raised{background:var(--rfp-raised)}
6766
- .rfp-obj.rfp-accent{background:var(--rfp-accent)}
6767
- .rfp-numeral{font-family:var(--rfp-mono);font-size:20px;font-variant-numeric:tabular-nums;color:var(--rfp-ink-2)}
6768
- .rfp-track{height:34px;border-radius:6px;background:var(--rfp-stage);border:1px solid var(--rfp-rule);position:relative;overflow:hidden}
6903
+ .rfp-obj.rfp-raised{background:var(--rfp-card)}
6904
+ .rfp-obj.rfp-accent{background:var(--rfp-spec)}
6905
+ .rfp-numeral{font-family:var(--rfp-mono);font-size:19px;font-variant-numeric:tabular-nums;color:var(--rfp-ink-2)}
6906
+ .rfp-track{height:34px;border-radius:8px;background:var(--rfp-sunk);border:1px solid var(--rfp-line);position:relative;overflow:hidden}
6769
6907
  .rfp-dot{position:absolute;top:6px;left:6px;width:22px;height:22px;border-radius:5px;background:var(--rfp-spec)}
6770
- .rfp-play{font:inherit;font-family:var(--rfp-mono);font-size:11.5px;padding:3px 10px;border-radius:4px;
6771
- border:1px solid var(--rfp-rule-2);background:var(--rfp-panel);color:var(--rfp-ink);cursor:pointer;margin-left:auto}
6908
+ .rfp-play{font:inherit;font-family:var(--rfp-mono);font-size:11.5px;padding:3px 10px;border-radius:5px;
6909
+ border:1px solid var(--rfp-line-2);background:var(--rfp-sunk);color:var(--rfp-ink);cursor:pointer;margin-left:auto}
6910
+ /* ── Tables ── */
6772
6911
  .rfp-scroll{overflow-x:auto}
6773
- .rfp-diff{width:100%;border-collapse:collapse;font-size:13px}
6774
- .rfp-diff th{text-align:left;font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
6775
- color:var(--rfp-ink-3);font-weight:500;padding:0 12px 8px 0;white-space:nowrap}
6776
- .rfp-diff td{padding:7px 12px 7px 0;border-top:1px solid var(--rfp-rule);vertical-align:middle}
6912
+ .rfp-diff,.rfp-matrix{border-collapse:collapse;width:100%}
6913
+ .rfp-diff th,.rfp-matrix th{text-align:left;font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.1em;
6914
+ text-transform:uppercase;color:var(--rfp-ink-3);font-weight:500;padding:0 12px 9px 0;white-space:nowrap}
6915
+ .rfp-diff td{padding:9px 12px 9px 0;border-top:1px solid var(--rfp-line);vertical-align:middle}
6916
+ .rfp-matrix{min-width:560px}
6917
+ .rfp-matrix th{text-align:center;padding:0 8px 10px}
6918
+ .rfp-matrix th:first-child{text-align:left}
6919
+ .rfp-matrix td{padding:13px 8px;border-top:1px solid var(--rfp-line);text-align:center}
6920
+ .rfp-matrix td:first-child{text-align:left;width:208px}
6921
+ .rfp-none{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);opacity:.55}
6777
6922
  .rfp-swatch-cell{display:flex;align-items:center;gap:8px}
6778
- .rfp-chip{width:18px;height:18px;border-radius:4px;border:1px solid var(--rfp-rule-2);flex:none}
6923
+ .rfp-chip-sm{width:18px;height:18px;border-radius:5px;border:1px solid var(--rfp-line-2);flex:none}
6779
6924
  .rfp-arrow{color:var(--rfp-ink-3);font-family:var(--rfp-mono)}
6780
- .rfp-prose{border:1px solid var(--rfp-rule);border-radius:6px;padding:26px 28px;overflow:hidden}
6781
- .rfp-matrix{border-collapse:collapse;min-width:560px;width:100%}
6782
- .rfp-matrix th{font-family:var(--rfp-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
6783
- color:var(--rfp-ink-3);font-weight:500;text-align:center;padding:0 8px 10px}
6784
- .rfp-matrix th:first-child{text-align:left}
6785
- .rfp-matrix td{padding:12px 8px;border-top:1px solid var(--rfp-rule);text-align:center}
6786
- .rfp-matrix td:first-child{text-align:left;width:210px}
6787
- .rfp-none{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);opacity:.6}
6788
- .rfp-recipes{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px}
6789
- .rfp-recipe{border:1px solid var(--rfp-rule);border-radius:6px;overflow:hidden}
6790
- .rfp-recipe-stage{min-height:96px;display:grid;place-items:center;padding:20px 16px;
6791
- background:linear-gradient(45deg,var(--rfp-panel) 25%,transparent 25%,transparent 75%,var(--rfp-panel) 75%),
6792
- linear-gradient(45deg,var(--rfp-panel) 25%,transparent 25%,transparent 75%,var(--rfp-panel) 75%);
6793
- background-size:14px 14px;background-position:0 0,7px 7px}
6794
- .rfp-recipe-foot{padding:9px 12px;border-top:1px solid var(--rfp-rule);background:var(--rfp-panel)}
6925
+ /* ── Recipes + prose ── */
6926
+ .rfp-recipes{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
6927
+ .rfp-recipe{border:1px solid var(--rfp-line);border-radius:12px;overflow:hidden;background:var(--rfp-card)}
6928
+ .rfp-recipe-stage{min-height:92px;display:grid;place-items:center;padding:18px;background:var(--rfp-sunk)}
6929
+ .rfp-recipe-foot{padding:9px 12px;border-top:1px solid var(--rfp-line)}
6795
6930
  .rfp-addr{font-family:var(--rfp-mono);font-size:11px;color:var(--rfp-ink-3);display:block}
6796
6931
  .rfp-compose{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
6797
- .rfp-cls{font-family:var(--rfp-mono);font-size:11.5px;padding:3px 8px;border-radius:4px;
6798
- border:1px solid var(--rfp-rule-2);background:var(--rfp-panel)}
6932
+ .rfp-cls{font-family:var(--rfp-mono);font-size:11.5px;padding:3px 9px;border-radius:6px;
6933
+ border:1px solid var(--rfp-line);background:var(--rfp-sunk)}
6799
6934
  .rfp-cls b{font-weight:600;color:var(--rfp-ink)}
6800
6935
  .rfp-cls span{color:var(--rfp-ink-3)}
6801
6936
  .rfp-plus{color:var(--rfp-ink-3);font-family:var(--rfp-mono)}
6802
- .rfp-notice{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:start;padding:14px 16px;
6803
- border:1px solid var(--rfp-rule-2);border-radius:6px;background:var(--rfp-panel);margin-top:18px}
6937
+ .rfp-prose{border:1px solid var(--rfp-line);border-radius:14px;padding:26px 28px;background:var(--rfp-card);overflow:hidden}
6938
+ .rfp-notice{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;padding:14px 16px;
6939
+ border:1px solid var(--rfp-line-2);border-radius:12px;background:var(--rfp-card);margin-top:16px}
6804
6940
  .rfp-notice-mark{font-family:var(--rfp-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
6805
- padding:2px 6px;border-radius:3px;background:var(--rfp-ink);color:var(--rfp-ground);white-space:nowrap}
6941
+ padding:2px 7px;border-radius:5px;background:var(--rfp-ink);color:var(--rfp-card);white-space:nowrap;height:fit-content}
6806
6942
  .rfp-notice p{margin:0;color:var(--rfp-ink-2);font-size:13px;max-width:68ch}
6807
6943
  .rfp-notice p+p{margin-top:6px}
6808
- .rfp-notice strong{color:var(--rfp-ink);font-weight:600}
6944
+ .rfp-notice strong{color:var(--rfp-ink)}
6809
6945
  .rfp-frame{margin:0 auto}
6810
6946
  @media (prefers-reduced-motion:reduce){.rfp *{transition:none!important;animation:none!important}}
6811
6947
  @media (max-width:900px){
6812
- .rfp{grid-template-columns:minmax(0,1fr);gap:0;padding:0 20px 72px}
6813
- .rfp-rail{position:static;max-height:none;padding:24px 0 0}
6948
+ .rfp-shell{grid-template-columns:minmax(0,1fr);gap:0;padding:0 20px 72px}
6949
+ .rfp-masthead{padding:36px 20px 28px}
6950
+ .rfp-rail{position:static;max-height:none;padding:20px 0 0}
6814
6951
  .rfp-nav{flex-direction:row;overflow-x:auto;gap:4px;padding-bottom:4px}
6815
6952
  .rfp-nav a{white-space:nowrap}
6816
- .rfp-ladder{grid-template-columns:minmax(0,1fr);gap:10px}
6817
6953
  .rfp-row{grid-template-columns:130px minmax(0,1fr)}
6818
6954
  .rfp-rowval{grid-column:1/-1;text-align:left}
6819
6955
  }
@@ -6878,7 +7014,7 @@ function renderThemeLinks(stylesheets, contents, inline) {
6878
7014
  // Entry point
6879
7015
  // ---------------------------------------------------------------------------
6880
7016
  function buildPreview(source, options) {
6881
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
7017
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
6882
7018
  const { usage, preview, tokens, model } = source;
6883
7019
  const fileName = (_a = options.file) !== null && _a !== void 0 ? _a : DEFAULT_FILE;
6884
7020
  const inline = (_b = options.inline) !== null && _b !== void 0 ? _b : true;
@@ -6893,6 +7029,7 @@ function buildPreview(source, options) {
6893
7029
  // entirely, so a theme without shadows shows no elevation plate rather than an empty box.
6894
7030
  const bodies = new Map();
6895
7031
  const counts = new Map();
7032
+ let pairings = { total: 0, passing: 0 };
6896
7033
  for (const [group, items] of groups) {
6897
7034
  const section = (_d = SECTION_OF[group]) !== null && _d !== void 0 ? _d : "other";
6898
7035
  counts.set(section, ((_e = counts.get(section)) !== null && _e !== void 0 ? _e : 0) + items.length);
@@ -6902,8 +7039,11 @@ function buildPreview(source, options) {
6902
7039
  if (!own.length)
6903
7040
  continue;
6904
7041
  let body = "";
6905
- if (section.id === "palette")
6906
- body = renderPalette(own.flatMap(([, l]) => l), tokenName);
7042
+ if (section.id === "palette") {
7043
+ const palette = renderPalette(own.flatMap(([, l]) => l), model, tokenName);
7044
+ body = palette.html;
7045
+ pairings = palette.pairings;
7046
+ }
6907
7047
  else if (section.id === "type")
6908
7048
  body = renderTypography(own.flatMap(([, l]) => l), tokenName);
6909
7049
  else if (section.id === "space")
@@ -6931,7 +7071,7 @@ function buildPreview(source, options) {
6931
7071
  for (const section of SECTIONS) {
6932
7072
  if (!bodies.has(section.id))
6933
7073
  continue;
6934
- navItems.push(`<a href="#rfp-${section.id}">${section.eyebrow}<span class="rfp-n">${(_h = counts.get(section.id)) !== null && _h !== void 0 ? _h : 0}</span></a>`);
7074
+ navItems.push(`<a href="#rfp-${section.id}">${esc(section.eyebrow)}<span class="rfp-n">${(_h = counts.get(section.id)) !== null && _h !== void 0 ? _h : 0}</span></a>`);
6935
7075
  }
6936
7076
  if (modesHtml)
6937
7077
  navItems.push(`<a href="#rfp-modes">Appearance</a>`);
@@ -6941,16 +7081,18 @@ function buildPreview(source, options) {
6941
7081
  // ── Masthead ────────────────────────────────────────────────────────────
6942
7082
  const title = (_j = options.title) !== null && _j !== void 0 ? _j : `${usage.format} theme`;
6943
7083
  const totalBytes = options.files.reduce((sum, f) => { var _a, _b, _c; return sum + ((_c = (_b = (_a = options.contents) === null || _a === void 0 ? void 0 : _a[f]) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0); }, 0);
6944
- const metaBits = [
6945
- `<span><i class="rfp-dot"></i> ${esc(usage.format)} · ${live ? "live" : "tokens only"}</span>`,
6946
- `<span>emit <b>${esc(options.plan.type)}</b></span>`,
6947
- `<span>${leaves.length} tokens</span>`,
6948
- `<span>${usage.recipes.length} recipes</span>`,
6949
- totalBytes ? `<span>${options.files.length} file(s) · ${bytes(totalBytes)}</span>` : "",
6950
- ].filter(Boolean);
7084
+ const subsystems = Object.keys(model.subsystems).length;
7085
+ const elements = Object.keys((_m = (_l = (_k = model.subsystems.globals) === null || _k === void 0 ? void 0 : _k.ruleSets) === null || _l === void 0 ? void 0 : _l.elements) !== null && _m !== void 0 ? _m : {}).length;
7086
+ const metrics = [
7087
+ { value: String(leaves.length), label: "tokens" },
7088
+ { value: String(subsystems), label: "subsystems" },
7089
+ { value: `${usage.recipes.length}${elements ? ` + ${elements}` : ""}`, label: elements ? "recipes + elements" : "recipes" },
7090
+ pairings.total ? { value: `${pairings.passing}/${pairings.total}`, label: "WCAG AA+ pairings" } : undefined,
7091
+ totalBytes ? { value: bytes(totalBytes), label: (_o = options.files[0]) !== null && _o !== void 0 ? _o : "output" } : undefined,
7092
+ ].filter((m) => m !== undefined);
6951
7093
  const modeAttribute = preview === null || preview === void 0 ? void 0 : preview.modeAttribute;
6952
7094
  const modes = collectModes(model);
6953
- const breakpoints = (_k = model.breakpoints) !== null && _k !== void 0 ? _k : {};
7095
+ const breakpoints = (_p = model.breakpoints) !== null && _p !== void 0 ? _p : {};
6954
7096
  const controls = [];
6955
7097
  if (modeAttribute && modes.length) {
6956
7098
  const buttons = [`<button type="button" data-rfp-mode data-value="" aria-pressed="true">auto</button>`]
@@ -6967,11 +7109,14 @@ function buildPreview(source, options) {
6967
7109
  }
6968
7110
  // ── Sections ────────────────────────────────────────────────────────────
6969
7111
  const sectionHtml = SECTIONS.filter(s => bodies.has(s.id))
6970
- .map(s => `<section class="rfp-section" id="rfp-${s.id}"><div class="rfp-section-head"><div>` +
6971
- `<div class="rfp-eyebrow">${s.eyebrow}</div><h2>${s.title}</h2></div></div>` +
6972
- (s.note ? `<p class="rfp-note">${s.note}</p>` : "") +
6973
- bodies.get(s.id) +
6974
- `</section>`)
7112
+ .map(s => {
7113
+ var _a;
7114
+ return `<section class="rfp-section" id="rfp-${s.id}"><div class="rfp-section-head">` +
7115
+ `<h2>${s.title}</h2><span class="rfp-count">${(_a = counts.get(s.id)) !== null && _a !== void 0 ? _a : 0} token(s)</span></div>` +
7116
+ (s.note ? `<p class="rfp-note">${s.note}</p>` : "") +
7117
+ bodies.get(s.id) +
7118
+ `</section>`;
7119
+ })
6975
7120
  .join("");
6976
7121
  const head = [
6977
7122
  `<meta charset="utf-8">`,
@@ -6985,15 +7130,28 @@ function buildPreview(source, options) {
6985
7130
  ]
6986
7131
  .filter(Boolean)
6987
7132
  .join("\n");
7133
+ // The masthead is the ONE place the chrome takes a hue, and it takes the theme's own first
7134
+ // palette — the theme colouring itself rather than the tool asserting a brand.
7135
+ const brand = mastheadColor(leaves);
7136
+ const mastheadStyle = brand
7137
+ ? ` style="background:${cssValue(brand.bg)};color:${cssValue(brand.fg)}"`
7138
+ : ` style="background:var(--rfp-ink);color:var(--rfp-card)"`;
7139
+ const indexHtml = renderIndex(bodies, counts, brand === null || brand === void 0 ? void 0 : brand.bg);
6988
7140
  const body = `<div class="rfp">` +
7141
+ `<header class="rfp-masthead"${mastheadStyle}>` +
7142
+ `<p class="rfp-kicker">Theme specimen · ${esc(usage.format)} · ${esc(options.plan.type)}${live ? "" : " · tokens only"}</p>` +
7143
+ `<h1>${esc(title)}</h1>` +
7144
+ `<div class="rfp-metrics">` +
7145
+ metrics.map(m => `<div class="rfp-metric"><b>${esc(m.value)}</b><span>${esc(m.label)}</span></div>`).join("") +
7146
+ `</div></header>` +
7147
+ `<div class="rfp-shell">` +
6989
7148
  `<aside class="rfp-rail"><div class="rfp-brand">refract preview</div>` +
6990
7149
  `<nav class="rfp-nav">${navItems.join("")}</nav></aside>` +
6991
7150
  `<main class="rfp-main">` +
6992
- `<header class="rfp-masthead"><div class="rfp-eyebrow">Theme specimen</div>` +
6993
- `<h1>${esc(title)}</h1><div class="rfp-meta">${metaBits.join("")}</div></header>` +
6994
7151
  (controls.length ? `<div class="rfp-controls">${controls.join("")}</div>` : "") +
6995
7152
  notes.map(n => `<p class="rfp-note">${esc(n)}</p>`).join("") +
6996
7153
  `<div class="rfp-frame" id="rfp-frame">` +
7154
+ indexHtml +
6997
7155
  sectionHtml +
6998
7156
  modesHtml +
6999
7157
  globalsHtml +
@@ -7002,7 +7160,7 @@ function buildPreview(source, options) {
7002
7160
  `<span class="rfp-tag">${live ? "rendered live" : "names only"}</span></div>` +
7003
7161
  renderRecipes(usage, preview, live) +
7004
7162
  `</section>` +
7005
- `</div></main></div>` +
7163
+ `</div></main></div></div>` +
7006
7164
  `<script>\n${CHROME_JS}\n</script>`;
7007
7165
  const html = `<!doctype html>\n<html lang="en"${modeAttribute ? ` data-rfp-mode-attr="${esc(modeAttribute)}"` : ""}>\n` +
7008
7166
  `<head>\n${head}\n</head>\n<body>\n${body}\n</body>\n</html>\n`;