diff --git a/docs/design-system.md b/docs/design-system.md index 09ff3c75e6..42458247dc 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -423,3 +423,33 @@ icons, subtitles, disabled states, and required-status text. Standard Button sizes use the 14px `text-label-sm` role while retaining their existing minimum heights. The extra-small capsule keeps its caption role. + +## Colorways and plugin gradients + +Plugin artwork uses existing palette colors, including the complete Appearance +ramps: Bee, Magenta, Iris, Cobalt, Citrus and Mono. Flame and Jade remain +independent artwork palettes. All twelve steps and +existing ink, pressed and link mappings reference explicit palette owners. +Values are extracted unchanged. Mode-specific owners (for example +`--jade-3-light`) remain available independently of the page mode; the resolved +`--jade-3` follows the nearest light/dark scope. This preserves mixed-mode +specimens as well as product Appearance choices. Registry and Color viewer +expose all twelve steps of every named family in Layer 0. No new colors are invented. +Artwork aliases resolve at the page root; their current step-9 endpoints are +identical in both themes. Nested opposite-mode artwork is not a supported preview. + +| Category | Top → bottom | Identity | +| --- | --- | --- | +| Communication | cyan 9 → jade 9 | Teal/green, separate from Agents. | +| Agents | blue 9 → iris 9 | Lighter blue over existing violet. | +| Agent tools | purple 9 → magenta 9 | Violet to rose. | +| Developer tools & integrations | orange 9 → flame 9 | Warm ember. | +| People & communities | red 9 → magenta 9 | Scarlet to rose. | +| Productivity | amber 9 → green 9 | Approved yellow-to-green direction retained. | + +The proposed registry entries are owned by Settings plugin catalog; their +complete gradients compose palette-backed endpoints. There is no artwork-literal +exception. Existing UI palettes, Appearance preference IDs and semantic roles +are unchanged. The mapping does not preserve the original fixed luminance, +chroma, contrast or 35-degree hue sweep. Inspect real catalog captures in both +themes; artwork and lighting also affect the resulting tiles. diff --git a/scripts/design-system/check-adoption.mjs b/scripts/design-system/check-adoption.mjs index 1679768fdc..b487e8cf50 100644 --- a/scripts/design-system/check-adoption.mjs +++ b/scripts/design-system/check-adoption.mjs @@ -42,7 +42,7 @@ const visualProperty = const controlSelector = /(?:^|[\s>+~])(?:button|input|textarea|select)(?:[.#][\w-]+|\[[^\]]*\]|:[\w-]+)*$|\.buzz-(?:button|input|textarea|radio|checkbox)\b/; const palette = - /--(?:neutral|purple|red|green|amber|blue|cyan|orange)-(?:\d+|action-pressed)\b|\b(?:bg|text|border|outline|ring|fill|stroke|shadow|from|via|to)-(?:neutral|purple|red|green|amber|blue|cyan|orange)-\d+\b/g; + /--(?:neutral|purple|red|green|amber|blue|cyan|orange|gray|chartreuse|bee|citrus|mono|flame|magenta|iris|cobalt|jade)-(?:\d+|action-pressed|contrast|ink|pressed|link(?:-hover)?)\b|\b(?:bg|text|border|outline|ring|fill|stroke|shadow|from|via|to)-(?:neutral|purple|red|green|amber|blue|cyan|orange|gray|chartreuse|bee|citrus|mono|flame|magenta|iris|cobalt|jade)-\d+\b/g; export function adoptionFindings(file, source) { const code = source diff --git a/scripts/design-system/check-color.mjs b/scripts/design-system/check-color.mjs index a8d211eccb..238aac7e44 100644 --- a/scripts/design-system/check-color.mjs +++ b/scripts/design-system/check-color.mjs @@ -223,6 +223,27 @@ auditLayers(); */ function auditLayers() { const css = readFileSync(TOKENS_FILE, "utf8"); + // Artwork endpoints follow the same palette ownership as other aliases. + // Complete plugin gradients may compose their own two palette-backed endpoints. + for (const match of css.matchAll(/(--art-[a-z0-9-]+):\s*([^;]+);/g)) { + const [declaration, name, value] = match; + const alias = + /^var\(--(?:(?:amber|blue|cyan|green|orange|purple|red)-(?:[1-9]|1[0-2])|(?:bee|citrus|mono|flame|magenta|iris|cobalt|jade)-(?:[1-9]|1[0-2]))\)$/.test( + value.trim(), + ); + const category = /^--art-plugin-(.+)-gradient$/.exec(name)?.[1]; + const composition = + category && + value.replace(/\s+/g, " ").trim() === + `linear-gradient( to bottom in oklch, var(--art-plugin-${category}-start), var(--art-plugin-${category}-end) )`; + if (!alias && !composition) + failures.push({ + at: "styles/tokens.css", + found: declaration, + why: "Artwork must reference an existing family step or compose its own palette-backed endpoints.", + }); + } + const rel = "styles/tokens.css"; /** Every `:root` or `.dark` block, concatenated. One per layer, so several. */ @@ -303,6 +324,45 @@ function auditLayers() { } } + // Appearance palettes retain explicit mode owners and scoped ramp bindings. + for (const family of [ + "bee", + "citrus", + "mono", + "flame", + "magenta", + "iris", + "cobalt", + "jade", + ]) { + for (let step = 1; step <= 12; step += 1) { + for (const [mode, block] of Object.entries(modes)) { + const owner = `--${family}-${step}-${mode}`; + if ( + !/^#[0-9a-f]{6}$/i.test( + read(css.slice(0, css.indexOf("LAYER 1 — FAMILIES")), owner) ?? "", + ) || + read(block, `--${family}-${step}`) !== `var(${owner})` + ) { + failures.push({ + at: `${rel} (${mode})`, + found: owner, + why: "Each Appearance step needs a literal mode owner and the matching scoped binding.", + }); + } + } + } + } + for (const match of css.matchAll( + /--accent-[a-z0-9-]+:\s*(#[0-9a-f]{3,8})/g, + )) { + failures.push({ + at: rel, + found: match[0], + why: "Accent mappings must reference named palette owners, not literals.", + }); + } + // Semantic names describe purpose even when both themes choose the same step. // Require every new semantic role to declare both modes and reference a token. const roles = [ diff --git a/scripts/flake-check.mjs b/scripts/flake-check.mjs index 359cb352bb..5b61286911 100644 --- a/scripts/flake-check.mjs +++ b/scripts/flake-check.mjs @@ -120,11 +120,10 @@ export function testBodies(source, lang, lines) { return [...bodies.values()].sort((a, b) => a.start - b.start); } -// Vitest `file:line` filters for the selected tests. Vitest lists an -// `it.each([…])(` at its second parenthesis but filters by the call's first -// line, so a listed line outside it finds no test. -export const vitestLines = (lines, bodies) => - lines.map((line) => bodies.find((body) => body.line === line).start); +// Runtime collection and execution use the same Vitest call-site locations. +// Keep those locations: a parameterized call's AST start may not be runnable. +export const vitestFilters = (file, selection) => + selection.whole ? [file] : selection.lines.map((line) => `${file}:${line}`); // The calls owning every line of a range, or undefined once a line lies // outside them all. @@ -297,11 +296,7 @@ function select(base = "HEAD^1", head = "HEAD") { let entries; let note; if (runner === "vitest") { - entries = selection.whole - ? [file] - : vitestLines(selection.lines, bodies).map( - (line) => `${file}:${line}`, - ); + entries = vitestFilters(file, selection); note = selection.whole ? `whole file (${selection.reason})` : `${selection.lines.length} changed test(s)`; diff --git a/src/app/Settings.module.css b/src/app/Settings.module.css index 871eddc39d..bb4c4d19b2 100644 --- a/src/app/Settings.module.css +++ b/src/app/Settings.module.css @@ -297,13 +297,14 @@ } } -/* Shared category gradients preserve their original cross-color transitions. */ +/* Category tiles use shared palette pairs. Neutral glyphs on the teal, ember + and yellow/green tiles avoid tints inherited from the former gradients. */ .pluginIcon[data-branded][data-category="communication"] { --plugin-icon-gradient: var(--art-plugin-communication-gradient); } .pluginIcon[data-branded][data-category="communication"] img { - filter: hue-rotate(-12deg) saturate(0.8); + filter: grayscale(1); } .pluginIcon[data-branded][data-category="agents"] { @@ -330,7 +331,7 @@ .pluginIcon[data-branded][data-category="developer-tools-and-integrations"] img { - filter: hue-rotate(126deg) saturate(0.8); + filter: grayscale(1); } .pluginIcon[data-branded][data-category="people-and-communities"] { @@ -346,7 +347,7 @@ } .pluginIcon[data-branded][data-category="productivity"] img { - filter: hue-rotate(-169deg) saturate(0.8); + filter: grayscale(1); } :root[data-color-mode="dark"] .pluginIcon[data-branded] { diff --git a/src/shared/design-system/styles/tokens.css b/src/shared/design-system/styles/tokens.css index 49cf75556c..49ef097368 100644 --- a/src/shared/design-system/styles/tokens.css +++ b/src/shared/design-system/styles/tokens.css @@ -54,6 +54,386 @@ component — families name the jobs, roles name the meanings. ============================================================ */ +/* Existing Appearance values, extracted unchanged. Explicit mode owners allow + a light specimen inside a dark page (and the reverse) without inheriting + the wrong resolved ramp. Numeric steps, ink, pressed and link colors keep their authored values. */ +:root { + --magenta-1-light: #fffbfc; + --magenta-2-light: #fff7fa; + --magenta-3-light: #ffeef3; + --magenta-4-light: #fee2ec; + --magenta-5-light: #fed8e4; + --magenta-6-light: #f9cbdb; + --magenta-7-light: #f1bbce; + --magenta-8-light: #e7a4bd; + --magenta-9-light: #df1e85; + --magenta-10-light: #db2984; + --magenta-11-light: #6c3149; + --magenta-12-light: #42242f; + --magenta-contrast-light: #ffffff; + --magenta-ink-light: #ffffff; + --magenta-pressed-light: #d12c7f; + --magenta-link-light: #84034b; + --magenta-link-hover-light: #ffeef3; + --magenta-1-dark: #120e10; + --magenta-2-dark: #191415; + --magenta-3-dark: #2b1f23; + --magenta-4-dark: #38252c; + --magenta-5-dark: #452d36; + --magenta-6-dark: #553843; + --magenta-7-dark: #6b4754; + --magenta-8-dark: #865a6a; + --magenta-9-dark: #df1e85; + --magenta-10-dark: #db2984; + --magenta-11-dark: #f4bccf; + --magenta-12-dark: #feeaf1; + --magenta-contrast-dark: #ffffff; + --magenta-ink-dark: #ffffff; + --magenta-pressed-dark: #d12c7f; + --magenta-link-dark: #fdb6d0; + --magenta-link-hover-dark: #2b1f23; + --iris-1-light: #fbfcff; + --iris-2-light: #f8f9ff; + --iris-3-light: #f0f2ff; + --iris-4-light: #e6eafe; + --iris-5-light: #dde1fe; + --iris-6-light: #d2d7fd; + --iris-7-light: #c2c8f8; + --iris-8-light: #aeb6f0; + --iris-9-light: #5f55fa; + --iris-10-light: #5a4ef2; + --iris-11-light: #3d407a; + --iris-12-light: #282b48; + --iris-contrast-light: #ffffff; + --iris-ink-light: #ffffff; + --iris-pressed-light: #5648ea; + --iris-link-light: #3c04c3; + --iris-link-hover-light: #f0f2ff; + --iris-1-dark: #0f0f13; + --iris-2-dark: #14151a; + --iris-3-dark: #20212e; + --iris-4-dark: #272a3c; + --iris-5-dark: #31344a; + --iris-6-dark: #3c405b; + --iris-7-dark: #4c4f72; + --iris-8-dark: #60658f; + --iris-9-dark: #5f55fa; + --iris-10-dark: #5a4ef2; + --iris-11-dark: #c3cafb; + --iris-12-dark: #edf0fe; + --iris-contrast-dark: #ffffff; + --iris-ink-dark: #ffffff; + --iris-pressed-dark: #5648ea; + --iris-link-dark: #c2cafd; + --iris-link-hover-dark: #20212e; + --cobalt-1-light: #fafcff; + --cobalt-2-light: #f6faff; + --cobalt-3-light: #ecf4ff; + --cobalt-4-light: #dfecfe; + --cobalt-5-light: #d3e5fe; + --cobalt-6-light: #c3dcfc; + --cobalt-7-light: #b0cff8; + --cobalt-8-light: #97bdf0; + --cobalt-9-light: #1475e1; + --cobalt-10-light: #2176d9; + --cobalt-11-light: #264975; + --cobalt-12-light: #1e2f45; + --cobalt-contrast-light: #ffffff; + --cobalt-ink-light: #ffffff; + --cobalt-pressed-light: #2271cf; + --cobalt-link-light: #02468e; + --cobalt-link-hover-light: #ecf4ff; + --cobalt-1-dark: #0e1012; + --cobalt-2-dark: #131519; + --cobalt-3-dark: #1c232c; + --cobalt-4-dark: #212c3a; + --cobalt-5-dark: #293748; + --cobalt-6-dark: #334358; + --cobalt-7-dark: #40546f; + --cobalt-8-dark: #516b8c; + --cobalt-9-dark: #1475e1; + --cobalt-10-dark: #2176d9; + --cobalt-11-dark: #b1d1fa; + --cobalt-12-dark: #e8f2fe; + --cobalt-contrast-dark: #ffffff; + --cobalt-ink-dark: #ffffff; + --cobalt-pressed-dark: #2271cf; + --cobalt-link-dark: #afd1fe; + --cobalt-link-hover-dark: #1c232c; + --bee-1-light: #fdfcf8; + --bee-2-light: #fcf9f1; + --bee-3-light: #faf3df; + --bee-4-light: #f5eace; + --bee-5-light: #f1e3be; + --bee-6-light: #e8d9af; + --bee-7-light: #ddcb98; + --bee-8-light: #d0b979; + --bee-9-light: #f5c518; + --bee-10-light: #fdcd2a; + --bee-11-light: #584504; + --bee-12-light: #362d13; + --bee-contrast-light: #000000; + --bee-ink-light: #000000; + --bee-pressed-light: #ffd763; + --bee-link-light: #594501; + --bee-link-hover-light: #faf3df; + --bee-1-dark: #100f0c; + --bee-2-dark: #171511; + --bee-3-dark: #252118; + --bee-4-dark: #312b1c; + --bee-5-dark: #3d3521; + --bee-6-dark: #4a412a; + --bee-7-dark: #5c5234; + --bee-8-dark: #766842; + --bee-9-dark: #f5c518; + --bee-10-dark: #fdcd2a; + --bee-11-dark: #e0cd97; + --bee-12-dark: #f6f0df; + --bee-contrast-dark: #000000; + --bee-ink-dark: #000000; + --bee-pressed-dark: #ffd763; + --bee-link-dark: #f2c94e; + --bee-link-hover-dark: #252118; + --citrus-1-light: #fcfdf7; + --citrus-2-light: #f8fbef; + --citrus-3-light: #f1f6de; + --citrus-4-light: #e8f0cd; + --citrus-5-light: #e0e9bc; + --citrus-6-light: #d5e1aa; + --citrus-7-light: #c8d495; + --citrus-8-light: #b6c378; + --citrus-9-light: #beda36; + --citrus-10-light: #bdd836; + --citrus-11-light: #485311; + --citrus-12-light: #2b3110; + --citrus-contrast-light: #000000; + --citrus-ink-light: #000000; + --citrus-pressed-light: #bcd83a; + --citrus-link-light: #485400; + --citrus-link-hover-light: #f1f6de; + --citrus-1-dark: #0f100c; + --citrus-2-dark: #15170f; + --citrus-3-dark: #202413; + --citrus-4-dark: #292e16; + --citrus-5-dark: #333a18; + --citrus-6-dark: #3e471f; + --citrus-7-dark: #4f5827; + --citrus-8-dark: #637033; + --citrus-9-dark: #beda36; + --citrus-10-dark: #bdd836; + --citrus-11-dark: #cee28b; + --citrus-12-dark: #edf7d2; + --citrus-contrast-dark: #000000; + --citrus-ink-dark: #000000; + --citrus-pressed-dark: #bcd83a; + --citrus-link-dark: #cbe648; + --citrus-link-hover-dark: #202413; + --mono-1-light: #fcfcfc; + --mono-2-light: #f9f9f9; + --mono-3-light: #f3f3f3; + --mono-4-light: #ebebeb; + --mono-5-light: #e3e3e3; + --mono-6-light: #d9d9d9; + --mono-7-light: #cccccc; + --mono-8-light: #bababa; + --mono-9-light: #1a1a1a; + --mono-10-light: #141414; + --mono-11-light: #484848; + --mono-12-light: #2e2e2e; + --mono-contrast-light: #ffffff; + --mono-ink-light: #ffffff; + --mono-pressed-light: #0f0f0f; + --mono-link-light: #484848; + --mono-link-hover-light: #f3f3f3; + --mono-1-dark: #0f0f0f; + --mono-2-dark: #151515; + --mono-3-dark: #222222; + --mono-4-dark: #2b2b2b; + --mono-5-dark: #363636; + --mono-6-dark: #424242; + --mono-7-dark: #535353; + --mono-8-dark: #696969; + --mono-9-dark: #e6e6e6; + --mono-10-dark: #eeeeee; + --mono-11-dark: #cecece; + --mono-12-dark: #f0f0f0; + --mono-contrast-dark: #000000; + --mono-ink-dark: #000000; + --mono-pressed-dark: #f7f7f7; + --mono-link-dark: #cecece; + --mono-link-hover-dark: #222222; + --flame-1-light: #fefcfc; + --flame-2-light: #fff8f6; + --flame-3-light: #ffeeea; + --flame-4-light: #ffe4de; + --flame-5-light: #fedad0; + --flame-6-light: #ffccbf; + --flame-7-light: #feb9a7; + --flame-8-light: #fe9d85; + --flame-9-light: #d93a0c; + --flame-10-light: #c93202; + --flame-11-light: #a4351a; + --flame-12-light: #4f1b0f; + --flame-contrast-light: #ffffff; + --flame-ink-light: #ffffff; + --flame-pressed-light: #b92d01; + --flame-link-light: var(--flame-11-light); + --flame-link-hover-light: var(--flame-3-light); + --flame-1-dark: #140e0c; + --flame-2-dark: #1d1311; + --flame-3-dark: #301c16; + --flame-4-dark: #40221b; + --flame-5-dark: #502920; + --flame-6-dark: #633327; + --flame-7-dark: #7c3f30; + --flame-8-dark: #9f4e3a; + --flame-9-dark: #d93a0c; + --flame-10-dark: #c93202; + --flame-11-dark: #ffaf9a; + --flame-12-dark: #ffebe6; + --flame-contrast-dark: #ffffff; + --flame-ink-dark: #ffffff; + --flame-pressed-dark: #b92d01; + --flame-link-dark: var(--flame-11-dark); + --flame-link-hover-dark: var(--flame-3-dark); + --jade-1-light: #f9fefc; + --jade-2-light: #f2fcf8; + --jade-3-light: #e2f9f0; + --jade-4-light: #d3f4e7; + --jade-5-light: #c4eede; + --jade-6-light: #b4e6d3; + --jade-7-light: #9edbc5; + --jade-8-light: #81cdb2; + --jade-9-light: #0b8467; + --jade-10-light: #06795e; + --jade-11-light: #226a55; + --jade-12-light: #15352a; + --jade-contrast-light: #ffffff; + --jade-ink-light: #ffffff; + --jade-pressed-light: #026d54; + --jade-link-light: var(--jade-11-light); + --jade-link-hover-light: var(--jade-3-light); + --jade-1-dark: #0d100f; + --jade-2-dark: #121715; + --jade-3-dark: #192520; + --jade-4-dark: #1e3029; + --jade-5-dark: #243b33; + --jade-6-dark: #2d493f; + --jade-7-dark: #375b4f; + --jade-8-dark: #447464; + --jade-9-dark: #0b8467; + --jade-10-dark: #06795e; + --jade-11-dark: #90d1ba; + --jade-12-dark: #e2f6ee; + --jade-contrast-dark: #ffffff; + --jade-ink-dark: #ffffff; + --jade-pressed-dark: #026d54; + --jade-link-dark: var(--jade-11-dark); + --jade-link-hover-dark: var(--jade-3-dark); +} + +.light, +[data-color-mode="light"], +:root { + /* Named palettes resolve the current scope mode. */ + --bee-1: var(--bee-1-light); + --bee-2: var(--bee-2-light); + --bee-3: var(--bee-3-light); + --bee-4: var(--bee-4-light); + --bee-5: var(--bee-5-light); + --bee-6: var(--bee-6-light); + --bee-7: var(--bee-7-light); + --bee-8: var(--bee-8-light); + --bee-9: var(--bee-9-light); + --bee-10: var(--bee-10-light); + --bee-11: var(--bee-11-light); + --bee-12: var(--bee-12-light); + --magenta-1: var(--magenta-1-light); + --magenta-2: var(--magenta-2-light); + --magenta-3: var(--magenta-3-light); + --magenta-4: var(--magenta-4-light); + --magenta-5: var(--magenta-5-light); + --magenta-6: var(--magenta-6-light); + --magenta-7: var(--magenta-7-light); + --magenta-8: var(--magenta-8-light); + --magenta-9: var(--magenta-9-light); + --magenta-10: var(--magenta-10-light); + --magenta-11: var(--magenta-11-light); + --magenta-12: var(--magenta-12-light); + --iris-1: var(--iris-1-light); + --iris-2: var(--iris-2-light); + --iris-3: var(--iris-3-light); + --iris-4: var(--iris-4-light); + --iris-5: var(--iris-5-light); + --iris-6: var(--iris-6-light); + --iris-7: var(--iris-7-light); + --iris-8: var(--iris-8-light); + --iris-9: var(--iris-9-light); + --iris-10: var(--iris-10-light); + --iris-11: var(--iris-11-light); + --iris-12: var(--iris-12-light); + --cobalt-1: var(--cobalt-1-light); + --cobalt-2: var(--cobalt-2-light); + --cobalt-3: var(--cobalt-3-light); + --cobalt-4: var(--cobalt-4-light); + --cobalt-5: var(--cobalt-5-light); + --cobalt-6: var(--cobalt-6-light); + --cobalt-7: var(--cobalt-7-light); + --cobalt-8: var(--cobalt-8-light); + --cobalt-9: var(--cobalt-9-light); + --cobalt-10: var(--cobalt-10-light); + --cobalt-11: var(--cobalt-11-light); + --cobalt-12: var(--cobalt-12-light); + --citrus-1: var(--citrus-1-light); + --citrus-2: var(--citrus-2-light); + --citrus-3: var(--citrus-3-light); + --citrus-4: var(--citrus-4-light); + --citrus-5: var(--citrus-5-light); + --citrus-6: var(--citrus-6-light); + --citrus-7: var(--citrus-7-light); + --citrus-8: var(--citrus-8-light); + --citrus-9: var(--citrus-9-light); + --citrus-10: var(--citrus-10-light); + --citrus-11: var(--citrus-11-light); + --citrus-12: var(--citrus-12-light); + --mono-1: var(--mono-1-light); + --mono-2: var(--mono-2-light); + --mono-3: var(--mono-3-light); + --mono-4: var(--mono-4-light); + --mono-5: var(--mono-5-light); + --mono-6: var(--mono-6-light); + --mono-7: var(--mono-7-light); + --mono-8: var(--mono-8-light); + --mono-9: var(--mono-9-light); + --mono-10: var(--mono-10-light); + --mono-11: var(--mono-11-light); + --mono-12: var(--mono-12-light); + --flame-1: var(--flame-1-light); + --flame-2: var(--flame-2-light); + --flame-3: var(--flame-3-light); + --flame-4: var(--flame-4-light); + --flame-5: var(--flame-5-light); + --flame-6: var(--flame-6-light); + --flame-7: var(--flame-7-light); + --flame-8: var(--flame-8-light); + --flame-9: var(--flame-9-light); + --flame-10: var(--flame-10-light); + --flame-11: var(--flame-11-light); + --flame-12: var(--flame-12-light); + --jade-1: var(--jade-1-light); + --jade-2: var(--jade-2-light); + --jade-3: var(--jade-3-light); + --jade-4: var(--jade-4-light); + --jade-5: var(--jade-5-light); + --jade-6: var(--jade-6-light); + --jade-7: var(--jade-7-light); + --jade-8: var(--jade-8-light); + --jade-9: var(--jade-9-light); + --jade-10: var(--jade-10-light); + --jade-11: var(--jade-11-light); + --jade-12: var(--jade-12-light); +} + .light, :root { /* gray */ @@ -157,6 +537,108 @@ --orange-12: #582d1d; } +:root[data-color-mode="dark"], +[data-color-mode="dark"], +.dark { + /* Named palettes resolve the current scope mode. */ + --bee-1: var(--bee-1-dark); + --bee-2: var(--bee-2-dark); + --bee-3: var(--bee-3-dark); + --bee-4: var(--bee-4-dark); + --bee-5: var(--bee-5-dark); + --bee-6: var(--bee-6-dark); + --bee-7: var(--bee-7-dark); + --bee-8: var(--bee-8-dark); + --bee-9: var(--bee-9-dark); + --bee-10: var(--bee-10-dark); + --bee-11: var(--bee-11-dark); + --bee-12: var(--bee-12-dark); + --magenta-1: var(--magenta-1-dark); + --magenta-2: var(--magenta-2-dark); + --magenta-3: var(--magenta-3-dark); + --magenta-4: var(--magenta-4-dark); + --magenta-5: var(--magenta-5-dark); + --magenta-6: var(--magenta-6-dark); + --magenta-7: var(--magenta-7-dark); + --magenta-8: var(--magenta-8-dark); + --magenta-9: var(--magenta-9-dark); + --magenta-10: var(--magenta-10-dark); + --magenta-11: var(--magenta-11-dark); + --magenta-12: var(--magenta-12-dark); + --iris-1: var(--iris-1-dark); + --iris-2: var(--iris-2-dark); + --iris-3: var(--iris-3-dark); + --iris-4: var(--iris-4-dark); + --iris-5: var(--iris-5-dark); + --iris-6: var(--iris-6-dark); + --iris-7: var(--iris-7-dark); + --iris-8: var(--iris-8-dark); + --iris-9: var(--iris-9-dark); + --iris-10: var(--iris-10-dark); + --iris-11: var(--iris-11-dark); + --iris-12: var(--iris-12-dark); + --cobalt-1: var(--cobalt-1-dark); + --cobalt-2: var(--cobalt-2-dark); + --cobalt-3: var(--cobalt-3-dark); + --cobalt-4: var(--cobalt-4-dark); + --cobalt-5: var(--cobalt-5-dark); + --cobalt-6: var(--cobalt-6-dark); + --cobalt-7: var(--cobalt-7-dark); + --cobalt-8: var(--cobalt-8-dark); + --cobalt-9: var(--cobalt-9-dark); + --cobalt-10: var(--cobalt-10-dark); + --cobalt-11: var(--cobalt-11-dark); + --cobalt-12: var(--cobalt-12-dark); + --citrus-1: var(--citrus-1-dark); + --citrus-2: var(--citrus-2-dark); + --citrus-3: var(--citrus-3-dark); + --citrus-4: var(--citrus-4-dark); + --citrus-5: var(--citrus-5-dark); + --citrus-6: var(--citrus-6-dark); + --citrus-7: var(--citrus-7-dark); + --citrus-8: var(--citrus-8-dark); + --citrus-9: var(--citrus-9-dark); + --citrus-10: var(--citrus-10-dark); + --citrus-11: var(--citrus-11-dark); + --citrus-12: var(--citrus-12-dark); + --mono-1: var(--mono-1-dark); + --mono-2: var(--mono-2-dark); + --mono-3: var(--mono-3-dark); + --mono-4: var(--mono-4-dark); + --mono-5: var(--mono-5-dark); + --mono-6: var(--mono-6-dark); + --mono-7: var(--mono-7-dark); + --mono-8: var(--mono-8-dark); + --mono-9: var(--mono-9-dark); + --mono-10: var(--mono-10-dark); + --mono-11: var(--mono-11-dark); + --mono-12: var(--mono-12-dark); + --flame-1: var(--flame-1-dark); + --flame-2: var(--flame-2-dark); + --flame-3: var(--flame-3-dark); + --flame-4: var(--flame-4-dark); + --flame-5: var(--flame-5-dark); + --flame-6: var(--flame-6-dark); + --flame-7: var(--flame-7-dark); + --flame-8: var(--flame-8-dark); + --flame-9: var(--flame-9-dark); + --flame-10: var(--flame-10-dark); + --flame-11: var(--flame-11-dark); + --flame-12: var(--flame-12-dark); + --jade-1: var(--jade-1-dark); + --jade-2: var(--jade-2-dark); + --jade-3: var(--jade-3-dark); + --jade-4: var(--jade-4-dark); + --jade-5: var(--jade-5-dark); + --jade-6: var(--jade-6-dark); + --jade-7: var(--jade-7-dark); + --jade-8: var(--jade-8-dark); + --jade-9: var(--jade-9-dark); + --jade-10: var(--jade-10-dark); + --jade-11: var(--jade-11-dark); + --jade-12: var(--jade-12-dark); +} + :root[data-color-mode="dark"], .dark { /* gray */ @@ -499,6 +981,54 @@ --texture-dots-color: color-mix(in srgb, var(--bg-app) 92%, white); } +/* Plugin artwork aliases: existing palette steps own values in each theme. + Distinct solid-color family pairs preserve visible hue variation. + No fixed-luminance or 35-degree hue-sweep guarantee remains. */ +:root { + --art-plugin-communication-start: var(--cyan-9); + --art-plugin-communication-end: var(--jade-9); + --art-plugin-communication-gradient: linear-gradient( + to bottom in oklch, + var(--art-plugin-communication-start), + var(--art-plugin-communication-end) + ); + --art-plugin-agents-start: var(--blue-9); + --art-plugin-agents-end: var(--iris-9); + --art-plugin-agents-gradient: linear-gradient( + to bottom in oklch, + var(--art-plugin-agents-start), + var(--art-plugin-agents-end) + ); + --art-plugin-agent-tools-start: var(--purple-9); + --art-plugin-agent-tools-end: var(--magenta-9); + --art-plugin-agent-tools-gradient: linear-gradient( + to bottom in oklch, + var(--art-plugin-agent-tools-start), + var(--art-plugin-agent-tools-end) + ); + --art-plugin-developer-tools-and-integrations-start: var(--orange-9); + --art-plugin-developer-tools-and-integrations-end: var(--flame-9); + --art-plugin-developer-tools-and-integrations-gradient: linear-gradient( + to bottom in oklch, + var(--art-plugin-developer-tools-and-integrations-start), + var(--art-plugin-developer-tools-and-integrations-end) + ); + --art-plugin-people-and-communities-start: var(--red-9); + --art-plugin-people-and-communities-end: var(--magenta-9); + --art-plugin-people-and-communities-gradient: linear-gradient( + to bottom in oklch, + var(--art-plugin-people-and-communities-start), + var(--art-plugin-people-and-communities-end) + ); + --art-plugin-productivity-start: var(--amber-9); + --art-plugin-productivity-end: var(--green-9); + --art-plugin-productivity-gradient: linear-gradient( + to bottom in oklch, + var(--art-plugin-productivity-start), + var(--art-plugin-productivity-end) + ); +} + /* ============================================================ LEGACY BACKDROP SPECIMENS Numbered slots remain only for the glass study in the viewer. @@ -1036,226 +1566,226 @@ :root[data-accent="magenta"], [data-accent="magenta"] { - --accent-1-light: #fffbfc; - --accent-2-light: #fff7fa; - --accent-3-light: #ffeef3; - --accent-4-light: #fee2ec; - --accent-5-light: #fed8e4; - --accent-6-light: #f9cbdb; - --accent-7-light: #f1bbce; - --accent-8-light: #e7a4bd; - --accent-9-light: #df1e85; - --accent-10-light: #db2984; - --accent-11-light: #6c3149; - --accent-12-light: #42242f; - --accent-contrast-light: #ffffff; - --accent-ink-light: #ffffff; - --accent-pressed-light: #d12c7f; - --accent-link-light: #84034b; - --accent-link-hover-light: #ffeef3; - --accent-1-dark: #120e10; - --accent-2-dark: #191415; - --accent-3-dark: #2b1f23; - --accent-4-dark: #38252c; - --accent-5-dark: #452d36; - --accent-6-dark: #553843; - --accent-7-dark: #6b4754; - --accent-8-dark: #865a6a; - --accent-9-dark: #df1e85; - --accent-10-dark: #db2984; - --accent-11-dark: #f4bccf; - --accent-12-dark: #feeaf1; - --accent-contrast-dark: #ffffff; - --accent-ink-dark: #ffffff; - --accent-pressed-dark: #d12c7f; - --accent-link-dark: #fdb6d0; - --accent-link-hover-dark: #2b1f23; + --accent-1-light: var(--magenta-1-light); + --accent-2-light: var(--magenta-2-light); + --accent-3-light: var(--magenta-3-light); + --accent-4-light: var(--magenta-4-light); + --accent-5-light: var(--magenta-5-light); + --accent-6-light: var(--magenta-6-light); + --accent-7-light: var(--magenta-7-light); + --accent-8-light: var(--magenta-8-light); + --accent-9-light: var(--magenta-9-light); + --accent-10-light: var(--magenta-10-light); + --accent-11-light: var(--magenta-11-light); + --accent-12-light: var(--magenta-12-light); + --accent-contrast-light: var(--magenta-contrast-light); + --accent-ink-light: var(--magenta-ink-light); + --accent-pressed-light: var(--magenta-pressed-light); + --accent-link-light: var(--magenta-link-light); + --accent-link-hover-light: var(--magenta-link-hover-light); + --accent-1-dark: var(--magenta-1-dark); + --accent-2-dark: var(--magenta-2-dark); + --accent-3-dark: var(--magenta-3-dark); + --accent-4-dark: var(--magenta-4-dark); + --accent-5-dark: var(--magenta-5-dark); + --accent-6-dark: var(--magenta-6-dark); + --accent-7-dark: var(--magenta-7-dark); + --accent-8-dark: var(--magenta-8-dark); + --accent-9-dark: var(--magenta-9-dark); + --accent-10-dark: var(--magenta-10-dark); + --accent-11-dark: var(--magenta-11-dark); + --accent-12-dark: var(--magenta-12-dark); + --accent-contrast-dark: var(--magenta-contrast-dark); + --accent-ink-dark: var(--magenta-ink-dark); + --accent-pressed-dark: var(--magenta-pressed-dark); + --accent-link-dark: var(--magenta-link-dark); + --accent-link-hover-dark: var(--magenta-link-hover-dark); } :root[data-accent="iris"], [data-accent="iris"] { - --accent-1-light: #fbfcff; - --accent-2-light: #f8f9ff; - --accent-3-light: #f0f2ff; - --accent-4-light: #e6eafe; - --accent-5-light: #dde1fe; - --accent-6-light: #d2d7fd; - --accent-7-light: #c2c8f8; - --accent-8-light: #aeb6f0; - --accent-9-light: #5f55fa; - --accent-10-light: #5a4ef2; - --accent-11-light: #3d407a; - --accent-12-light: #282b48; - --accent-contrast-light: #ffffff; - --accent-ink-light: #ffffff; - --accent-pressed-light: #5648ea; - --accent-link-light: #3c04c3; - --accent-link-hover-light: #f0f2ff; - --accent-1-dark: #0f0f13; - --accent-2-dark: #14151a; - --accent-3-dark: #20212e; - --accent-4-dark: #272a3c; - --accent-5-dark: #31344a; - --accent-6-dark: #3c405b; - --accent-7-dark: #4c4f72; - --accent-8-dark: #60658f; - --accent-9-dark: #5f55fa; - --accent-10-dark: #5a4ef2; - --accent-11-dark: #c3cafb; - --accent-12-dark: #edf0fe; - --accent-contrast-dark: #ffffff; - --accent-ink-dark: #ffffff; - --accent-pressed-dark: #5648ea; - --accent-link-dark: #c2cafd; - --accent-link-hover-dark: #20212e; + --accent-1-light: var(--iris-1-light); + --accent-2-light: var(--iris-2-light); + --accent-3-light: var(--iris-3-light); + --accent-4-light: var(--iris-4-light); + --accent-5-light: var(--iris-5-light); + --accent-6-light: var(--iris-6-light); + --accent-7-light: var(--iris-7-light); + --accent-8-light: var(--iris-8-light); + --accent-9-light: var(--iris-9-light); + --accent-10-light: var(--iris-10-light); + --accent-11-light: var(--iris-11-light); + --accent-12-light: var(--iris-12-light); + --accent-contrast-light: var(--iris-contrast-light); + --accent-ink-light: var(--iris-ink-light); + --accent-pressed-light: var(--iris-pressed-light); + --accent-link-light: var(--iris-link-light); + --accent-link-hover-light: var(--iris-link-hover-light); + --accent-1-dark: var(--iris-1-dark); + --accent-2-dark: var(--iris-2-dark); + --accent-3-dark: var(--iris-3-dark); + --accent-4-dark: var(--iris-4-dark); + --accent-5-dark: var(--iris-5-dark); + --accent-6-dark: var(--iris-6-dark); + --accent-7-dark: var(--iris-7-dark); + --accent-8-dark: var(--iris-8-dark); + --accent-9-dark: var(--iris-9-dark); + --accent-10-dark: var(--iris-10-dark); + --accent-11-dark: var(--iris-11-dark); + --accent-12-dark: var(--iris-12-dark); + --accent-contrast-dark: var(--iris-contrast-dark); + --accent-ink-dark: var(--iris-ink-dark); + --accent-pressed-dark: var(--iris-pressed-dark); + --accent-link-dark: var(--iris-link-dark); + --accent-link-hover-dark: var(--iris-link-hover-dark); } :root[data-accent="cobalt"], [data-accent="cobalt"] { - --accent-1-light: #fafcff; - --accent-2-light: #f6faff; - --accent-3-light: #ecf4ff; - --accent-4-light: #dfecfe; - --accent-5-light: #d3e5fe; - --accent-6-light: #c3dcfc; - --accent-7-light: #b0cff8; - --accent-8-light: #97bdf0; - --accent-9-light: #1475e1; - --accent-10-light: #2176d9; - --accent-11-light: #264975; - --accent-12-light: #1e2f45; - --accent-contrast-light: #ffffff; - --accent-ink-light: #ffffff; - --accent-pressed-light: #2271cf; - --accent-link-light: #02468e; - --accent-link-hover-light: #ecf4ff; - --accent-1-dark: #0e1012; - --accent-2-dark: #131519; - --accent-3-dark: #1c232c; - --accent-4-dark: #212c3a; - --accent-5-dark: #293748; - --accent-6-dark: #334358; - --accent-7-dark: #40546f; - --accent-8-dark: #516b8c; - --accent-9-dark: #1475e1; - --accent-10-dark: #2176d9; - --accent-11-dark: #b1d1fa; - --accent-12-dark: #e8f2fe; - --accent-contrast-dark: #ffffff; - --accent-ink-dark: #ffffff; - --accent-pressed-dark: #2271cf; - --accent-link-dark: #afd1fe; - --accent-link-hover-dark: #1c232c; + --accent-1-light: var(--cobalt-1-light); + --accent-2-light: var(--cobalt-2-light); + --accent-3-light: var(--cobalt-3-light); + --accent-4-light: var(--cobalt-4-light); + --accent-5-light: var(--cobalt-5-light); + --accent-6-light: var(--cobalt-6-light); + --accent-7-light: var(--cobalt-7-light); + --accent-8-light: var(--cobalt-8-light); + --accent-9-light: var(--cobalt-9-light); + --accent-10-light: var(--cobalt-10-light); + --accent-11-light: var(--cobalt-11-light); + --accent-12-light: var(--cobalt-12-light); + --accent-contrast-light: var(--cobalt-contrast-light); + --accent-ink-light: var(--cobalt-ink-light); + --accent-pressed-light: var(--cobalt-pressed-light); + --accent-link-light: var(--cobalt-link-light); + --accent-link-hover-light: var(--cobalt-link-hover-light); + --accent-1-dark: var(--cobalt-1-dark); + --accent-2-dark: var(--cobalt-2-dark); + --accent-3-dark: var(--cobalt-3-dark); + --accent-4-dark: var(--cobalt-4-dark); + --accent-5-dark: var(--cobalt-5-dark); + --accent-6-dark: var(--cobalt-6-dark); + --accent-7-dark: var(--cobalt-7-dark); + --accent-8-dark: var(--cobalt-8-dark); + --accent-9-dark: var(--cobalt-9-dark); + --accent-10-dark: var(--cobalt-10-dark); + --accent-11-dark: var(--cobalt-11-dark); + --accent-12-dark: var(--cobalt-12-dark); + --accent-contrast-dark: var(--cobalt-contrast-dark); + --accent-ink-dark: var(--cobalt-ink-dark); + --accent-pressed-dark: var(--cobalt-pressed-dark); + --accent-link-dark: var(--cobalt-link-dark); + --accent-link-hover-dark: var(--cobalt-link-hover-dark); } :root[data-accent="bee"], [data-accent="bee"] { - --accent-1-light: #fdfcf8; - --accent-2-light: #fcf9f1; - --accent-3-light: #faf3df; - --accent-4-light: #f5eace; - --accent-5-light: #f1e3be; - --accent-6-light: #e8d9af; - --accent-7-light: #ddcb98; - --accent-8-light: #d0b979; - --accent-9-light: #f5c518; - --accent-10-light: #fdcd2a; - --accent-11-light: #584504; - --accent-12-light: #362d13; - --accent-contrast-light: #000000; - --accent-ink-light: #000000; - --accent-pressed-light: #ffd763; - --accent-link-light: #594501; - --accent-link-hover-light: #faf3df; - --accent-1-dark: #100f0c; - --accent-2-dark: #171511; - --accent-3-dark: #252118; - --accent-4-dark: #312b1c; - --accent-5-dark: #3d3521; - --accent-6-dark: #4a412a; - --accent-7-dark: #5c5234; - --accent-8-dark: #766842; - --accent-9-dark: #f5c518; - --accent-10-dark: #fdcd2a; - --accent-11-dark: #e0cd97; - --accent-12-dark: #f6f0df; - --accent-contrast-dark: #000000; - --accent-ink-dark: #000000; - --accent-pressed-dark: #ffd763; - --accent-link-dark: #f2c94e; - --accent-link-hover-dark: #252118; + --accent-1-light: var(--bee-1-light); + --accent-2-light: var(--bee-2-light); + --accent-3-light: var(--bee-3-light); + --accent-4-light: var(--bee-4-light); + --accent-5-light: var(--bee-5-light); + --accent-6-light: var(--bee-6-light); + --accent-7-light: var(--bee-7-light); + --accent-8-light: var(--bee-8-light); + --accent-9-light: var(--bee-9-light); + --accent-10-light: var(--bee-10-light); + --accent-11-light: var(--bee-11-light); + --accent-12-light: var(--bee-12-light); + --accent-contrast-light: var(--bee-contrast-light); + --accent-ink-light: var(--bee-ink-light); + --accent-pressed-light: var(--bee-pressed-light); + --accent-link-light: var(--bee-link-light); + --accent-link-hover-light: var(--bee-link-hover-light); + --accent-1-dark: var(--bee-1-dark); + --accent-2-dark: var(--bee-2-dark); + --accent-3-dark: var(--bee-3-dark); + --accent-4-dark: var(--bee-4-dark); + --accent-5-dark: var(--bee-5-dark); + --accent-6-dark: var(--bee-6-dark); + --accent-7-dark: var(--bee-7-dark); + --accent-8-dark: var(--bee-8-dark); + --accent-9-dark: var(--bee-9-dark); + --accent-10-dark: var(--bee-10-dark); + --accent-11-dark: var(--bee-11-dark); + --accent-12-dark: var(--bee-12-dark); + --accent-contrast-dark: var(--bee-contrast-dark); + --accent-ink-dark: var(--bee-ink-dark); + --accent-pressed-dark: var(--bee-pressed-dark); + --accent-link-dark: var(--bee-link-dark); + --accent-link-hover-dark: var(--bee-link-hover-dark); } :root[data-accent="citrus"], [data-accent="citrus"], :root { - --accent-1-light: #fcfdf7; - --accent-2-light: #f8fbef; - --accent-3-light: #f1f6de; - --accent-4-light: #e8f0cd; - --accent-5-light: #e0e9bc; - --accent-6-light: #d5e1aa; - --accent-7-light: #c8d495; - --accent-8-light: #b6c378; - --accent-9-light: #beda36; - --accent-10-light: #bdd836; - --accent-11-light: #485311; - --accent-12-light: #2b3110; - --accent-contrast-light: #000000; - --accent-ink-light: #000000; - --accent-pressed-light: #bcd83a; - --accent-link-light: #485400; - --accent-link-hover-light: #f1f6de; - --accent-1-dark: #0f100c; - --accent-2-dark: #15170f; - --accent-3-dark: #202413; - --accent-4-dark: #292e16; - --accent-5-dark: #333a18; - --accent-6-dark: #3e471f; - --accent-7-dark: #4f5827; - --accent-8-dark: #637033; - --accent-9-dark: #beda36; - --accent-10-dark: #bdd836; - --accent-11-dark: #cee28b; - --accent-12-dark: #edf7d2; - --accent-contrast-dark: #000000; - --accent-ink-dark: #000000; - --accent-pressed-dark: #bcd83a; - --accent-link-dark: #cbe648; - --accent-link-hover-dark: #202413; + --accent-1-light: var(--citrus-1-light); + --accent-2-light: var(--citrus-2-light); + --accent-3-light: var(--citrus-3-light); + --accent-4-light: var(--citrus-4-light); + --accent-5-light: var(--citrus-5-light); + --accent-6-light: var(--citrus-6-light); + --accent-7-light: var(--citrus-7-light); + --accent-8-light: var(--citrus-8-light); + --accent-9-light: var(--citrus-9-light); + --accent-10-light: var(--citrus-10-light); + --accent-11-light: var(--citrus-11-light); + --accent-12-light: var(--citrus-12-light); + --accent-contrast-light: var(--citrus-contrast-light); + --accent-ink-light: var(--citrus-ink-light); + --accent-pressed-light: var(--citrus-pressed-light); + --accent-link-light: var(--citrus-link-light); + --accent-link-hover-light: var(--citrus-link-hover-light); + --accent-1-dark: var(--citrus-1-dark); + --accent-2-dark: var(--citrus-2-dark); + --accent-3-dark: var(--citrus-3-dark); + --accent-4-dark: var(--citrus-4-dark); + --accent-5-dark: var(--citrus-5-dark); + --accent-6-dark: var(--citrus-6-dark); + --accent-7-dark: var(--citrus-7-dark); + --accent-8-dark: var(--citrus-8-dark); + --accent-9-dark: var(--citrus-9-dark); + --accent-10-dark: var(--citrus-10-dark); + --accent-11-dark: var(--citrus-11-dark); + --accent-12-dark: var(--citrus-12-dark); + --accent-contrast-dark: var(--citrus-contrast-dark); + --accent-ink-dark: var(--citrus-ink-dark); + --accent-pressed-dark: var(--citrus-pressed-dark); + --accent-link-dark: var(--citrus-link-dark); + --accent-link-hover-dark: var(--citrus-link-hover-dark); } :root[data-accent="mono"], [data-accent="mono"] { - --accent-1-light: #fcfcfc; - --accent-2-light: #f9f9f9; - --accent-3-light: #f3f3f3; - --accent-4-light: #ebebeb; - --accent-5-light: #e3e3e3; - --accent-6-light: #d9d9d9; - --accent-7-light: #cccccc; - --accent-8-light: #bababa; - --accent-9-light: #1a1a1a; - --accent-10-light: #141414; - --accent-11-light: #484848; - --accent-12-light: #2e2e2e; - --accent-contrast-light: #ffffff; - --accent-ink-light: #ffffff; - --accent-pressed-light: #0f0f0f; - --accent-link-light: #484848; - --accent-link-hover-light: #f3f3f3; - --accent-1-dark: #0f0f0f; - --accent-2-dark: #151515; - --accent-3-dark: #222222; - --accent-4-dark: #2b2b2b; - --accent-5-dark: #363636; - --accent-6-dark: #424242; - --accent-7-dark: #535353; - --accent-8-dark: #696969; - --accent-9-dark: #e6e6e6; - --accent-10-dark: #eeeeee; - --accent-11-dark: #cecece; - --accent-12-dark: #f0f0f0; - --accent-contrast-dark: #000000; - --accent-ink-dark: #000000; - --accent-pressed-dark: #f7f7f7; - --accent-link-dark: #cecece; - --accent-link-hover-dark: #222222; + --accent-1-light: var(--mono-1-light); + --accent-2-light: var(--mono-2-light); + --accent-3-light: var(--mono-3-light); + --accent-4-light: var(--mono-4-light); + --accent-5-light: var(--mono-5-light); + --accent-6-light: var(--mono-6-light); + --accent-7-light: var(--mono-7-light); + --accent-8-light: var(--mono-8-light); + --accent-9-light: var(--mono-9-light); + --accent-10-light: var(--mono-10-light); + --accent-11-light: var(--mono-11-light); + --accent-12-light: var(--mono-12-light); + --accent-contrast-light: var(--mono-contrast-light); + --accent-ink-light: var(--mono-ink-light); + --accent-pressed-light: var(--mono-pressed-light); + --accent-link-light: var(--mono-link-light); + --accent-link-hover-light: var(--mono-link-hover-light); + --accent-1-dark: var(--mono-1-dark); + --accent-2-dark: var(--mono-2-dark); + --accent-3-dark: var(--mono-3-dark); + --accent-4-dark: var(--mono-4-dark); + --accent-5-dark: var(--mono-5-dark); + --accent-6-dark: var(--mono-6-dark); + --accent-7-dark: var(--mono-7-dark); + --accent-8-dark: var(--mono-8-dark); + --accent-9-dark: var(--mono-9-dark); + --accent-10-dark: var(--mono-10-dark); + --accent-11-dark: var(--mono-11-dark); + --accent-12-dark: var(--mono-12-dark); + --accent-contrast-dark: var(--mono-contrast-dark); + --accent-ink-dark: var(--mono-ink-dark); + --accent-pressed-dark: var(--mono-pressed-dark); + --accent-link-dark: var(--mono-link-dark); + --accent-link-hover-dark: var(--mono-link-hover-dark); } /* All three vibe levels keep app surfaces neutral. Hummin’ adds soft personal @@ -1667,51 +2197,3 @@ --spacing-chip-inset: var(--space-chip-inset); --spacing-chip-gap: var(--space-chip-gap); } - -/* Plugin category gradients preserve the original cross-color hue pairs. - Endpoint luminance targets 0.18; chroma is shared across each pair and capped - for sRGB along the entire OKLCH transition. Icon lighting changes final pixels. */ -:root { - --art-plugin-communication-start: oklch(55.67495% 0.1 220); - --art-plugin-communication-end: oklch(56.46810% 0.1 255); - --art-plugin-communication-gradient: linear-gradient( - to bottom in oklch, - var(--art-plugin-communication-start), - var(--art-plugin-communication-end) - ); - --art-plugin-agents-start: oklch(57.29854% 0.1 295); - --art-plugin-agents-end: oklch(56.58608% 0.1 260); - --art-plugin-agents-gradient: linear-gradient( - to bottom in oklch, - var(--art-plugin-agents-start), - var(--art-plugin-agents-end) - ); - --art-plugin-agent-tools-start: oklch(57.71968% 0.1 335); - --art-plugin-agent-tools-end: oklch(57.37636% 0.1 300); - --art-plugin-agent-tools-gradient: linear-gradient( - to bottom in oklch, - var(--art-plugin-agent-tools-start), - var(--art-plugin-agent-tools-end) - ); - --art-plugin-developer-tools-and-integrations-start: oklch(57.69208% 0.1 375); - --art-plugin-developer-tools-and-integrations-end: oklch(57.74021% 0.1 340); - --art-plugin-developer-tools-and-integrations-gradient: linear-gradient( - to bottom in oklch, - var(--art-plugin-developer-tools-and-integrations-start), - var(--art-plugin-developer-tools-and-integrations-end) - ); - --art-plugin-people-and-communities-start: oklch(57.65787% 0.1 380); - --art-plugin-people-and-communities-end: oklch(57.22866% 0.1 415); - --art-plugin-people-and-communities-gradient: linear-gradient( - to bottom in oklch, - var(--art-plugin-people-and-communities-start), - var(--art-plugin-people-and-communities-end) - ); - --art-plugin-productivity-start: oklch(57.14190% 0.1 420); - --art-plugin-productivity-end: oklch(56.41273% 0.1 455); - --art-plugin-productivity-gradient: linear-gradient( - to bottom in oklch, - var(--art-plugin-productivity-start), - var(--art-plugin-productivity-end) - ); -} diff --git a/src/shared/design-system/tokens/brand.test.ts b/src/shared/design-system/tokens/brand.test.ts index 433a9656eb..bef3e13cfa 100644 --- a/src/shared/design-system/tokens/brand.test.ts +++ b/src/shared/design-system/tokens/brand.test.ts @@ -1,61 +1,133 @@ import { readFileSync } from "node:fs"; import { expect, it } from "vitest"; -import { BRAND_GRADIENTS } from "./brand"; +import { ACCENTS, BRAND_GRADIENTS, PALETTE_FAMILIES } from "./brand"; +import { PALETTE, ROLE_GROUPS } from "./registry"; const css = readFileSync( new URL("../styles/tokens.css", import.meta.url), "utf8", ); -function rgb(l: number, c: number, h: number) { - const a = c * Math.cos((h * Math.PI) / 180); - const b = c * Math.sin((h * Math.PI) / 180); - const x = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3; - const y = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3; - const z = (l - 0.0894841775 * a - 1.291485548 * b) ** 3; - return [ - 4.0767416621 * x - 3.3077115913 * y + 0.2309699292 * z, - -1.2684380046 * x + 2.6097574011 * y - 0.3413193965 * z, - -0.0041960863 * x - 0.7034186147 * y + 1.707614701 * z, - ] as const; -} +const light = css.slice(0, css.indexOf(':root[data-color-mode="dark"]')); +const dark = css.slice( + css.indexOf(':root[data-color-mode="dark"]'), + css.indexOf("LAYER 1 — FAMILIES"), +); -it("gives six distinct identities comparable contrast across the full gradient", () => { +it("maps all artwork endpoints to existing family steps in both themes", () => { expect(new Set(BRAND_GRADIENTS.map(({ category }) => category)).size).toBe(6); for (const gradient of BRAND_GRADIENTS) { - const readStop = (stop: string) => { - const match = css.match( - new RegExp( - `--art-plugin-${gradient.category}-${stop}: oklch\\(([\\d.]+)% ([\\d.]+) ([\\d.]+)\\)`, - ), + for (const [part, palette] of [ + ["start", gradient.start], + ["end", gradient.end], + ] as const) { + expect(`--${palette}`).toMatch( + /^--(amber|blue|cyan|green|orange|purple|red|chartreuse|flame|magenta|iris|cobalt|jade)-(?:[1-9]|1[0-2])$/, + ); + expect(css).toContain( + `--art-plugin-${gradient.category}-${part}: var(--${palette});`, ); - if (!match) throw new Error(`Missing ${gradient.category} ${stop}`); - return [ - Number(match[1]) / 100, - Number(match[2]), - Number(match[3]), - ] as const; + for (const [name, mode] of [ + ["light", light], + ["dark", dark], + ] as const) { + if (PALETTE_FAMILIES.some((family) => palette.startsWith(`${family}-`))) + expect(mode).toContain(`--${palette}: var(--${palette}-${name});`); + else expect(mode).toMatch(new RegExp(`--${palette}: #[a-f0-9]{6};`)); + } + } + const pattern = new RegExp( + `--art-plugin-${gradient.category}-gradient: linear-gradient\\([\\s\\S]*?var\\(--art-plugin-${gradient.category}-start\\),[\\s\\S]*?var\\(--art-plugin-${gradient.category}-end\\)[\\s\\S]*?\\);`, + ); + expect(css).toMatch(pattern); + } +}); + +it("registers all public artwork with palette ownership and distinct hue families", () => { + const roles = ROLE_GROUPS.flatMap(({ roles }) => roles); + for (const gradient of BRAND_GRADIENTS) + for (const part of ["start", "end", "gradient"]) { + expect( + roles.find( + ({ variable }) => + variable === `--art-plugin-${gradient.category}-${part}`, + ), + ).toMatchObject({ status: "proposed", owner: "Settings plugin catalog" }); + } + const pairs = new Set(); + for (const gradient of BRAND_GRADIENTS) { + const family = (step: string) => step.split("-")[0]; + expect(family(gradient.start)).not.toBe(family(gradient.end)); + const pair = `${gradient.start}/${gradient.end}`; + expect(pairs.has(pair)).toBe(false); + pairs.add(pair); + } + expect(css).not.toContain("PLUGIN ARTWORK PALETTE"); +}); + +it("keeps Agents blue above violet with a lighter top in both themes", () => { + expect( + BRAND_GRADIENTS.find(({ category }) => category === "agents"), + ).toMatchObject({ start: "blue-9", end: "iris-9" }); + const luminance = (hex: string) => { + const channels = [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)] + .map((part) => parseInt(part, 16) / 255) + .map((v) => (v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4)); + return channels.reduce( + (sum, channel, index) => + sum + channel * ([0.2126, 0.7152, 0.0722][index] ?? 0), + 0, + ); + }; + for (const [modeName, mode] of [ + ["light", light], + ["dark", dark], + ] as const) { + const value = (token: string) => { + const source = token === "iris-9" ? css : mode; + const name = token === "iris-9" ? `${token}-${modeName}` : token; + const match = source.match(new RegExp(`--${name}: #([a-f0-9]{6});`)); + if (!match?.[1]) throw new Error(`Missing palette token ${token}`); + return match[1]; }; - const start = readStop("start"); - const end = readStop("end"); - expect(start[2]).toBe(gradient.fromHue); - expect(end[2]).toBe(gradient.toHue); - expect(Math.abs(end[2] - start[2])).toBe(35); - expect(gradient.from).not.toBe(gradient.to); - for (let index = 0; index <= 20; index++) { - const t = index / 20; - const l = start[0] + (end[0] - start[0]) * t; - const c = start[1] + (end[1] - start[1]) * t; - const h = start[2] + (end[2] - start[2]) * t; - const channels = rgb(l, c, h); - for (const channel of channels) { - expect(channel).toBeGreaterThanOrEqual(0); - expect(channel).toBeLessThanOrEqual(1); + expect(luminance(value("blue-9"))).toBeGreaterThan( + luminance(value("iris-9")), + ); + } +}); + +it("gives every Appearance step explicit mode ownership and complete registry metadata", () => { + for (const family of ACCENTS) { + const block = css.match( + new RegExp(String.raw`\[data-accent="${family}"\][^{]*\{([^}]+)`), + )?.[1]; + const steps = PALETTE.find(({ id }) => id === family)?.steps; + expect(steps).toHaveLength(12); + for (let step = 1; step <= 12; step += 1) { + expect(steps).toContainEqual( + expect.objectContaining({ step, variable: `--${family}-${step}` }), + ); + for (const [mode, declarations] of [ + ["light", light], + ["dark", dark], + ] as const) { + expect(css).toMatch( + new RegExp(`--${family}-${step}-${mode}: #[a-f0-9]{6};`), + ); + expect(declarations).toContain( + `--${family}-${step}: var(--${family}-${step}-${mode});`, + ); + expect(block).toContain( + `--accent-${step}-${mode}: var(--${family}-${step}-${mode});`, + ); + } + } + for (const role of ["contrast", "ink", "pressed", "link", "link-hover"]) { + for (const mode of ["light", "dark"]) { + expect(block).toContain( + `--accent-${role}-${mode}: var(--${family}-${role}-${mode});`, + ); } - const luminance = - channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722; - expect(Math.abs(luminance - 0.18)).toBeLessThan(0.002); - expect(1.05 / (luminance + 0.05)).toBeGreaterThanOrEqual(4.5); - expect((luminance + 0.05) / 0.05).toBeGreaterThanOrEqual(4.5); } + expect(block).not.toMatch(/#[a-f0-9]{3,8}/); } }); diff --git a/src/shared/design-system/tokens/brand.ts b/src/shared/design-system/tokens/brand.ts index 0d2c9cca6f..f521c37cb5 100644 --- a/src/shared/design-system/tokens/brand.ts +++ b/src/shared/design-system/tokens/brand.ts @@ -1,62 +1,64 @@ -/** Existing accent identities, reused by plugin artwork. */ +/** Palette metadata for Appearance references and proposed plugin artwork. */ export const BRAND_SHADES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as const; -export const BRAND_FAMILIES = [ - { id: "cobalt", hue: 266.6948 }, - { id: "iris", hue: 284.502 }, - { id: "magenta", hue: 355.6028 }, - { id: "flame", hue: 34.6181 }, - { id: "jade", hue: 169.9836 }, - { id: "chartreuse", hue: 109.5919 }, +/** Canonical Appearance order; re-exported by shared/theme/accent.ts. */ +export const ACCENTS = [ + "bee", + "magenta", + "iris", + "cobalt", + "citrus", + "mono", ] as const; -/** Cross-color pairs preserve the plugin artwork direction; accent ramps above - * remain the existing Appearance choices, not replacements for these endpoints. */ +/** Retained artwork palettes are independent of selectable Appearance accents. */ +export const PALETTE_FAMILIES = [...ACCENTS, "flame", "jade"] as const; +/** Proposed artwork mapping uses existing theme-aware palette families independently + * of the existing Appearance choices above. */ export const BRAND_GRADIENTS = [ { name: "Communication", category: "communication", - from: "cyan", - to: "blue", - fromHue: 220, - toHue: 255, + start: "cyan-9", + end: "jade-9", + compromise: + "Cyan to jade gives Communication a teal/green identity distinct from Agents.", }, { name: "Agents", category: "agents", - from: "violet", - to: "indigo", - fromHue: 295, - toHue: 260, + start: "blue-9", + end: "iris-9", + compromise: + "Existing Iris supplies violet beneath lighter blue in both themes.", }, { name: "Agent tools", category: "agent-tools", - from: "fuchsia", - to: "purple", - fromHue: 335, - toHue: 300, + start: "purple-9", + end: "magenta-9", + compromise: + "Purple to the existing Magenta fill gives agent tools a violet-to-rose identity.", }, { name: "Developer tools & integrations", category: "developer-tools-and-integrations", - from: "coral", - to: "pink", - fromHue: 375, - toHue: 340, + start: "orange-9", + end: "flame-9", + compromise: + "Orange to the existing Flame fill gives developer tools a warm ember identity.", }, { name: "People & communities", category: "people-and-communities", - from: "scarlet", - to: "orange", - fromHue: 380, - toHue: 415, + start: "red-9", + end: "magenta-9", + compromise: + "Red to existing Magenta gives people a scarlet-to-rose identity.", }, { name: "Productivity", category: "productivity", - from: "gold", - to: "lime", - fromHue: 420, - toHue: 455, + start: "amber-9", + end: "green-9", + compromise: "Preserves the approved yellow-to-green transition.", }, ] as const; diff --git a/src/shared/design-system/tokens/contrast.test.mjs b/src/shared/design-system/tokens/contrast.test.mjs index 493f7b9726..9b7267f7ba 100644 --- a/src/shared/design-system/tokens/contrast.test.mjs +++ b/src/shared/design-system/tokens/contrast.test.mjs @@ -118,33 +118,27 @@ describe("semantic contrast contract", () => { it.each([ [ "wrong dark ink", - "--accent-contrast-dark: #ffffff;", - "--accent-contrast-dark: #000000;", + "--magenta-contrast-dark: #ffffff;", + "--magenta-contrast-dark: #000000;", "magenta/neutral/dark", ], [ "unresolved ink", - "--accent-contrast-light: #ffffff;", - "--accent-contrast-light: var(--missing-ink);", + "--magenta-contrast-light: #ffffff;", + "--magenta-contrast-light: var(--missing-ink);", "magenta/neutral/light: unresolved accent pair", ], [ "faint insertion line", - "--accent-11-light: #6c3149;", - "--accent-11-light: #feedf3;", + "--magenta-11-light: #6c3149;", + "--magenta-11-light: #feedf3;", "magenta/neutral/light: --border-accent", ], ])( "rejects a non-default accent family's %s", (_name, from, to, evidence) => { - const start = tokens.indexOf('[data-accent="magenta"] {'); - expect(start).toBeGreaterThan(0); - const end = tokens.indexOf("}", start); - const family = tokens.slice(start, end); - expect(family).toContain(from); - const result = check( - tokens.slice(0, start) + family.replace(from, to) + tokens.slice(end), - ); + expect(tokens).toContain(from); + const result = check(tokens.replace(from, to)); expect(result.status, result.output).toBe(1); expect(result.output).toContain(evidence); }, @@ -236,8 +230,8 @@ describe("semantic contrast contract", () => { ], [ "accent hover", - "--accent-10-light: #2176d9;", - "--accent-10-light: #9a5cd0;", + "--cobalt-10-light: #2176d9;", + "--cobalt-10-light: #9a5cd0;", "--text-on-accent", ], ])( diff --git a/src/shared/design-system/tokens/registry.ts b/src/shared/design-system/tokens/registry.ts index 302c227d29..93c731f767 100644 --- a/src/shared/design-system/tokens/registry.ts +++ b/src/shared/design-system/tokens/registry.ts @@ -1,3 +1,5 @@ +import { ACCENTS, BRAND_GRADIENTS, PALETTE_FAMILIES } from "./brand"; + /** * Documentation metadata for the shared tokens: names, purposes, sources, * and adoption status. The viewer renders these entries; token guards check @@ -125,6 +127,26 @@ export const PALETTE: PaletteHue[] = [ })), })); +// Existing Appearance ramps are palette owners in both modes. +PALETTE.push( + ...PALETTE_FAMILIES.map((id) => ({ + id, + usedBy: BRAND_GRADIENTS.some( + ({ start, end }) => + start.startsWith(`${id}-`) || end.startsWith(`${id}-`), + ) + ? ACCENTS.some((family) => family === id) + ? "Appearance and artwork" + : "artwork" + : "Appearance", + steps: STEP_JOBS.map((job, index) => ({ + step: index + 1, + variable: `--${id}-${index + 1}`, + job, + })), + })), +); + export const BACKDROP_TREATMENTS: BackdropTreatment[] = [ { id: "sky-field", @@ -248,6 +270,35 @@ export const RAMPS: Ramp[] = [ /** Semantic roles name color jobs; the palette remains an implementation detail. */ export const ROLE_GROUPS: RoleGroup[] = [ + { + id: "plugin-artwork", + name: "Plugin artwork (proposed)", + description: + "Palette-backed category artwork used by the Settings plugin catalog.", + roles: BRAND_GRADIENTS.flatMap((gradient) => + (["start", "end", "gradient"] as const).map( + (part): Role => ({ + token: `art-plugin-${gradient.category}-${part}`, + variable: `--art-plugin-${gradient.category}-${part}`, + pointsAt: + part === "gradient" + ? `${gradient.start} → ${gradient.end}` + : part === "start" + ? `--${gradient.start}` + : `--${gradient.end}`, + use: `${gradient.name} decorative artwork ${part}.`, + status: "proposed", + owner: "Settings plugin catalog", + ...(part === "gradient" + ? { + exception: + "Complete decorative composition of palette-backed endpoints.", + } + : {}), + }), + ), + ), + }, { id: "semantic-surface", name: "Surface", diff --git a/src/shared/theme/accent.ts b/src/shared/theme/accent.ts index 4120f97557..1e549a128e 100644 --- a/src/shared/theme/accent.ts +++ b/src/shared/theme/accent.ts @@ -1,15 +1,9 @@ import type { CSSProperties } from "react"; +import { ACCENTS } from "../design-system/tokens/brand"; // One device-local accent for actions, links and selection. // App surfaces stay neutral; contrast controls messages and selected navigation. -export const ACCENTS = [ - "bee", - "magenta", - "iris", - "cobalt", - "citrus", - "mono", -] as const; +export { ACCENTS }; export type Accent = (typeof ACCENTS)[number]; export const DEFAULT_ACCENT: Accent = "citrus"; export const ACCENT_KEY = "buzz-accent.v1"; diff --git a/src/shared/theme/adoption.test.mjs b/src/shared/theme/adoption.test.mjs index c7ef093c87..96c308e587 100644 --- a/src/shared/theme/adoption.test.mjs +++ b/src/shared/theme/adoption.test.mjs @@ -22,6 +22,32 @@ test("adoption guard catches utilities, CSS and renderer string reads", () => { ).toEqual([]); }); +test("adoption guard covers every Appearance family and explicit mode owner", () => { + for (const family of [ + "bee", + "citrus", + "mono", + "gray", + "chartreuse", + "flame", + "magenta", + "iris", + "cobalt", + "jade", + ]) { + for (const source of [ + `color: var(--${family}-9)`, + `color: var(--${family}-11-light)`, + `color: var(--${family}-12-dark)`, + ...["contrast", "ink", "pressed", "link", "link-hover"].map( + (role) => `color: var(--${family}-${role}-dark)`, + ), + `className="text-${family}-9"`, + ]) + expect(adoptionFindings("src/example.tsx", source)).toHaveLength(1); + } +}); + test("feature layout remains local but cannot repaint shared controls", () => { expect( adoptionFindings( diff --git a/tests/fixtures/design-system/styles.css b/tests/fixtures/design-system/styles.css index fe58714884..24f2718151 100644 --- a/tests/fixtures/design-system/styles.css +++ b/tests/fixtures/design-system/styles.css @@ -758,3 +758,10 @@ .component-specimen-group > h2 { scroll-margin-block-start: var(--space-8); } + +/* Viewer-only gradient reference layout. */ +.brand-gradient-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr)); + gap: var(--space-4); +} diff --git a/tests/fixtures/design-system/ui/BrandColors.test.ts b/tests/fixtures/design-system/ui/BrandColors.test.ts new file mode 100644 index 0000000000..e4452f854a --- /dev/null +++ b/tests/fixtures/design-system/ui/BrandColors.test.ts @@ -0,0 +1,15 @@ +// @vitest-environment jsdom +import { createElement } from "react"; +import { expect, it } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { BRAND_GRADIENTS } from "../../../../src/shared/design-system/tokens/brand"; +import { BrandColors } from "./BrandColors"; + +it("shows artwork mappings without duplicating the palette ramps", () => { + render(createElement(BrandColors)); + for (const gradient of BRAND_GRADIENTS) + expect( + screen.getByText(`${gradient.start} → ${gradient.end}`), + ).toBeTruthy(); + expect(screen.queryAllByRole("button")).toHaveLength(0); +}); diff --git a/tests/fixtures/design-system/ui/BrandColors.tsx b/tests/fixtures/design-system/ui/BrandColors.tsx new file mode 100644 index 0000000000..2bf7478959 --- /dev/null +++ b/tests/fixtures/design-system/ui/BrandColors.tsx @@ -0,0 +1,37 @@ +import { BRAND_GRADIENTS } from "../../../../src/shared/design-system/tokens/brand"; +import { Section } from "./primitives"; + +export function BrandColors() { + return ( +
+
+ {BRAND_GRADIENTS.map((gradient) => ( +
+
+ {gradient.name} + + {gradient.start} → {gradient.end} + +

{gradient.compromise}

+
+ ))} +
+

+ Family references follow the active theme. Shared luminance, chroma, + contrast and the original 35° hue shifts are no longer guaranteed. + Compare the real catalog in both modes before judging the mapping. +

+
+ ); +} diff --git a/tests/fixtures/design-system/ui/ColorPage.tsx b/tests/fixtures/design-system/ui/ColorPage.tsx index 1bd2594ce9..efedd7d855 100644 --- a/tests/fixtures/design-system/ui/ColorPage.tsx +++ b/tests/fixtures/design-system/ui/ColorPage.tsx @@ -9,6 +9,8 @@ import { type Role, } from "../../../../src/shared/design-system/tokens/registry"; +import { BrandColors } from "./BrandColors"; + import { Note, PageHeader, Section, Swatch } from "./primitives"; /** @@ -86,7 +88,7 @@ export function ColorPage() {
{PALETTE.map((hue) => ( @@ -127,6 +129,8 @@ export function ColorPage() {
+ +
`${file}:${line}`), + ...vitestFilters(file, selection), "--reporter=json", `--outputFile=${report}`, ]);