@theme-registry/refract 0.1.4 → 0.1.5

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
@@ -8004,7 +8004,7 @@ const RATIO_VALUES = {
8004
8004
  * overlap, so a scaffolded literal and an authored `harmony:` key agree. `pentadic` is local — the
8005
8005
  * subsystem has no five-way scheme, and the scaffolder needs one to reach five brand colours.
8006
8006
  */
8007
- const SCHEME_ROTATIONS = {
8007
+ const SCHEME_ROTATIONS$1 = {
8008
8008
  complement: [180],
8009
8009
  analogous: [-30, 30],
8010
8010
  "split-complement": [150, 210],
@@ -8106,7 +8106,7 @@ const defaultSchemeFor = (brandCount) => {
8106
8106
  return "pentadic";
8107
8107
  };
8108
8108
  /** Schemes that produce exactly `brandCount − 1` members — the valid choices at that count. */
8109
- const schemesFor = (brandCount) => Object.keys(SCHEME_ROTATIONS).filter((s) => SCHEME_ROTATIONS[s].length === brandCount - 1);
8109
+ const schemesFor = (brandCount) => Object.keys(SCHEME_ROTATIONS$1).filter((s) => SCHEME_ROTATIONS$1[s].length === brandCount - 1);
8110
8110
  /**
8111
8111
  * The contrast gate. Builds the palette set, audits every text-on-base pairing, and walks the
8112
8112
  * lightness of each failing colour down one OKLCH point at a time until it clears the bar.
@@ -8202,7 +8202,7 @@ function scaffoldTheme(answers) {
8202
8202
  }
8203
8203
  else if (brandCount > 1) {
8204
8204
  const scheme = (_g = answers.scheme) !== null && _g !== void 0 ? _g : defaultSchemeFor(brandCount);
8205
- const rotations = SCHEME_ROTATIONS[scheme];
8205
+ const rotations = SCHEME_ROTATIONS$1[scheme];
8206
8206
  if (!rotations)
8207
8207
  throw new Error(`Unknown harmony scheme "${scheme}".`);
8208
8208
  rotations.slice(0, brandCount - 1).forEach((deg, i) => {
@@ -8460,6 +8460,35 @@ const cursorUp = (n) => (n > 0 ? `\u001b[${n}A` : "");
8460
8460
  const CLEAR_DOWN = "\u001b[0J";
8461
8461
  const HIDE_CURSOR = "\u001b[?25l";
8462
8462
  const SHOW_CURSOR = "\u001b[?25h";
8463
+ /**
8464
+ * Does this terminal do 24-bit colour? `COLORTERM` is the only real signal, and plenty of capable
8465
+ * terminals (Apple Terminal among them) never set it — so a missing value means "approximate", not
8466
+ * "give up". Gating swatches on truecolor alone would silently delete them for those users.
8467
+ */
8468
+ const truecolor = () => { var _a; return /truecolor|24bit/i.test((_a = process.env.COLORTERM) !== null && _a !== void 0 ? _a : ""); };
8469
+ /** Nearest xterm-256 colour-cube index for an RGB triple — the fallback when 24-bit isn't offered. */
8470
+ const to256 = (r, g, b) => {
8471
+ const axis = (v) => (v < 48 ? 0 : v < 114 ? 1 : Math.min(5, Math.round((v - 35) / 40)));
8472
+ return 16 + 36 * axis(r) + 6 * axis(g) + axis(b);
8473
+ };
8474
+ /**
8475
+ * `#rrggbb` → a coloured block: exact in 24-bit terminals, approximated in 256-colour ones, and
8476
+ * empty when there's no colour at all (a pipe, `NO_COLOR`) so redirected output stays clean.
8477
+ *
8478
+ * Approximating is right here — the swatch is a preview, and the exact value is written to the theme
8479
+ * file regardless. An approximately-right hue beats a blank space.
8480
+ */
8481
+ function swatch(hex, width = 2) {
8482
+ if (!useColor())
8483
+ return "";
8484
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
8485
+ if (!m)
8486
+ return "";
8487
+ const n = parseInt(m[1], 16);
8488
+ const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
8489
+ const bg = truecolor() ? `48;2;${r};${g};${b}` : `48;5;${to256(r, g, b)}`;
8490
+ return `\u001b[${bg}m${" ".repeat(width)}\u001b[0m`;
8491
+ }
8463
8492
  /** Decode a raw stdin chunk into a {@link ListKey}. Arrows arrive as escape sequences; j/k mirror vim. */
8464
8493
  function decodeKey(chunk) {
8465
8494
  switch (chunk) {
@@ -8650,13 +8679,24 @@ class Prompter {
8650
8679
  };
8651
8680
  let painted = 0;
8652
8681
  const help = multi ? "↑↓ move · space toggle · a all · enter confirm" : "↑↓ move · enter select";
8682
+ // Pad labels so the hints line up into a column — a ragged right edge is most of why a long
8683
+ // option list reads as noise.
8684
+ const widest = choices.reduce((w, c) => Math.max(w, c.label.length), 0);
8653
8685
  const frame = () => {
8654
8686
  const rows = choices.map((c, i) => {
8687
+ var _a;
8655
8688
  const here = i === state.cursor;
8656
- const mark = multi ? (state.selected.has(i) ? green("◉") : dim("◯")) : here ? green("❯") : " ";
8689
+ // Focus and selection are SEPARATE signals and both need to be visible: the caret says
8690
+ // where you are, the box says what's chosen. Showing only one (as this did) leaves a
8691
+ // multi-select where you can't tell what the keys will act on.
8692
+ const caret = here ? green("❯") : " ";
8693
+ const box = multi ? (state.selected.has(i) ? green("[✓]") : dim("[ ]")) : "";
8657
8694
  const name = here ? bold(c.label) : c.label;
8658
- const hint = c.hint ? ` ${dim(`— ${c.hint}`)}` : "";
8659
- return ` ${mark} ${name}${hint}`;
8695
+ const pad = " ".repeat(Math.max(0, widest - c.label.length));
8696
+ const blocks = ((_a = c.swatches) !== null && _a !== void 0 ? _a : []).map(s => swatch(s)).filter(Boolean).join(" ");
8697
+ const art = blocks ? ` ${blocks}` : "";
8698
+ const hint = c.hint ? ` ${dim(c.hint)}` : "";
8699
+ return ` ${caret} ${box ? `${box} ` : ""}${name}${pad}${art}${hint}`;
8660
8700
  });
8661
8701
  return [`${cyan("?")} ${bold(label)} ${dim(help)}`, ...rows].join("\n");
8662
8702
  };
@@ -8762,6 +8802,19 @@ class Prompter {
8762
8802
  }
8763
8803
  }
8764
8804
 
8805
+ /**
8806
+ * The hues a scheme derives from a seed — the same rotations the generator will bake, so the
8807
+ * preview beside each option is the palette you'd actually get, not an illustration.
8808
+ */
8809
+ const SCHEME_ROTATIONS = {
8810
+ complement: [180],
8811
+ analogous: [-30, 30],
8812
+ "split-complement": [150, 210],
8813
+ triadic: [120, 240],
8814
+ tetradic: [90, 180, 270],
8815
+ pentadic: [72, 144, 216, 288],
8816
+ };
8817
+ const schemeHues = (seedHex, scheme) => { var _a; return ((_a = SCHEME_ROTATIONS[scheme]) !== null && _a !== void 0 ? _a : []).map(deg => convertRgbToHex(parseColor(rotateHue(seedHex, deg)).rgb)); };
8765
8818
  /** What each harmony scheme is for, in one line — the names mean nothing to most people. */
8766
8819
  const SCHEME_HINTS = {
8767
8820
  complement: "one companion, 180° opposite — maximum separation",
@@ -8804,7 +8857,9 @@ async function promptCreateAnswers(p, given = {}) {
8804
8857
  var _a, _b, _c, _d, _e, _f, _g, _h;
8805
8858
  const seed = (_a = given.seed) !== null && _a !== void 0 ? _a : (await p.text("Primary colour", "#4c6ef5", isColor));
8806
8859
  const lightness = Math.round(rgbToOklch(parseColor(seed).rgb).L * 10) / 10;
8807
- p.write(` ${dim(`parsed · lightness ${lightness}% — lands at ≈${nearestLadderStep(lightness)} on the ladder`)}`);
8860
+ const seedHex = convertRgbToHex(parseColor(seed).rgb);
8861
+ const chip = swatch(seedHex, 3);
8862
+ p.write(` ${chip ? `${chip} ` : ""}${dim(`${seedHex} · lightness ${lightness}% — lands at ≈${nearestLadderStep(lightness)} on the ladder`)}`);
8808
8863
  p.write();
8809
8864
  const manual = Boolean(given.manual);
8810
8865
  let brandCount = 2;
@@ -8827,7 +8882,14 @@ async function promptCreateAnswers(p, given = {}) {
8827
8882
  const options = schemesFor(brandCount);
8828
8883
  scheme = (_c = given.scheme) !== null && _c !== void 0 ? _c : defaultSchemeFor(brandCount);
8829
8884
  if (!given.scheme && options.length > 1) {
8830
- scheme = await p.select("Harmony scheme", options.map(s => ({ value: s, label: s, hint: SCHEME_HINTS[s] })), Math.max(0, options.indexOf(scheme)));
8885
+ // Show the hues each scheme actually produces. "split-complement" is a word; two blocks of
8886
+ // real colour beside the seed is the thing you're choosing between.
8887
+ scheme = await p.select("Harmony scheme", options.map(s => ({
8888
+ value: s,
8889
+ label: s,
8890
+ hint: SCHEME_HINTS[s],
8891
+ swatches: [seedHex, ...schemeHues(seedHex, s)],
8892
+ })), Math.max(0, options.indexOf(scheme)));
8831
8893
  }
8832
8894
  if (scheme && brandCount > 1) {
8833
8895
  p.write(` ${dim(`→ ${scheme} · each member becomes its own palette with a full ladder`)}`);