From 643e8dbf8ba6ab21b1071d1ee8bd5f1eee1c8250 Mon Sep 17 00:00:00 2001 From: Pato Perpetua Date: Thu, 13 Aug 2026 23:47:10 +1000 Subject: [PATCH] fix: DS-61 consume tokens from npm The CDN --fg/--colors contract broke. Point Tailwind at published --ssd-* vars from @singleton-sd/tokens. Co-authored-by: Cursor --- .github/workflows/deploy-marketing.yml | 2 + .github/workflows/preview-marketing.yml | 2 + .gitignore | 1 + .npmrc | 1 + README.md | 2 +- SETUP.md | 2 + apps/marketing/package.json | 6 +- apps/marketing/public/admin/admin-preview.js | 4 +- apps/marketing/public/admin/admin-theme.js | 30 ++--- apps/marketing/public/admin/admin.css | 57 ++++----- apps/marketing/public/admin/index.html | 3 +- apps/marketing/public/admin/preview.css | 62 ++++------ apps/marketing/scripts/copy-token-css.mjs | 15 +++ apps/marketing/src/styles/global.css | 119 ++++--------------- apps/marketing/tailwind.config.mjs | 65 +++++----- pnpm-lock.yaml | 9 ++ 16 files changed, 157 insertions(+), 223 deletions(-) create mode 100644 apps/marketing/scripts/copy-token-css.mjs diff --git a/.github/workflows/deploy-marketing.yml b/.github/workflows/deploy-marketing.yml index 5779ef4..b53f09d 100644 --- a/.github/workflows/deploy-marketing.yml +++ b/.github/workflows/deploy-marketing.yml @@ -7,6 +7,8 @@ on: paths: - 'apps/marketing/**' - 'scripts/validate-staticwebapp-config.mjs' + - '.npmrc' + - 'pnpm-lock.yaml' - '.github/workflows/deploy-marketing.yml' workflow_dispatch: diff --git a/.github/workflows/preview-marketing.yml b/.github/workflows/preview-marketing.yml index 23d982c..540a1f7 100644 --- a/.github/workflows/preview-marketing.yml +++ b/.github/workflows/preview-marketing.yml @@ -7,6 +7,8 @@ on: paths: - 'apps/marketing/**' - 'scripts/validate-staticwebapp-config.mjs' + - '.npmrc' + - 'pnpm-lock.yaml' - '.github/workflows/preview-marketing.yml' concurrency: diff --git a/.gitignore b/.gitignore index 0d8b8b3..626a8b8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ coverage/ .idea/ .vscode/ !.vscode/extensions.json +apps/marketing/public/admin/vendor/ diff --git a/.npmrc b/.npmrc index 4c2f52b..d65a160 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ auto-install-peers=true strict-peer-dependencies=false +@singleton-sd:registry=https://gitlab.com/api/v4/packages/npm/ diff --git a/README.md b/README.md index 6debb66..310a447 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Public landing site for **Singleton Software Development** at [singletonsd.com]( ## Stack -- Astro SSG + Tailwind + Singleton SD design tokens +- Astro SSG + Tailwind + `@singleton-sd/tokens` (GitLab npm, `--ssd-*`) - Decap CMS at `/admin` (GitHub backend) - Azure Static Web Apps Free + Decap OAuth Azure Function - Shared Key Vault in `rg-ssd-global` diff --git a/SETUP.md b/SETUP.md index 75a92fb..3933474 100644 --- a/SETUP.md +++ b/SETUP.md @@ -9,6 +9,8 @@ ## Local +`@singleton-sd/tokens` comes from GitLab’s npm registry (see `.npmrc`). + ```powershell pnpm install pnpm dev diff --git a/apps/marketing/package.json b/apps/marketing/package.json index ec39c1c..e139388 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -4,8 +4,9 @@ "private": true, "type": "module", "scripts": { - "dev": "astro dev", - "build": "node ../../scripts/validate-staticwebapp-config.mjs public/staticwebapp.config.json && astro build", + "copy-tokens": "node ./scripts/copy-token-css.mjs", + "dev": "node ./scripts/copy-token-css.mjs && astro dev", + "build": "node ../../scripts/validate-staticwebapp-config.mjs public/staticwebapp.config.json && node ./scripts/copy-token-css.mjs && astro build", "preview": "astro preview", "lint": "node ../../scripts/validate-staticwebapp-config.mjs public/staticwebapp.config.json && astro check", "test": "node --test ../../scripts/validate-staticwebapp-config.test.mjs src/lib/site.test.mjs && node ../../scripts/validate-staticwebapp-config.mjs public/staticwebapp.config.json && astro check" @@ -14,6 +15,7 @@ "@astrojs/check": "^0.9.4", "@astrojs/sitemap": "3.6.0", "@astrojs/tailwind": "^5.1.4", + "@singleton-sd/tokens": "1.3.0", "astro": "^4.16.18", "tailwindcss": "^3.4.15", "typescript": "^5.7.2" diff --git a/apps/marketing/public/admin/admin-preview.js b/apps/marketing/public/admin/admin-preview.js index d5a8faf..ab00313 100644 --- a/apps/marketing/public/admin/admin-preview.js +++ b/apps/marketing/public/admin/admin-preview.js @@ -14,8 +14,8 @@ CMS.registerPreviewStyle('/admin/preview.css'); /** - * Token CSS is gated on [data-theme='dark']. Decap preview iframes omit it, - * so stamp the attribute whenever a preview frame mounts. + * Preview CSS uses root/dark tokens on :root. Stamp data-theme anyway so + * any [data-theme='dark'] sheets in the iframe still apply. */ function stampPreviewTheme() { const frames = document.querySelectorAll('iframe'); diff --git a/apps/marketing/public/admin/admin-theme.js b/apps/marketing/public/admin/admin-theme.js index 111cf6a..2bfbc57 100644 --- a/apps/marketing/public/admin/admin-theme.js +++ b/apps/marketing/public/admin/admin-theme.js @@ -50,21 +50,21 @@ const cs = getComputedStyle(root); const get = (name, fallback) => cs.getPropertyValue(name).trim() || fallback; return { - accent: get('--pk-accent', 'var(--colors-yellow-light-400)'), - accentBg: get('--pk-accent-bg', 'var(--colors-yellow-light-900)'), - bg: get('--pk-bg', 'var(--colors-gray-900)'), - surface: get('--pk-surface', 'var(--colors-gray-800)'), - surfaceRaised: get('--pk-surface-raised', 'var(--colors-gray-700)'), - fg: get('--pk-fg', 'var(--colors-gray-100)'), - muted: get('--pk-muted', 'var(--colors-gray-400)'), - border: get('--pk-border', 'var(--colors-gray-700)'), - danger: get('--pk-danger', 'var(--colors-red-400)'), - dangerBg: get('--pk-danger-bg', 'var(--colors-red-900)'), - success: get('--pk-success', 'var(--colors-green-400)'), - successBg: get('--pk-success-bg', 'var(--colors-green-900)'), - warn: get('--pk-warn', 'var(--colors-yellow-light-300)'), - warnBg: get('--pk-warn-bg', 'var(--colors-yellow-light-900)'), - info: get('--pk-info', 'var(--colors-yellow-light-400)'), + accent: get('--pk-accent', 'var(--ssd-color-yellow-light-400)'), + accentBg: get('--pk-accent-bg', 'var(--ssd-color-yellow-light-900)'), + bg: get('--pk-bg', 'var(--ssd-color-gray-900)'), + surface: get('--pk-surface', 'var(--ssd-color-gray-800)'), + surfaceRaised: get('--pk-surface-raised', 'var(--ssd-color-gray-700)'), + fg: get('--pk-fg', 'var(--ssd-color-gray-100)'), + muted: get('--pk-muted', 'var(--ssd-color-gray-400)'), + border: get('--pk-border', 'var(--ssd-color-gray-700)'), + danger: get('--pk-danger', 'var(--ssd-color-red-400)'), + dangerBg: get('--pk-danger-bg', 'var(--ssd-color-red-900)'), + success: get('--pk-success', 'var(--ssd-color-green-400)'), + successBg: get('--pk-success-bg', 'var(--ssd-color-green-900)'), + warn: get('--pk-warn', 'var(--ssd-color-yellow-light-300)'), + warnBg: get('--pk-warn-bg', 'var(--ssd-color-yellow-light-900)'), + info: get('--pk-info', 'var(--ssd-color-yellow-light-400)'), }; } diff --git a/apps/marketing/public/admin/admin.css b/apps/marketing/public/admin/admin.css index 7a33abc..7c10d60 100644 --- a/apps/marketing/public/admin/admin.css +++ b/apps/marketing/public/admin/admin.css @@ -7,39 +7,30 @@ * * Emotion class names include component labels; substring selectors may drift * across Decap versions — pin the CDN build in index.html. + * + * Token CSS is copied from @singleton-sd/tokens into /admin/vendor at build. */ -@import url('https://tokens.design.singletonsd.com/css/dark/_variables.css'); - html[data-theme='dark'] { - /* Product roles */ - --pk-bg: var(--colors-gray-900); - --pk-fg: var(--colors-gray-100); - --pk-muted: var(--colors-gray-400); - --pk-accent: var(--colors-yellow-light-400); - --pk-accent-on: var(--colors-gray-900); - --pk-accent-bg: var(--colors-yellow-light-900); - --pk-surface: var(--colors-gray-800); - --pk-surface-raised: var(--colors-gray-700); - --pk-border: var(--colors-gray-700); + /* Decap chrome: primitives for readable gray/yellow admin UI. */ + --pk-bg: var(--ssd-color-gray-900); + --pk-fg: var(--ssd-color-gray-100); + --pk-muted: var(--ssd-color-gray-400); + --pk-accent: var(--ssd-color-yellow-light-400); + --pk-accent-on: var(--ssd-color-gray-900); + --pk-accent-bg: var(--ssd-color-yellow-light-900); + --pk-surface: var(--ssd-color-gray-800); + --pk-surface-raised: var(--ssd-color-gray-700); + --pk-border: var(--ssd-color-gray-700); --pk-glow: color-mix(in srgb, var(--pk-accent) 18%, transparent); - --pk-danger: var(--colors-red-400); - --pk-danger-bg: var(--colors-red-900); - --pk-success: var(--colors-green-400); - --pk-success-bg: var(--colors-green-900); - --pk-warn: var(--colors-yellow-light-300); - --pk-warn-bg: var(--colors-yellow-light-900); + --pk-danger: var(--ssd-color-red-400); + --pk-danger-bg: var(--ssd-color-red-900); + --pk-success: var(--ssd-color-green-400); + --pk-success-bg: var(--ssd-color-green-900); + --pk-warn: var(--ssd-color-yellow-light-300); + --pk-warn-bg: var(--ssd-color-yellow-light-900); --pk-info: var(--pk-accent); --pk-info-bg: var(--pk-accent-bg); - /* Override CDN token semantics that clash with product yellow/gray */ - --fg-default: var(--pk-fg); - --fg-muted: var(--pk-muted); - --bg-default: var(--pk-bg); - --bg-muted: var(--pk-surface); - --accent-default: var(--pk-accent); - --accent-on-accent: var(--pk-accent-on); - --accent-bg: var(--pk-accent-bg); - /* Decap styles.js semantic → product (documentation aid for remapper) */ --decap-background: var(--pk-bg); --decap-foreground: var(--pk-surface); @@ -53,16 +44,16 @@ html, body { margin: 0; min-height: 100%; - font-family: var(--font-families-body), ui-sans-serif, system-ui, sans-serif; - background-color: var(--bg-default); + font-family: var(--ssd-font-family-body), ui-sans-serif, system-ui, sans-serif; + background-color: var(--pk-bg); background-image: radial-gradient(ellipse 80% 55% at 50% -12%, var(--pk-glow), transparent 55%), radial-gradient( circle at 88% 82%, - color-mix(in srgb, var(--colors-gray-700) 35%, transparent), + color-mix(in srgb, var(--ssd-color-gray-700) 35%, transparent), transparent 42% ); - color: var(--fg-default); + color: var(--pk-fg); } body > div { @@ -75,7 +66,7 @@ button, } a { - color: var(--accent-default); + color: var(--pk-accent); } /* ——— Shell: solid surfaces (never transparent main panes) ——— */ @@ -132,7 +123,7 @@ a { content: 'Content admin'; display: block; margin-top: 0.75rem; - font-family: var(--font-families-body), ui-sans-serif, system-ui, sans-serif; + font-family: var(--ssd-font-family-body), ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 500; letter-spacing: 0.02em; diff --git a/apps/marketing/public/admin/index.html b/apps/marketing/public/admin/index.html index d752280..56cf134 100644 --- a/apps/marketing/public/admin/index.html +++ b/apps/marketing/public/admin/index.html @@ -28,11 +28,12 @@ /> - + + diff --git a/apps/marketing/public/admin/preview.css b/apps/marketing/public/admin/preview.css index 9e20f3a..9b6576f 100644 --- a/apps/marketing/public/admin/preview.css +++ b/apps/marketing/public/admin/preview.css @@ -2,38 +2,20 @@ * Preview pane iframe styles — official CMS.registerPreviewStyle target. * Mirrors marketing token roles + home / [slug] layouts. * - * Token sheet vars are gated on [data-theme='dark']. Decap’s preview iframe - * has no data-theme by default (admin-preview.js stamps it). Mirror the needed - * --colors-* / font tokens on :root so contrast works even before that runs. + * Token CSS is copied from @singleton-sd/tokens (root/dark) so vars live on + * :root even before admin-preview.js stamps [data-theme='dark']. */ -@import url('https://tokens.design.singletonsd.com/css/dark/_variables.css'); +@import url('./vendor/tokens-root-dark.css'); @import url('https://cdn.jsdelivr.net/npm/@fontsource/open-sauce-sans@5.3.0/index.css'); :root { - /* Same values as tokens.design.singletonsd.com dark sheet (iframe bridge). */ - --colors-gray-100: #f5f6f7; - --colors-gray-400: #b3b7bd; - --colors-gray-700: #60656b; - --colors-gray-800: #44484d; - --colors-gray-900: #292c30; - --colors-yellow-light-400: #fdc94a; - --colors-yellow-light-900: #6b4500; - --font-families-heading: 'Open Sauce Sans'; - --font-families-body: 'Open Sauce Sans'; - - --pk-bg: var(--colors-gray-900); - --pk-fg: var(--colors-gray-100); - --pk-muted: var(--colors-gray-400); - --pk-accent: var(--colors-yellow-light-400); - --pk-accent-on: var(--colors-gray-900); - --pk-accent-bg: var(--colors-yellow-light-900); - --pk-surface: var(--colors-gray-800); - --fg-default: var(--pk-fg); - --fg-muted: var(--pk-muted); - --bg-default: var(--pk-bg); - --accent-default: var(--pk-accent); - --accent-on-accent: var(--pk-accent-on); - --accent-bg: var(--pk-accent-bg); + --pk-bg: var(--ssd-color-background-default); + --pk-fg: var(--ssd-color-text-default); + --pk-muted: var(--ssd-color-text-muted); + --pk-accent: var(--ssd-color-background-brand); + --pk-accent-on: var(--ssd-color-text-on-brand); + --pk-accent-bg: var(--ssd-color-background-muted); + --pk-surface: var(--ssd-color-background-muted); color-scheme: dark; } @@ -41,9 +23,9 @@ html, body { margin: 0; padding: 0; - font-family: var(--font-families-body), ui-sans-serif, system-ui, sans-serif; - background: var(--bg-default); - color: var(--fg-default); + font-family: var(--ssd-font-family-body), ui-sans-serif, system-ui, sans-serif; + background: var(--pk-bg); + color: var(--pk-fg); } /* ——— Home (index.astro) ——— */ @@ -57,7 +39,7 @@ body { .pk-preview__brand { margin: 0 0 1.25rem; - font-family: var(--font-families-heading), ui-sans-serif, system-ui, sans-serif; + font-family: var(--ssd-font-family-heading), ui-sans-serif, system-ui, sans-serif; font-size: clamp(2.25rem, 7vw, 3.5rem); font-weight: 700; line-height: 1; @@ -71,7 +53,7 @@ body { .pk-preview__headline { margin: 0 0 0.75rem; - font-family: var(--font-families-heading), ui-sans-serif, system-ui, sans-serif; + font-family: var(--ssd-font-family-heading), ui-sans-serif, system-ui, sans-serif; font-size: clamp(1.15rem, 2.5vw, 1.35rem); font-weight: 500; letter-spacing: -0.02em; @@ -128,7 +110,7 @@ body { .pk-preview__title { margin: 0 0 0.25rem; - font-family: var(--font-families-heading), ui-sans-serif, system-ui, sans-serif; + font-family: var(--ssd-font-family-heading), ui-sans-serif, system-ui, sans-serif; font-size: 1.875rem; font-weight: 700; letter-spacing: -0.02em; @@ -168,7 +150,7 @@ body { .pk-preview__brand, .pk-preview__headline { color: var(--pk-fg) !important; - font-family: var(--font-families-heading), ui-sans-serif, system-ui, sans-serif; + font-family: var(--ssd-font-family-heading), ui-sans-serif, system-ui, sans-serif; } .pk-preview__body a, @@ -184,22 +166,22 @@ body > div:not(.pk-preview) { h1, h2, h3 { - color: var(--fg-default); - font-family: var(--font-families-heading), ui-sans-serif, system-ui, sans-serif; + color: var(--pk-fg); + font-family: var(--ssd-font-family-heading), ui-sans-serif, system-ui, sans-serif; } p, li { - color: var(--fg-muted); + color: var(--pk-muted); line-height: 1.55; } a { - color: var(--accent-default); + color: var(--pk-accent); } strong { - color: var(--fg-default); + color: var(--pk-fg); } code { diff --git a/apps/marketing/scripts/copy-token-css.mjs b/apps/marketing/scripts/copy-token-css.mjs new file mode 100644 index 0000000..197e438 --- /dev/null +++ b/apps/marketing/scripts/copy-token-css.mjs @@ -0,0 +1,15 @@ +import { copyFileSync, mkdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const dest = join(dirname(fileURLToPath(import.meta.url)), '../public/admin/vendor'); +mkdirSync(dest, { recursive: true }); + +const files = [ + ['@singleton-sd/tokens/css/dark', 'tokens-dark.css'], + ['@singleton-sd/tokens/css/root/dark', 'tokens-root-dark.css'], +]; + +for (const [specifier, name] of files) { + copyFileSync(fileURLToPath(import.meta.resolve(specifier)), join(dest, name)); +} diff --git a/apps/marketing/src/styles/global.css b/apps/marketing/src/styles/global.css index 60bc0eb..8665999 100644 --- a/apps/marketing/src/styles/global.css +++ b/apps/marketing/src/styles/global.css @@ -1,111 +1,36 @@ -/* Singleton SD design tokens — canonical source, not vendored/hardcoded. - Both themes imported; active theme via [data-theme] on . - Product semantic roles map Figma foundations (bg/fg/accent/border/status). */ -@import url('https://tokens.design.singletonsd.com/css/light/_variables.css'); -@import url('https://tokens.design.singletonsd.com/css/dark/_variables.css'); +/* Singleton SD design tokens from @singleton-sd/tokens (npm, not CDN). + Both themes; active theme via [data-theme] on . */ +@import '@singleton-sd/tokens/css/light'; +@import '@singleton-sd/tokens/css/dark'; @tailwind base; @tailwind components; @tailwind utilities; -[data-theme='light'] { - --pk-bg: var(--colors-white, var(--colors-gray-100)); - --pk-fg: var(--colors-gray-900); - --pk-muted: var(--colors-gray-600); - --pk-subtle: var(--colors-gray-400); - --pk-accent: var(--colors-yellow-light-600); - --pk-accent-hover: var(--colors-yellow-light-700); - --pk-accent-muted: var(--colors-yellow-light-300); - --pk-accent-on: var(--colors-gray-900); - --pk-accent-tint: var(--colors-yellow-light-100); - --pk-border: var(--colors-gray-300); - --pk-border-muted: var(--colors-gray-200); - --pk-border-strong: var(--colors-gray-400); - --pk-inverse: var(--colors-gray-900); - --pk-error: var(--colors-red-600); - --pk-error-bg: var(--colors-red-100); - --pk-success: var(--colors-green-600); - --pk-success-bg: var(--colors-green-100); - - --fg-default: var(--pk-fg); - --fg-muted: var(--pk-muted); - --fg-subtle: var(--pk-subtle); - --fg-inverse: var(--colors-white, var(--colors-gray-100)); - --fg-brand: var(--pk-accent); - --bg-default: var(--pk-bg); - --bg-muted: var(--colors-gray-100); - --bg-subtle: var(--colors-gray-200); - --bg-inverse: var(--pk-inverse); - --bg-hero: var(--pk-accent-tint); - --accent-default: var(--pk-accent); - --accent-hover: var(--pk-accent-hover); - --accent-muted: var(--pk-accent-muted); - --accent-on-accent: var(--pk-accent-on); - --accent-bg: var(--pk-accent-tint); - --border-default: var(--pk-border); - --border-muted: var(--pk-border-muted); - --border-strong: var(--pk-border-strong); - --status-error: var(--pk-error); - --status-error-bg: var(--pk-error-bg); - --status-success: var(--pk-success); - --status-success-bg: var(--pk-success-bg); - --shadow-sm: 0 2px 4px color-mix(in srgb, var(--pk-fg) 10%, transparent); - --shadow-md: 0 4px 8px color-mix(in srgb, var(--pk-fg) 12%, transparent); - --shadow-lg: 0 12px 24px color-mix(in srgb, var(--pk-fg) 15%, transparent); -} - +/* + * Local product aliases the token package does not publish: + * - hero / brand-muted: page wash (FOUNDATIONS: no hero token; brand at lower emphasis) + * - shadows: package CSS currently emits invalid `dropShadow …` values + */ +[data-theme='light'], [data-theme='dark'] { - --pk-bg: var(--colors-gray-900); - --pk-fg: var(--colors-gray-100); - --pk-muted: var(--colors-gray-400); - --pk-subtle: var(--colors-gray-600); - --pk-accent: var(--colors-yellow-light-400); - --pk-accent-hover: var(--colors-yellow-light-300); - --pk-accent-muted: var(--colors-yellow-light-700); - --pk-accent-on: var(--colors-gray-900); - --pk-accent-tint: var(--colors-yellow-light-900); - --pk-border: var(--colors-gray-700); - --pk-border-muted: var(--colors-gray-800); - --pk-border-strong: var(--colors-gray-500); - --pk-inverse: var(--colors-gray-100); - --pk-error: var(--colors-red-400); - --pk-error-bg: var(--colors-red-900); - --pk-success: var(--colors-green-400); - --pk-success-bg: var(--colors-green-900); - - --fg-default: var(--pk-fg); - --fg-muted: var(--pk-muted); - --fg-subtle: var(--pk-subtle); - --fg-inverse: var(--colors-gray-900); - --fg-brand: var(--pk-accent); - --bg-default: var(--pk-bg); - --bg-muted: var(--colors-gray-800); - --bg-subtle: var(--colors-gray-700); - --bg-inverse: var(--colors-gray-100); - --bg-hero: var(--pk-accent-tint); - --accent-default: var(--pk-accent); - --accent-hover: var(--pk-accent-hover); - --accent-muted: var(--pk-accent-muted); - --accent-on-accent: var(--pk-accent-on); - --accent-bg: var(--pk-accent-tint); - --border-default: var(--pk-border); - --border-muted: var(--pk-border-muted); - --border-strong: var(--pk-border-strong); - --status-error: var(--pk-error); - --status-error-bg: var(--pk-error-bg); - --status-success: var(--pk-success); - --status-success-bg: var(--pk-success-bg); - --shadow-sm: 0 2px 4px color-mix(in srgb, var(--colors-gray-900) 45%, transparent); - --shadow-md: 0 4px 8px color-mix(in srgb, var(--colors-gray-900) 50%, transparent); - --shadow-lg: 0 12px 24px color-mix(in srgb, var(--colors-gray-900) 55%, transparent); + --mkt-color-background-brand-muted: color-mix( + in srgb, + var(--ssd-color-background-brand) 16%, + var(--ssd-color-background-default) + ); + --mkt-color-background-hero: var(--mkt-color-background-brand-muted); + --mkt-shadow-sm: 0 1px 2px color-mix(in srgb, var(--ssd-color-gray-900) 12%, transparent); + --mkt-shadow-md: 0 4px 8px color-mix(in srgb, var(--ssd-color-gray-900) 16%, transparent); + --mkt-shadow-lg: 0 12px 24px color-mix(in srgb, var(--ssd-color-gray-900) 20%, transparent); } body { margin: 0; min-height: 100vh; - font-family: var(--font-families-body), ui-sans-serif, system-ui, sans-serif; - background-color: var(--bg-default); - color: var(--fg-default); + font-family: var(--ssd-font-family-body), ui-sans-serif, system-ui, sans-serif; + background-color: var(--ssd-color-background-default); + color: var(--ssd-color-text-default); } .prose-legal h2 { diff --git a/apps/marketing/tailwind.config.mjs b/apps/marketing/tailwind.config.mjs index fb94183..3e77107 100644 --- a/apps/marketing/tailwind.config.mjs +++ b/apps/marketing/tailwind.config.mjs @@ -1,46 +1,47 @@ /** @type {import('tailwindcss').Config} */ -// Colors map to Singleton SD design token CSS variables — never hardcode palette hex. +// Product utility names (fg/bg/accent) map to @singleton-sd/tokens --ssd-* vars. export default { content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], theme: { extend: { colors: { fg: { - DEFAULT: 'var(--fg-default)', - muted: 'var(--fg-muted)', - subtle: 'var(--fg-subtle)', - inverse: 'var(--fg-inverse)', - brand: 'var(--fg-brand)', + DEFAULT: 'var(--ssd-color-text-default)', + muted: 'var(--ssd-color-text-muted)', + subtle: 'var(--ssd-color-text-subtle)', + inverse: 'var(--ssd-color-text-inverse)', + brand: 'var(--ssd-color-text-brand)', }, bg: { - DEFAULT: 'var(--bg-default)', - muted: 'var(--bg-muted)', - subtle: 'var(--bg-subtle)', - inverse: 'var(--bg-inverse)', - hero: 'var(--bg-hero)', + DEFAULT: 'var(--ssd-color-background-default)', + muted: 'var(--ssd-color-background-muted)', + subtle: 'var(--ssd-color-background-subtle)', + inverse: 'var(--ssd-color-background-inverse)', + hero: 'var(--mkt-color-background-hero)', + elevated: 'var(--ssd-color-background-subtle)', }, accent: { - DEFAULT: 'var(--accent-default)', - hover: 'var(--accent-hover)', - muted: 'var(--accent-muted)', - on: 'var(--accent-on-accent)', - bg: 'var(--accent-bg)', + DEFAULT: 'var(--ssd-color-background-brand)', + hover: 'var(--ssd-color-background-brand-hovered)', + muted: 'var(--mkt-color-background-brand-muted)', + on: 'var(--ssd-color-text-on-brand)', + bg: 'var(--mkt-color-background-brand-muted)', }, border: { - DEFAULT: 'var(--border-default)', - muted: 'var(--border-muted)', - strong: 'var(--border-strong)', + DEFAULT: 'var(--ssd-color-border-default)', + muted: 'var(--ssd-color-border-muted)', + strong: 'var(--ssd-color-border-strong)', }, status: { - error: 'var(--status-error)', - 'error-bg': 'var(--status-error-bg)', - success: 'var(--status-success)', - 'success-bg': 'var(--status-success-bg)', + error: 'var(--ssd-color-feedback-danger-text)', + 'error-bg': 'var(--ssd-color-feedback-danger-background)', + success: 'var(--ssd-color-feedback-success-text)', + 'success-bg': 'var(--ssd-color-feedback-success-background)', }, }, fontFamily: { - heading: 'var(--font-families-heading)', - body: 'var(--font-families-body)', + heading: 'var(--ssd-font-family-heading)', + body: 'var(--ssd-font-family-body)', }, fontSize: { display: ['4.5rem', { lineHeight: '1', letterSpacing: '-0.02em', fontWeight: '500' }], @@ -60,15 +61,15 @@ export default { xxl: '8rem', }, borderRadius: { - sm: '0.25rem', - md: '0.5rem', - lg: '0.75rem', - xl: '1rem', + sm: 'var(--ssd-radius-sm)', + md: 'var(--ssd-radius-md)', + lg: 'var(--ssd-radius-lg)', + xl: 'var(--ssd-radius-xl)', }, boxShadow: { - sm: 'var(--shadow-sm)', - md: 'var(--shadow-md)', - lg: 'var(--shadow-lg)', + sm: 'var(--mkt-shadow-sm)', + md: 'var(--mkt-shadow-md)', + lg: 'var(--mkt-shadow-lg)', }, maxWidth: { content: '68.5rem', // 1096px — Figma 12-col content width diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a42aeca..02f25e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: '@astrojs/tailwind': specifier: ^5.1.4 version: 5.1.5(astro@4.16.19(@types/node@24.13.3)(rollup@4.62.4)(typescript@5.9.3))(tailwindcss@3.4.19(yaml@2.9.0))(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)) + '@singleton-sd/tokens': + specifier: 1.3.0 + version: 1.3.0 astro: specifier: ^4.16.18 version: 4.16.19(@types/node@24.13.3)(rollup@4.62.4)(typescript@5.9.3) @@ -1020,6 +1023,10 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@singleton-sd/tokens@1.3.0': + resolution: {integrity: sha1-ERZjS4tz7E0TIOEUVhjkyC8URRA=, tarball: https://gitlab.com/api/v4/projects/70589530/packages/npm/@singleton-sd/tokens/-/@singleton-sd/tokens-1.3.0.tgz} + engines: {node: 22.x} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -4449,6 +4456,8 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@singleton-sd/tokens@1.3.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} '@tsconfig/node10@1.0.12': {}