@letterstory/design 0.1.0

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/PRINCIPLES.md ADDED
@@ -0,0 +1,60 @@
1
+ <!-- Published copy. The source is AGENTS.md at the root of github.com/letterstory/letterstory; edit there and run design/scripts/build.mjs. -->
2
+
3
+ # Letterstory design principles
4
+
5
+ ## The four principles
6
+
7
+ 1. **Primitives + intelligence, never a baked opinion.** Every surface is an unopinionated
8
+ substrate (data structures, one pipeline, storable as plain data) plus an intelligence layer
9
+ that pushes clients in an opinionated direction _through_ those primitives. The tell that
10
+ you have baked an opinion into a primitive: a consumer wants to branch on "what kind of
11
+ thing is this". Thesis and diagnostic in `docs/architecture/primitives-and-assembly.md`.
12
+ Precedents: `src/types/article-plan.ts` (an untemplated post is `preference: []`, not a
13
+ mode), `src/lib/collections/capabilities.ts` (branch on `brand_subject` /
14
+ `publish_control`, never on `profile` or `kind`).
15
+
16
+ 2. **One engine.** A blog collection and a phantom are the same engine with different
17
+ capabilities. Templated and untemplated posts are the same writing pipeline with different
18
+ constraints. Fleet tabs read one derivation of one measure. If you are about to write a
19
+ second module "like X but for Y", stop and add a flag or a capability to X. A module that
20
+ stops being phantom-only loses its `phantom-` prefix in the same PR.
21
+
22
+ 3. **One operation, one function, many projections.** UI route, MCP tool, REST endpoint and CLI
23
+ command are _projections_ of one lib function with one Zod schema. The manifest is `TOOLS`
24
+ in `src/lib/mcp/registry.ts`; MCP, OpenAPI and `/docs` are derived from it at request time
25
+ and cannot drift (`src/app/docs/AGENTS.md`). Where the UI has its own `/api/*` route, that
26
+ route and the tool must call the same lib function and share the schema — derive the tool
27
+ contract with `.omit()` for key-derived fields exactly as
28
+ `src/lib/validations/collection-onboard.ts` does. A `// mirrors /api/...` comment is a
29
+ confession, not a pattern; the ratchet freezes the existing ones.
30
+
31
+ 4. **Primitives live in `components/ui`; features map meaning to tokens.** A feature decides
32
+ _what_ a state means (ok / warn / risk, rising / falling) and hands that to a primitive. It
33
+ never picks a colour, a pixel size, a table header class or a date format. Tokens are in
34
+ `src/app/globals.css` (`@theme inline`): `accent-peach`, `accent-mint`, `rising`,
35
+ `falling`, `warning`, `text-2xs`. Formatters are `src/lib/format/{date,number}.ts`.
36
+ The primitives in `components/ui`: `app-page` (THE page shell every `(app)` layout
37
+ renders; `surface="dark"` is a recorded decision, not a per-route class), `table`,
38
+ `empty-state`, `status-badge` (a feature passes a _tone_ — ok / warn / risk / rising /
39
+ falling / info / neutral — never a colour), `stat-tile` (one delta convention),
40
+ `copy-button` (+ `useCopyToClipboard`), `stage-progress` (one checklist for durable jobs),
41
+ `collapsible-section`, `tabs` (`variant="segmented"` replaced the perf tab bar). Hooks:
42
+ `useTabParam` / `usePersistedTab` (ONE `?tab=` URL contract) and `useConfirm` (the only
43
+ destructive-action confirmation; `confirm()` is an ESLint error). If the primitive you
44
+ need does not exist, add it there first, then use it. Cross-feature imports of another
45
+ feature's private module are an ESLint error.
46
+
47
+ ## The two review questions
48
+
49
+ Applied to every PR (from `docs/architecture/README.md`):
50
+
51
+ > When a consumer wants to branch on what kind of thing this is, the assembly layer failed to
52
+ > express it as a constraint.
53
+ >
54
+ > If you can't tell from the row who decided this and whether they can undo it, you wrote an
55
+ > opinion into a fact.
56
+
57
+ Add a third for surfaces:
58
+
59
+ > If the app can do it and an agent cannot (or vice versa), which projection is missing, and
60
+ > why did the shared function not force it?
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # `@letterstory/design`
2
+
3
+ The cross-repo layer of Letterstory's design system: the **tokens** (colour, radius, font,
4
+ type scale), the **phantom theme registry** Lettersprite renders, and the **design
5
+ principles** as a shipped file. Framework-neutral data with one thin adapter per framework,
6
+ so every Letterstory repo reads the same values and none of them copies a palette.
7
+
8
+ Zero runtime dependencies, no build step to install. Published from this directory by
9
+ `.github/workflows/publish-design.yml` (npm Trusted Publishing, on merge to `main`);
10
+ `design-version-guard.yml` requires a version bump whenever a shipped file changes.
11
+
12
+ ## What ships
13
+
14
+ | File | What it is |
15
+ | --------------------- | -------------------------------------------------------------------------------------------------- |
16
+ | `tokens.json` | **Source.** The token values as one grouped object: `color.{light,dark}`, `radius`, `font`, `size` |
17
+ | `tokens.css` | The tokens as CSS custom properties on `:root`, with the dark overrides under `.dark` |
18
+ | `tailwind-v4.css` | A `@theme inline` block mapping the custom properties to Tailwind v4 theme keys |
19
+ | `tailwind-v3.cjs` | A Tailwind v3 preset whose `theme.extend` references the custom properties |
20
+ | `styled.js` + `.d.ts` | A plain object of resolved values (no stylesheet needed) for styled-components and similar |
21
+ | `themes.json` | **Source.** The phantom theme registry: `groups` and `themes` (value, label, hint, group, tags) |
22
+ | `themes.d.ts` | Literal-union types for the registry (`PhantomBlogTheme`, `PhantomBlogThemeGroup`, …) |
23
+ | `PRINCIPLES.md` | The design principles, copied from the root `AGENTS.md` of the letterstory repo |
24
+
25
+ `tokens.json` and `themes.json` are the sources. Everything else is generated by
26
+ `scripts/build.mjs` and committed; a test regenerates and diffs, so editing a generated file
27
+ by hand fails CI. To change a value: edit the JSON, run `node design/scripts/build.mjs`,
28
+ commit both.
29
+
30
+ In `tokens.json`, `color.dark` holds only the values that differ from light (exactly what the
31
+ `.dark` block sets); `styled.js` resolves that into a full dark palette. Derived tokens keep
32
+ their CSS expressions (`brand-light` is `oklch(from var(--brand) …)`, `radius.sm` is
33
+ `calc(var(--radius) - 4px)`), so rebranding is one value: `color.light.brand`.
34
+
35
+ ## Consuming it
36
+
37
+ **Tailwind v4** (letterstory, lettersprite): import the tokens, then the mapping, after
38
+ Tailwind itself.
39
+
40
+ ```css
41
+ @import "tailwindcss";
42
+ @import "@letterstory/design/tokens.css";
43
+ @import "@letterstory/design/tailwind-v4.css";
44
+ ```
45
+
46
+ `bg-brand`, `text-accent-peach`, `text-rising`, `rounded-lg`, `text-2xs` now resolve to the
47
+ tokens. `--font-sans` / `--font-mono` are set under Tailwind's own names, so `font-sans` and
48
+ preflight's default pick up DM Sans with no mapping. Dark mode is the `.dark` class
49
+ (`@custom-variant dark (&:is(.dark *))` in your stylesheet).
50
+
51
+ **Tailwind v3** (lettertrace, kernels): load `tokens.css` on the page and add the preset.
52
+
53
+ ```js
54
+ // tailwind.config.js
55
+ module.exports = {
56
+ darkMode: "class",
57
+ presets: [require("@letterstory/design/tailwind-v3")],
58
+ };
59
+ ```
60
+
61
+ **styled-components** (the marketing repo): a resolved object, no stylesheet.
62
+
63
+ ```ts
64
+ import { tokens } from "@letterstory/design";
65
+ // tokens.color.light["accent-peach"] === "#ee724a"; tokens.color.dark.background; tokens.font.sans
66
+ ```
67
+
68
+ **Plain CSS** (admin, the company site): import `tokens.css` and use `var(--brand)`,
69
+ `var(--accent-peach)`, `var(--radius)`, `var(--font-size-2xs)` …
70
+
71
+ **Theme registry** (lettersprite, letterstory):
72
+
73
+ ```ts
74
+ import registry from "@letterstory/design/themes"; // themes.json, typed by themes.d.ts
75
+ import type { PhantomBlogTheme } from "@letterstory/design/themes";
76
+ ```
77
+
78
+ ## Where the values come from
79
+
80
+ The letterstory app is a **consumer**, not a source: `src/app/globals.css` imports
81
+ `design/tokens.css` and `design/tailwind-v4.css` by relative path and defines no token of its
82
+ own, and `src/types/deployments.ts` re-exports `themes.json` as `PHANTOM_BLOG_THEMES`. Edit
83
+ `design/tokens.json` or `design/themes.json`, never `globals.css`. The principles in
84
+ `PRINCIPLES.md` are generated from the root `AGENTS.md`; edit that.
85
+
86
+ ## Versioning
87
+
88
+ Bump `version` in `package.json` whenever a shipped file changes (the guard enforces it);
89
+ the merge to `main` publishes. Adding a token or a theme is a minor bump; changing a value is
90
+ a patch; renaming or removing a token is a major.
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@letterstory/design",
3
+ "version": "0.1.0",
4
+ "description": "Letterstory design tokens, the phantom theme registry, and the design principles — one source for every Letterstory repo.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "author": "Letterstory",
8
+ "homepage": "https://github.com/letterstory/letterstory/tree/main/design#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/letterstory/letterstory.git",
12
+ "directory": "design"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/letterstory/letterstory/issues"
16
+ },
17
+ "keywords": [
18
+ "letterstory",
19
+ "design-tokens",
20
+ "tailwind",
21
+ "styled-components",
22
+ "themes"
23
+ ],
24
+ "sideEffects": [
25
+ "*.css"
26
+ ],
27
+ "exports": {
28
+ ".": {
29
+ "types": "./styled.d.ts",
30
+ "default": "./styled.js"
31
+ },
32
+ "./tokens.css": "./tokens.css",
33
+ "./tokens.json": "./tokens.json",
34
+ "./tailwind-v4.css": "./tailwind-v4.css",
35
+ "./tailwind-v3": "./tailwind-v3.cjs",
36
+ "./styled": {
37
+ "types": "./styled.d.ts",
38
+ "default": "./styled.js"
39
+ },
40
+ "./themes": {
41
+ "types": "./themes.d.ts",
42
+ "default": "./themes.json"
43
+ },
44
+ "./themes.json": "./themes.json",
45
+ "./PRINCIPLES.md": "./PRINCIPLES.md",
46
+ "./package.json": "./package.json"
47
+ },
48
+ "files": [
49
+ "tokens.css",
50
+ "tokens.json",
51
+ "tailwind-v4.css",
52
+ "tailwind-v3.cjs",
53
+ "styled.js",
54
+ "styled.d.ts",
55
+ "themes.json",
56
+ "themes.d.ts",
57
+ "PRINCIPLES.md",
58
+ "README.md"
59
+ ],
60
+ "publishConfig": {
61
+ "access": "public"
62
+ }
63
+ }
package/styled.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ // GENERATED by design/scripts/build.mjs — do not edit by hand. Source: design/tokens.json.
2
+
3
+ export interface Palette {
4
+ readonly brand: string;
5
+ readonly "brand-foreground": string;
6
+ readonly "brand-light": string;
7
+ readonly "brand-text": string;
8
+ readonly "accent-peach": string;
9
+ readonly "accent-peach-hover": string;
10
+ readonly "accent-mint": string;
11
+ readonly rising: string;
12
+ readonly falling: string;
13
+ readonly warning: string;
14
+ readonly background: string;
15
+ readonly foreground: string;
16
+ readonly card: string;
17
+ readonly "card-foreground": string;
18
+ readonly popover: string;
19
+ readonly "popover-foreground": string;
20
+ readonly primary: string;
21
+ readonly "primary-foreground": string;
22
+ readonly secondary: string;
23
+ readonly "secondary-foreground": string;
24
+ readonly muted: string;
25
+ readonly "muted-foreground": string;
26
+ readonly accent: string;
27
+ readonly "accent-foreground": string;
28
+ readonly destructive: string;
29
+ readonly border: string;
30
+ readonly input: string;
31
+ readonly ring: string;
32
+ readonly "chart-1": string;
33
+ readonly "chart-2": string;
34
+ readonly "chart-3": string;
35
+ readonly "chart-4": string;
36
+ readonly "chart-5": string;
37
+ readonly sidebar: string;
38
+ readonly "sidebar-foreground": string;
39
+ readonly "sidebar-primary": string;
40
+ readonly "sidebar-primary-foreground": string;
41
+ readonly "sidebar-accent": string;
42
+ readonly "sidebar-accent-foreground": string;
43
+ readonly "sidebar-border": string;
44
+ readonly "sidebar-ring": string;
45
+ }
46
+
47
+ export interface Radius {
48
+ readonly DEFAULT: string;
49
+ readonly sm: string;
50
+ readonly md: string;
51
+ readonly lg: string;
52
+ readonly xl: string;
53
+ }
54
+
55
+ export interface FontFamily {
56
+ readonly sans: string;
57
+ readonly mono: string;
58
+ }
59
+
60
+ export interface TypeStep {
61
+ readonly fontSize: string;
62
+ readonly lineHeight: string;
63
+ }
64
+
65
+ export interface TypeScale {
66
+ readonly "2xs": TypeStep;
67
+ }
68
+
69
+ export interface DesignTokens {
70
+ readonly color: { readonly light: Palette; readonly dark: Palette };
71
+ readonly radius: Radius;
72
+ readonly font: FontFamily;
73
+ readonly size: TypeScale;
74
+ }
75
+
76
+ export declare const tokens: DesignTokens;
77
+ export default tokens;
package/styled.js ADDED
@@ -0,0 +1,115 @@
1
+ // GENERATED by design/scripts/build.mjs — do not edit by hand. Source: design/tokens.json.
2
+ //
3
+ // The tokens as a plain object of resolved CSS values — every var() reference is
4
+ // inlined, so this needs no stylesheet. `color.dark` is the full dark palette (light
5
+ // with the dark overrides applied). For a styled-components ThemeProvider:
6
+ // <ThemeProvider theme={tokens}>… then ${({ theme }) => theme.color.light["accent-peach"]}
7
+ export const tokens = {
8
+ color: {
9
+ light: {
10
+ brand: "oklch(0.332 0.074 243.6)",
11
+ "brand-foreground": "oklch(0.985 0 0)",
12
+ "brand-light": "oklch(from oklch(0.332 0.074 243.6) 0.82 0.06 h)",
13
+ "brand-text": "oklch(0.332 0.074 243.6)",
14
+ "accent-peach": "#ee724a",
15
+ "accent-peach-hover": "#e2643b",
16
+ "accent-mint": "#9af2e0",
17
+ rising: "oklch(0.72 0.17 155)",
18
+ falling: "oklch(0.63 0.21 25)",
19
+ warning: "oklch(0.75 0.16 75)",
20
+ background: "oklch(1 0 0)",
21
+ foreground: "oklch(0.145 0 0)",
22
+ card: "oklch(1 0 0)",
23
+ "card-foreground": "oklch(0.145 0 0)",
24
+ popover: "oklch(1 0 0)",
25
+ "popover-foreground": "oklch(0.145 0 0)",
26
+ primary: "oklch(0.205 0 0)",
27
+ "primary-foreground": "oklch(0.985 0 0)",
28
+ secondary: "oklch(0.97 0 0)",
29
+ "secondary-foreground": "oklch(0.205 0 0)",
30
+ muted: "oklch(0.97 0 0)",
31
+ "muted-foreground": "oklch(0.556 0 0)",
32
+ accent: "oklch(0.97 0 0)",
33
+ "accent-foreground": "oklch(0.205 0 0)",
34
+ destructive: "oklch(0.577 0.245 27.325)",
35
+ border: "oklch(0.922 0 0)",
36
+ input: "oklch(0.922 0 0)",
37
+ ring: "oklch(0.708 0 0)",
38
+ "chart-1": "oklch(0.646 0.222 41.116)",
39
+ "chart-2": "oklch(0.6 0.118 184.704)",
40
+ "chart-3": "oklch(0.398 0.07 227.392)",
41
+ "chart-4": "oklch(0.828 0.189 84.429)",
42
+ "chart-5": "oklch(0.769 0.188 70.08)",
43
+ sidebar: "oklch(0.985 0 0)",
44
+ "sidebar-foreground": "oklch(0.145 0 0)",
45
+ "sidebar-primary": "oklch(0.205 0 0)",
46
+ "sidebar-primary-foreground": "oklch(0.985 0 0)",
47
+ "sidebar-accent": "oklch(0.97 0 0)",
48
+ "sidebar-accent-foreground": "oklch(0.205 0 0)",
49
+ "sidebar-border": "oklch(0.922 0 0)",
50
+ "sidebar-ring": "oklch(0.708 0 0)",
51
+ },
52
+ dark: {
53
+ brand: "oklch(0.332 0.074 243.6)",
54
+ "brand-foreground": "oklch(0.985 0 0)",
55
+ "brand-light": "oklch(from oklch(0.332 0.074 243.6) 0.5 0.09 h)",
56
+ "brand-text": "oklch(from oklch(0.332 0.074 243.6) 0.72 0.1 h)",
57
+ "accent-peach": "#ee724a",
58
+ "accent-peach-hover": "#e2643b",
59
+ "accent-mint": "#9af2e0",
60
+ rising: "oklch(0.72 0.17 155)",
61
+ falling: "oklch(0.63 0.21 25)",
62
+ warning: "oklch(0.75 0.16 75)",
63
+ background: "oklch(0.145 0 0)",
64
+ foreground: "oklch(0.985 0 0)",
65
+ card: "oklch(0.205 0 0)",
66
+ "card-foreground": "oklch(0.985 0 0)",
67
+ popover: "oklch(0.205 0 0)",
68
+ "popover-foreground": "oklch(0.985 0 0)",
69
+ primary: "oklch(0.922 0 0)",
70
+ "primary-foreground": "oklch(0.205 0 0)",
71
+ secondary: "oklch(0.269 0 0)",
72
+ "secondary-foreground": "oklch(0.985 0 0)",
73
+ muted: "oklch(0.269 0 0)",
74
+ "muted-foreground": "oklch(0.708 0 0)",
75
+ accent: "oklch(0.269 0 0)",
76
+ "accent-foreground": "oklch(0.985 0 0)",
77
+ destructive: "oklch(0.704 0.191 22.216)",
78
+ border: "oklch(1 0 0 / 10%)",
79
+ input: "oklch(1 0 0 / 15%)",
80
+ ring: "oklch(0.556 0 0)",
81
+ "chart-1": "oklch(0.488 0.243 264.376)",
82
+ "chart-2": "oklch(0.696 0.17 162.48)",
83
+ "chart-3": "oklch(0.769 0.188 70.08)",
84
+ "chart-4": "oklch(0.692 0.162 38.8)",
85
+ "chart-5": "oklch(0.645 0.246 16.439)",
86
+ sidebar: "oklch(0.205 0 0)",
87
+ "sidebar-foreground": "oklch(0.985 0 0)",
88
+ "sidebar-primary": "oklch(0.488 0.243 264.376)",
89
+ "sidebar-primary-foreground": "oklch(0.985 0 0)",
90
+ "sidebar-accent": "oklch(0.269 0 0)",
91
+ "sidebar-accent-foreground": "oklch(0.985 0 0)",
92
+ "sidebar-border": "oklch(1 0 0 / 10%)",
93
+ "sidebar-ring": "oklch(0.556 0 0)",
94
+ },
95
+ },
96
+ radius: {
97
+ DEFAULT: "0.625rem",
98
+ sm: "calc(0.625rem - 4px)",
99
+ md: "calc(0.625rem - 2px)",
100
+ lg: "0.625rem",
101
+ xl: "calc(0.625rem + 4px)",
102
+ },
103
+ font: {
104
+ sans: '"DM Sans", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
105
+ mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
106
+ },
107
+ size: {
108
+ "2xs": {
109
+ fontSize: "0.6875rem",
110
+ lineHeight: "1rem",
111
+ },
112
+ },
113
+ };
114
+
115
+ export default tokens;
@@ -0,0 +1,75 @@
1
+ // GENERATED by design/scripts/build.mjs — do not edit by hand. Source: design/tokens.json.
2
+ //
3
+ // Tailwind v3 adapter: a preset whose theme.extend references the custom properties in
4
+ // tokens.css, so `bg-brand`, `text-rising`, `rounded-lg`, `text-2xs` resolve to the
5
+ // tokens. Load tokens.css on the page and add to tailwind.config:
6
+ // presets: [require("@letterstory/design/tailwind-v3")], darkMode: "class"
7
+ // Opacity modifiers (bg-brand/50) do not apply to var() colours in v3; use color-mix().
8
+ module.exports = {
9
+ theme: {
10
+ extend: {
11
+ colors: {
12
+ brand: "var(--brand)",
13
+ "brand-foreground": "var(--brand-foreground)",
14
+ "brand-light": "var(--brand-light)",
15
+ "brand-text": "var(--brand-text)",
16
+ "accent-peach": "var(--accent-peach)",
17
+ "accent-peach-hover": "var(--accent-peach-hover)",
18
+ "accent-mint": "var(--accent-mint)",
19
+ rising: "var(--rising)",
20
+ falling: "var(--falling)",
21
+ warning: "var(--warning)",
22
+ background: "var(--background)",
23
+ foreground: "var(--foreground)",
24
+ card: "var(--card)",
25
+ "card-foreground": "var(--card-foreground)",
26
+ popover: "var(--popover)",
27
+ "popover-foreground": "var(--popover-foreground)",
28
+ primary: "var(--primary)",
29
+ "primary-foreground": "var(--primary-foreground)",
30
+ secondary: "var(--secondary)",
31
+ "secondary-foreground": "var(--secondary-foreground)",
32
+ muted: "var(--muted)",
33
+ "muted-foreground": "var(--muted-foreground)",
34
+ accent: "var(--accent)",
35
+ "accent-foreground": "var(--accent-foreground)",
36
+ destructive: "var(--destructive)",
37
+ border: "var(--border)",
38
+ input: "var(--input)",
39
+ ring: "var(--ring)",
40
+ "chart-1": "var(--chart-1)",
41
+ "chart-2": "var(--chart-2)",
42
+ "chart-3": "var(--chart-3)",
43
+ "chart-4": "var(--chart-4)",
44
+ "chart-5": "var(--chart-5)",
45
+ sidebar: "var(--sidebar)",
46
+ "sidebar-foreground": "var(--sidebar-foreground)",
47
+ "sidebar-primary": "var(--sidebar-primary)",
48
+ "sidebar-primary-foreground": "var(--sidebar-primary-foreground)",
49
+ "sidebar-accent": "var(--sidebar-accent)",
50
+ "sidebar-accent-foreground": "var(--sidebar-accent-foreground)",
51
+ "sidebar-border": "var(--sidebar-border)",
52
+ "sidebar-ring": "var(--sidebar-ring)",
53
+ },
54
+ borderRadius: {
55
+ DEFAULT: "var(--radius)",
56
+ sm: "var(--radius-sm)",
57
+ md: "var(--radius-md)",
58
+ lg: "var(--radius-lg)",
59
+ xl: "var(--radius-xl)",
60
+ },
61
+ fontFamily: {
62
+ sans: "var(--font-sans)",
63
+ mono: "var(--font-mono)",
64
+ },
65
+ fontSize: {
66
+ "2xs": [
67
+ "var(--font-size-2xs)",
68
+ {
69
+ lineHeight: "var(--line-height-2xs)",
70
+ },
71
+ ],
72
+ },
73
+ },
74
+ },
75
+ };
@@ -0,0 +1,58 @@
1
+ /* GENERATED by design/scripts/build.mjs — do not edit by hand. Source: design/tokens.json.
2
+ *
3
+ * Tailwind v4 adapter: maps the custom properties in tokens.css onto Tailwind theme
4
+ * keys, so `bg-brand`, `text-rising`, `rounded-lg`, `text-2xs` resolve to the
5
+ * tokens. Import tokens.css first, then this file, after `@import "tailwindcss"`.
6
+ * `inline` so utilities emit the var() reference and follow the .dark switch.
7
+ * Fonts are not mapped here: tokens.css sets --font-sans / --font-mono under
8
+ * Tailwind's own names.
9
+ */
10
+ @theme inline {
11
+ --radius-sm: calc(var(--radius) - 4px);
12
+ --radius-md: calc(var(--radius) - 2px);
13
+ --radius-lg: var(--radius);
14
+ --radius-xl: calc(var(--radius) + 4px);
15
+ --color-brand: var(--brand);
16
+ --color-brand-foreground: var(--brand-foreground);
17
+ --color-brand-light: var(--brand-light);
18
+ --color-brand-text: var(--brand-text);
19
+ --color-accent-peach: var(--accent-peach);
20
+ --color-accent-peach-hover: var(--accent-peach-hover);
21
+ --color-accent-mint: var(--accent-mint);
22
+ --color-rising: var(--rising);
23
+ --color-falling: var(--falling);
24
+ --color-warning: var(--warning);
25
+ --color-background: var(--background);
26
+ --color-foreground: var(--foreground);
27
+ --color-card: var(--card);
28
+ --color-card-foreground: var(--card-foreground);
29
+ --color-popover: var(--popover);
30
+ --color-popover-foreground: var(--popover-foreground);
31
+ --color-primary: var(--primary);
32
+ --color-primary-foreground: var(--primary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-secondary-foreground: var(--secondary-foreground);
35
+ --color-muted: var(--muted);
36
+ --color-muted-foreground: var(--muted-foreground);
37
+ --color-accent: var(--accent);
38
+ --color-accent-foreground: var(--accent-foreground);
39
+ --color-destructive: var(--destructive);
40
+ --color-border: var(--border);
41
+ --color-input: var(--input);
42
+ --color-ring: var(--ring);
43
+ --color-chart-1: var(--chart-1);
44
+ --color-chart-2: var(--chart-2);
45
+ --color-chart-3: var(--chart-3);
46
+ --color-chart-4: var(--chart-4);
47
+ --color-chart-5: var(--chart-5);
48
+ --color-sidebar: var(--sidebar);
49
+ --color-sidebar-foreground: var(--sidebar-foreground);
50
+ --color-sidebar-primary: var(--sidebar-primary);
51
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
52
+ --color-sidebar-accent: var(--sidebar-accent);
53
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
54
+ --color-sidebar-border: var(--sidebar-border);
55
+ --color-sidebar-ring: var(--sidebar-ring);
56
+ --text-2xs: 0.6875rem;
57
+ --text-2xs--line-height: 1rem;
58
+ }
package/themes.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ // GENERATED by design/scripts/build.mjs — do not edit by hand. Source: design/themes.json.
2
+ //
3
+ // The phantom theme registry, typed with the literal unions the data implies, so a
4
+ // consumer's `theme` field is checked against the registry rather than `string`.
5
+
6
+ /** The families the theme picker groups themes under. */
7
+ export type PhantomBlogThemeGroup = "baseline" | "publication" | "brand" | "business" | "technology";
8
+
9
+ /** Every theme Lettersprite's THEME env var selects. */
10
+ export type PhantomBlogTheme =
11
+ | "sleek"
12
+ | "classic"
13
+ | "minimal"
14
+ | "magazine"
15
+ | "midnight"
16
+ | "gazette"
17
+ | "dispatch"
18
+ | "metro"
19
+ | "review"
20
+ | "signal"
21
+ | "current"
22
+ | "ledger"
23
+ | "atelier"
24
+ | "noir"
25
+ | "chronicle"
26
+ | "verdure"
27
+ | "couture"
28
+ | "atlas"
29
+ | "missive"
30
+ | "commit"
31
+ | "apex"
32
+ | "mint"
33
+ | "bloom"
34
+ | "cinder"
35
+ | "hearth"
36
+ | "harvest"
37
+ | "terra"
38
+ | "verve"
39
+ | "estate"
40
+ | "counsel"
41
+ | "clinic"
42
+ | "nook"
43
+ | "lumen"
44
+ | "cellar"
45
+ | "horizon"
46
+ | "derma"
47
+ | "meridian"
48
+ | "keystone"
49
+ | "vantage"
50
+ | "cobalt"
51
+ | "sterling"
52
+ | "momentum"
53
+ | "forum"
54
+ | "cadence"
55
+ | "evergreen"
56
+ | "beacon"
57
+ | "kernel"
58
+ | "graphite"
59
+ | "cortex"
60
+ | "cipher"
61
+ | "pulse"
62
+ | "apiwire"
63
+ | "bourse"
64
+ | "folio"
65
+ | "kiosk";
66
+
67
+ export interface PhantomBlogThemeGroupEntry {
68
+ readonly id: PhantomBlogThemeGroup;
69
+ readonly label: string;
70
+ }
71
+
72
+ export interface PhantomBlogThemeEntry {
73
+ readonly value: PhantomBlogTheme;
74
+ readonly label: string;
75
+ readonly hint: string;
76
+ readonly group: PhantomBlogThemeGroup;
77
+ /**
78
+ * Industry / aesthetic descriptors an identity generator matches against a site's
79
+ * inferred subject to shortlist fitting themes. Purely advisory — every theme stays
80
+ * user-selectable regardless of tags.
81
+ */
82
+ readonly tags: readonly string[];
83
+ }
84
+
85
+ export interface PhantomThemeRegistry {
86
+ readonly groups: readonly PhantomBlogThemeGroupEntry[];
87
+ readonly themes: readonly PhantomBlogThemeEntry[];
88
+ }
89
+
90
+ declare const registry: PhantomThemeRegistry;
91
+ export default registry;
package/themes.json ADDED
@@ -0,0 +1,411 @@
1
+ {
2
+ "groups": [
3
+ {
4
+ "id": "baseline",
5
+ "label": "Clean & versatile"
6
+ },
7
+ {
8
+ "id": "publication",
9
+ "label": "Magazine fronts"
10
+ },
11
+ {
12
+ "id": "brand",
13
+ "label": "Brand & product"
14
+ },
15
+ {
16
+ "id": "business",
17
+ "label": "Business & corporate"
18
+ },
19
+ {
20
+ "id": "technology",
21
+ "label": "Technology"
22
+ }
23
+ ],
24
+ "themes": [
25
+ {
26
+ "value": "sleek",
27
+ "label": "Sleek",
28
+ "hint": "Clean, modern, versatile",
29
+ "group": "baseline",
30
+ "tags": ["tech", "saas", "startup", "modern", "versatile"]
31
+ },
32
+ {
33
+ "value": "classic",
34
+ "label": "Classic",
35
+ "hint": "Editorial serif, timeless",
36
+ "group": "baseline",
37
+ "tags": ["finance", "professional-services", "editorial", "timeless", "versatile"]
38
+ },
39
+ {
40
+ "value": "minimal",
41
+ "label": "Minimal",
42
+ "hint": "Bare, text-first",
43
+ "group": "baseline",
44
+ "tags": ["design", "writing", "essay", "understated", "versatile"]
45
+ },
46
+ {
47
+ "value": "magazine",
48
+ "label": "Magazine",
49
+ "hint": "Bold, image-forward",
50
+ "group": "baseline",
51
+ "tags": ["media", "consumer", "culture", "bold", "versatile"]
52
+ },
53
+ {
54
+ "value": "midnight",
55
+ "label": "Midnight",
56
+ "hint": "Dark mode, high contrast",
57
+ "group": "baseline",
58
+ "tags": ["developer", "security", "gaming", "crypto", "dark"]
59
+ },
60
+ {
61
+ "value": "gazette",
62
+ "label": "Gazette",
63
+ "hint": "Broadsheet, paper-of-record serif",
64
+ "group": "publication",
65
+ "tags": ["news", "policy", "b2b", "authoritative", "serif"]
66
+ },
67
+ {
68
+ "value": "dispatch",
69
+ "label": "Dispatch",
70
+ "hint": "Tech-news feed",
71
+ "group": "publication",
72
+ "tags": ["tech", "startup", "developer", "news"]
73
+ },
74
+ {
75
+ "value": "metro",
76
+ "label": "Metro",
77
+ "hint": "Bold asymmetric culture mosaic",
78
+ "group": "publication",
79
+ "tags": ["culture", "urban", "creative", "media", "bold"]
80
+ },
81
+ {
82
+ "value": "review",
83
+ "label": "Review",
84
+ "hint": "Refined longform column",
85
+ "group": "publication",
86
+ "tags": ["longform", "essay", "academic", "consulting", "refined"]
87
+ },
88
+ {
89
+ "value": "signal",
90
+ "label": "Signal",
91
+ "hint": "Vivid tech-culture hero grid",
92
+ "group": "publication",
93
+ "tags": ["tech", "media", "consumer", "vivid"]
94
+ },
95
+ {
96
+ "value": "current",
97
+ "label": "Current",
98
+ "hint": "High-contrast electric grotesque",
99
+ "group": "publication",
100
+ "tags": ["fashion", "culture", "creative", "bold"]
101
+ },
102
+ {
103
+ "value": "ledger",
104
+ "label": "Ledger",
105
+ "hint": "Business / markets desk",
106
+ "group": "publication",
107
+ "tags": ["finance", "markets", "fintech", "b2b", "professional"]
108
+ },
109
+ {
110
+ "value": "atelier",
111
+ "label": "Atelier",
112
+ "hint": "Warm-paper lifestyle quarterly",
113
+ "group": "publication",
114
+ "tags": ["lifestyle", "craft", "hospitality", "warm"]
115
+ },
116
+ {
117
+ "value": "noir",
118
+ "label": "Noir",
119
+ "hint": "Dark neon terminal",
120
+ "group": "publication",
121
+ "tags": ["developer", "security", "crypto", "gaming", "dark"]
122
+ },
123
+ {
124
+ "value": "chronicle",
125
+ "label": "Chronicle",
126
+ "hint": "Authoritative politics + economics",
127
+ "group": "publication",
128
+ "tags": ["policy", "economics", "legal", "authoritative"]
129
+ },
130
+ {
131
+ "value": "verdure",
132
+ "label": "Verdure",
133
+ "hint": "Nutrition & wellness pinboard",
134
+ "group": "brand",
135
+ "tags": ["nutrition", "wellness", "health", "fitness", "green"]
136
+ },
137
+ {
138
+ "value": "couture",
139
+ "label": "Couture",
140
+ "hint": "High-fashion magazine cover",
141
+ "group": "brand",
142
+ "tags": ["fashion", "luxury", "beauty"]
143
+ },
144
+ {
145
+ "value": "atlas",
146
+ "label": "Atlas",
147
+ "hint": "Travel & exploration gallery",
148
+ "group": "brand",
149
+ "tags": ["travel", "outdoors", "hospitality", "exploration"]
150
+ },
151
+ {
152
+ "value": "missive",
153
+ "label": "Missive",
154
+ "hint": "Personal newsletter digest",
155
+ "group": "brand",
156
+ "tags": ["newsletter", "personal", "community", "writing"]
157
+ },
158
+ {
159
+ "value": "commit",
160
+ "label": "Commit",
161
+ "hint": "Engineering changelog timeline",
162
+ "group": "brand",
163
+ "tags": ["developer", "engineering", "saas", "technical"]
164
+ },
165
+ {
166
+ "value": "apex",
167
+ "label": "Apex",
168
+ "hint": "Athletic performance, dark cinematic",
169
+ "group": "brand",
170
+ "tags": ["fitness", "sports", "performance", "dark"]
171
+ },
172
+ {
173
+ "value": "mint",
174
+ "label": "Mint",
175
+ "hint": "Consumer fintech, rounded grid",
176
+ "group": "brand",
177
+ "tags": ["fintech", "consumer", "finance", "friendly"]
178
+ },
179
+ {
180
+ "value": "bloom",
181
+ "label": "Bloom",
182
+ "hint": "Beauty editorial, blush grid",
183
+ "group": "brand",
184
+ "tags": ["beauty", "skincare", "lifestyle", "soft"]
185
+ },
186
+ {
187
+ "value": "cinder",
188
+ "label": "Cinder",
189
+ "hint": "Gaming & esports, neon mosaic",
190
+ "group": "brand",
191
+ "tags": ["gaming", "esports", "entertainment", "dark"]
192
+ },
193
+ {
194
+ "value": "hearth",
195
+ "label": "Hearth",
196
+ "hint": "Interior design & architecture",
197
+ "group": "brand",
198
+ "tags": ["interior-design", "architecture", "real-estate", "warm"]
199
+ },
200
+ {
201
+ "value": "harvest",
202
+ "label": "Harvest",
203
+ "hint": "Food & recipe mosaic",
204
+ "group": "brand",
205
+ "tags": ["food", "recipes", "hospitality", "consumer"]
206
+ },
207
+ {
208
+ "value": "terra",
209
+ "label": "Terra",
210
+ "hint": "Sustainability & climate longform",
211
+ "group": "brand",
212
+ "tags": ["sustainability", "climate", "nonprofit", "green"]
213
+ },
214
+ {
215
+ "value": "verve",
216
+ "label": "Verve",
217
+ "hint": "Music & pop culture mosaic",
218
+ "group": "brand",
219
+ "tags": ["music", "pop-culture", "entertainment", "bold"]
220
+ },
221
+ {
222
+ "value": "estate",
223
+ "label": "Estate",
224
+ "hint": "Luxury real estate hero",
225
+ "group": "brand",
226
+ "tags": ["real-estate", "luxury", "professional"]
227
+ },
228
+ {
229
+ "value": "counsel",
230
+ "label": "Counsel",
231
+ "hint": "Professional-services thought leadership",
232
+ "group": "brand",
233
+ "tags": ["legal", "consulting", "professional-services", "b2b"]
234
+ },
235
+ {
236
+ "value": "clinic",
237
+ "label": "Clinic",
238
+ "hint": "Healthcare & biotech feed",
239
+ "group": "brand",
240
+ "tags": ["healthcare", "biotech", "medical", "clean"]
241
+ },
242
+ {
243
+ "value": "nook",
244
+ "label": "Nook",
245
+ "hint": "Parenting & family, cozy grid",
246
+ "group": "brand",
247
+ "tags": ["parenting", "family", "education", "cozy"]
248
+ },
249
+ {
250
+ "value": "lumen",
251
+ "label": "Lumen",
252
+ "hint": "Science & ideas feature feed",
253
+ "group": "brand",
254
+ "tags": ["science", "research", "education", "clean"]
255
+ },
256
+ {
257
+ "value": "cellar",
258
+ "label": "Cellar",
259
+ "hint": "Craft coffee & beverage journal",
260
+ "group": "brand",
261
+ "tags": ["coffee", "beverage", "craft", "hospitality"]
262
+ },
263
+ {
264
+ "value": "horizon",
265
+ "label": "Horizon",
266
+ "hint": "B2B SaaS product marketing, glossy gradient",
267
+ "group": "brand",
268
+ "tags": ["saas", "b2b", "tech", "product-marketing"]
269
+ },
270
+ {
271
+ "value": "derma",
272
+ "label": "Derma",
273
+ "hint": "Editorial skin/beauty, Caslon serif headlines",
274
+ "group": "brand",
275
+ "tags": ["beauty", "skincare", "wellness", "editorial", "serif"]
276
+ },
277
+ {
278
+ "value": "meridian",
279
+ "label": "Meridian",
280
+ "hint": "Management-consulting column, brass serif",
281
+ "group": "business",
282
+ "tags": ["consulting", "management", "professional-services", "b2b", "serif"]
283
+ },
284
+ {
285
+ "value": "keystone",
286
+ "label": "Keystone",
287
+ "hint": "Financial-services feed",
288
+ "group": "business",
289
+ "tags": ["finance", "financial-services", "banking", "b2b", "professional"]
290
+ },
291
+ {
292
+ "value": "vantage",
293
+ "label": "Vantage",
294
+ "hint": "B2B SaaS product grid",
295
+ "group": "business",
296
+ "tags": ["saas", "b2b", "product", "tech", "professional"]
297
+ },
298
+ {
299
+ "value": "cobalt",
300
+ "label": "Cobalt",
301
+ "hint": "Enterprise cloud & IT, dark",
302
+ "group": "business",
303
+ "tags": ["cloud", "enterprise", "it", "infrastructure", "dark"]
304
+ },
305
+ {
306
+ "value": "sterling",
307
+ "label": "Sterling",
308
+ "hint": "Private banking & wealth column",
309
+ "group": "business",
310
+ "tags": ["wealth", "private-banking", "finance", "luxury", "professional"]
311
+ },
312
+ {
313
+ "value": "momentum",
314
+ "label": "Momentum",
315
+ "hint": "Marketing & brand, glossy",
316
+ "group": "business",
317
+ "tags": ["marketing", "brand", "advertising", "growth", "bold"]
318
+ },
319
+ {
320
+ "value": "forum",
321
+ "label": "Forum",
322
+ "hint": "Economics & policy broadsheet",
323
+ "group": "business",
324
+ "tags": ["economics", "policy", "think-tank", "b2b", "authoritative"]
325
+ },
326
+ {
327
+ "value": "cadence",
328
+ "label": "Cadence",
329
+ "hint": "People & future-of-work grid",
330
+ "group": "business",
331
+ "tags": ["hr", "future-of-work", "workplace", "people", "b2b"]
332
+ },
333
+ {
334
+ "value": "evergreen",
335
+ "label": "Evergreen",
336
+ "hint": "Sustainability & ESG grid",
337
+ "group": "business",
338
+ "tags": ["sustainability", "esg", "climate", "corporate", "green"]
339
+ },
340
+ {
341
+ "value": "beacon",
342
+ "label": "Beacon",
343
+ "hint": "Professional-services feed",
344
+ "group": "business",
345
+ "tags": ["professional-services", "consulting", "advisory", "b2b"]
346
+ },
347
+ {
348
+ "value": "kernel",
349
+ "label": "Kernel",
350
+ "hint": "Developer & open-source, minimal mono",
351
+ "group": "technology",
352
+ "tags": ["developer", "open-source", "engineering", "technical", "minimal"]
353
+ },
354
+ {
355
+ "value": "graphite",
356
+ "label": "Graphite",
357
+ "hint": "Dark engineering writing, minimal",
358
+ "group": "technology",
359
+ "tags": ["engineering", "developer", "writing", "technical", "dark", "minimal"]
360
+ },
361
+ {
362
+ "value": "cortex",
363
+ "label": "Cortex",
364
+ "hint": "AI / ML research lab, glossy",
365
+ "group": "technology",
366
+ "tags": ["ai", "ml", "research", "data-science", "tech"]
367
+ },
368
+ {
369
+ "value": "cipher",
370
+ "label": "Cipher",
371
+ "hint": "Cybersecurity feed, alert red",
372
+ "group": "technology",
373
+ "tags": ["security", "cybersecurity", "infosec", "enterprise", "dark"]
374
+ },
375
+ {
376
+ "value": "pulse",
377
+ "label": "Pulse",
378
+ "hint": "DevOps & observability mosaic",
379
+ "group": "technology",
380
+ "tags": ["devops", "observability", "sre", "infrastructure", "tech"]
381
+ },
382
+ {
383
+ "value": "apiwire",
384
+ "label": "API Wire",
385
+ "hint": "Dark developer gallery, sci-fi type",
386
+ "group": "technology",
387
+ "tags": ["developer", "api", "integration", "security", "dark", "technical"]
388
+ },
389
+ {
390
+ "value": "bourse",
391
+ "label": "Bourse",
392
+ "hint": "Creative Boom-style VC/finance magazine, stock photos",
393
+ "group": "business",
394
+ "tags": ["venture", "vc", "finance", "startup", "editorial", "magazine"]
395
+ },
396
+ {
397
+ "value": "folio",
398
+ "label": "Folio",
399
+ "hint": "grafill-style design magazine, immersive photo articles",
400
+ "group": "business",
401
+ "tags": ["finance", "accounting", "b2b", "saas", "editorial", "design"]
402
+ },
403
+ {
404
+ "value": "kiosk",
405
+ "label": "Kiosk",
406
+ "hint": "Taste-style warm editorial newsstand, riso covers",
407
+ "group": "publication",
408
+ "tags": ["editorial", "magazine", "tech", "b2b", "warm", "design"]
409
+ }
410
+ ]
411
+ }
package/tokens.css ADDED
@@ -0,0 +1,111 @@
1
+ /* GENERATED by design/scripts/build.mjs — do not edit by hand. Source: design/tokens.json.
2
+ *
3
+ * Letterstory design tokens as CSS custom properties. Framework-neutral: load this
4
+ * stylesheet, then map the variables with the adapter for your framework
5
+ * (tailwind-v4.css, tailwind-v3.cjs, styled.js) or read them with var() directly.
6
+ * Dark values switch on a `.dark` class on an ancestor (next-themes attribute="class").
7
+ *
8
+ * --font-sans and --font-mono deliberately use Tailwind v4's own theme names so the
9
+ * font-sans / font-mono utilities and preflight's html default pick them up with no
10
+ * @theme mapping; radii and the type scale are mirrored in each adapter instead.
11
+ */
12
+ :root {
13
+ --radius: 0.625rem;
14
+ --radius-sm: calc(var(--radius) - 4px);
15
+ --radius-md: calc(var(--radius) - 2px);
16
+ --radius-lg: var(--radius);
17
+ --radius-xl: calc(var(--radius) + 4px);
18
+ /* BRAND COLOR — the single source (#093959). To rebrand, change color.light.brand in tokens.json. */
19
+ --brand: oklch(0.332 0.074 243.6);
20
+ /* text/icons on a --brand background */
21
+ --brand-foreground: oklch(0.985 0 0);
22
+ /* derived from --brand (same hue, adjusted lightness/chroma) */
23
+ --brand-light: oklch(from var(--brand) 0.82 0.06 h);
24
+ /* naked brand text/icons on light surfaces */
25
+ --brand-text: var(--brand);
26
+ /* "Burnt Peach" — the product accent */
27
+ --accent-peach: #ee724a;
28
+ --accent-peach-hover: #e2643b;
29
+ --accent-mint: #9af2e0;
30
+ /* emerald — up / good */
31
+ --rising: oklch(0.72 0.17 155);
32
+ /* red — down / at risk */
33
+ --falling: oklch(0.63 0.21 25);
34
+ /* amber — needs attention, not yet at risk */
35
+ --warning: oklch(0.75 0.16 75);
36
+ --background: oklch(1 0 0);
37
+ --foreground: oklch(0.145 0 0);
38
+ --card: oklch(1 0 0);
39
+ --card-foreground: oklch(0.145 0 0);
40
+ --popover: oklch(1 0 0);
41
+ --popover-foreground: oklch(0.145 0 0);
42
+ --primary: oklch(0.205 0 0);
43
+ --primary-foreground: oklch(0.985 0 0);
44
+ --secondary: oklch(0.97 0 0);
45
+ --secondary-foreground: oklch(0.205 0 0);
46
+ --muted: oklch(0.97 0 0);
47
+ --muted-foreground: oklch(0.556 0 0);
48
+ --accent: oklch(0.97 0 0);
49
+ --accent-foreground: oklch(0.205 0 0);
50
+ --destructive: oklch(0.577 0.245 27.325);
51
+ --border: oklch(0.922 0 0);
52
+ --input: oklch(0.922 0 0);
53
+ --ring: oklch(0.708 0 0);
54
+ --chart-1: oklch(0.646 0.222 41.116);
55
+ --chart-2: oklch(0.6 0.118 184.704);
56
+ --chart-3: oklch(0.398 0.07 227.392);
57
+ --chart-4: oklch(0.828 0.189 84.429);
58
+ --chart-5: oklch(0.769 0.188 70.08);
59
+ --sidebar: oklch(0.985 0 0);
60
+ --sidebar-foreground: oklch(0.145 0 0);
61
+ --sidebar-primary: oklch(0.205 0 0);
62
+ --sidebar-primary-foreground: oklch(0.985 0 0);
63
+ --sidebar-accent: oklch(0.97 0 0);
64
+ --sidebar-accent-foreground: oklch(0.205 0 0);
65
+ --sidebar-border: oklch(0.922 0 0);
66
+ --sidebar-ring: oklch(0.708 0 0);
67
+ --font-sans:
68
+ "DM Sans", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
69
+ "Noto Color Emoji";
70
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
71
+ --font-size-2xs: 0.6875rem;
72
+ --line-height-2xs: 1rem;
73
+ }
74
+
75
+ .dark {
76
+ /* --brand and --brand-foreground inherit from :root; only the on-dark variants are lightened */
77
+ --brand-light: oklch(from var(--brand) 0.5 0.09 h);
78
+ --brand-text: oklch(from var(--brand) 0.72 0.1 h);
79
+ --background: oklch(0.145 0 0);
80
+ --foreground: oklch(0.985 0 0);
81
+ --card: oklch(0.205 0 0);
82
+ --card-foreground: oklch(0.985 0 0);
83
+ --popover: oklch(0.205 0 0);
84
+ --popover-foreground: oklch(0.985 0 0);
85
+ --primary: oklch(0.922 0 0);
86
+ --primary-foreground: oklch(0.205 0 0);
87
+ --secondary: oklch(0.269 0 0);
88
+ --secondary-foreground: oklch(0.985 0 0);
89
+ --muted: oklch(0.269 0 0);
90
+ --muted-foreground: oklch(0.708 0 0);
91
+ --accent: oklch(0.269 0 0);
92
+ --accent-foreground: oklch(0.985 0 0);
93
+ --destructive: oklch(0.704 0.191 22.216);
94
+ --border: oklch(1 0 0 / 10%);
95
+ --input: oklch(1 0 0 / 15%);
96
+ --ring: oklch(0.556 0 0);
97
+ --chart-1: oklch(0.488 0.243 264.376);
98
+ --chart-2: oklch(0.696 0.17 162.48);
99
+ --chart-3: oklch(0.769 0.188 70.08);
100
+ /* Burnt Peach (#EE724A) */
101
+ --chart-4: oklch(0.692 0.162 38.8);
102
+ --chart-5: oklch(0.645 0.246 16.439);
103
+ --sidebar: oklch(0.205 0 0);
104
+ --sidebar-foreground: oklch(0.985 0 0);
105
+ --sidebar-primary: oklch(0.488 0.243 264.376);
106
+ --sidebar-primary-foreground: oklch(0.985 0 0);
107
+ --sidebar-accent: oklch(0.269 0 0);
108
+ --sidebar-accent-foreground: oklch(0.985 0 0);
109
+ --sidebar-border: oklch(1 0 0 / 10%);
110
+ --sidebar-ring: oklch(0.556 0 0);
111
+ }
package/tokens.json ADDED
@@ -0,0 +1,117 @@
1
+ {
2
+ "color": {
3
+ "light": {
4
+ "brand": "oklch(0.332 0.074 243.6)",
5
+ "brand-foreground": "oklch(0.985 0 0)",
6
+ "brand-light": "oklch(from var(--brand) 0.82 0.06 h)",
7
+ "brand-text": "var(--brand)",
8
+ "accent-peach": "#ee724a",
9
+ "accent-peach-hover": "#e2643b",
10
+ "accent-mint": "#9af2e0",
11
+ "rising": "oklch(0.72 0.17 155)",
12
+ "falling": "oklch(0.63 0.21 25)",
13
+ "warning": "oklch(0.75 0.16 75)",
14
+ "background": "oklch(1 0 0)",
15
+ "foreground": "oklch(0.145 0 0)",
16
+ "card": "oklch(1 0 0)",
17
+ "card-foreground": "oklch(0.145 0 0)",
18
+ "popover": "oklch(1 0 0)",
19
+ "popover-foreground": "oklch(0.145 0 0)",
20
+ "primary": "oklch(0.205 0 0)",
21
+ "primary-foreground": "oklch(0.985 0 0)",
22
+ "secondary": "oklch(0.97 0 0)",
23
+ "secondary-foreground": "oklch(0.205 0 0)",
24
+ "muted": "oklch(0.97 0 0)",
25
+ "muted-foreground": "oklch(0.556 0 0)",
26
+ "accent": "oklch(0.97 0 0)",
27
+ "accent-foreground": "oklch(0.205 0 0)",
28
+ "destructive": "oklch(0.577 0.245 27.325)",
29
+ "border": "oklch(0.922 0 0)",
30
+ "input": "oklch(0.922 0 0)",
31
+ "ring": "oklch(0.708 0 0)",
32
+ "chart-1": "oklch(0.646 0.222 41.116)",
33
+ "chart-2": "oklch(0.6 0.118 184.704)",
34
+ "chart-3": "oklch(0.398 0.07 227.392)",
35
+ "chart-4": "oklch(0.828 0.189 84.429)",
36
+ "chart-5": "oklch(0.769 0.188 70.08)",
37
+ "sidebar": "oklch(0.985 0 0)",
38
+ "sidebar-foreground": "oklch(0.145 0 0)",
39
+ "sidebar-primary": "oklch(0.205 0 0)",
40
+ "sidebar-primary-foreground": "oklch(0.985 0 0)",
41
+ "sidebar-accent": "oklch(0.97 0 0)",
42
+ "sidebar-accent-foreground": "oklch(0.205 0 0)",
43
+ "sidebar-border": "oklch(0.922 0 0)",
44
+ "sidebar-ring": "oklch(0.708 0 0)"
45
+ },
46
+ "dark": {
47
+ "brand-light": "oklch(from var(--brand) 0.5 0.09 h)",
48
+ "brand-text": "oklch(from var(--brand) 0.72 0.1 h)",
49
+ "background": "oklch(0.145 0 0)",
50
+ "foreground": "oklch(0.985 0 0)",
51
+ "card": "oklch(0.205 0 0)",
52
+ "card-foreground": "oklch(0.985 0 0)",
53
+ "popover": "oklch(0.205 0 0)",
54
+ "popover-foreground": "oklch(0.985 0 0)",
55
+ "primary": "oklch(0.922 0 0)",
56
+ "primary-foreground": "oklch(0.205 0 0)",
57
+ "secondary": "oklch(0.269 0 0)",
58
+ "secondary-foreground": "oklch(0.985 0 0)",
59
+ "muted": "oklch(0.269 0 0)",
60
+ "muted-foreground": "oklch(0.708 0 0)",
61
+ "accent": "oklch(0.269 0 0)",
62
+ "accent-foreground": "oklch(0.985 0 0)",
63
+ "destructive": "oklch(0.704 0.191 22.216)",
64
+ "border": "oklch(1 0 0 / 10%)",
65
+ "input": "oklch(1 0 0 / 15%)",
66
+ "ring": "oklch(0.556 0 0)",
67
+ "chart-1": "oklch(0.488 0.243 264.376)",
68
+ "chart-2": "oklch(0.696 0.17 162.48)",
69
+ "chart-3": "oklch(0.769 0.188 70.08)",
70
+ "chart-4": "oklch(0.692 0.162 38.8)",
71
+ "chart-5": "oklch(0.645 0.246 16.439)",
72
+ "sidebar": "oklch(0.205 0 0)",
73
+ "sidebar-foreground": "oklch(0.985 0 0)",
74
+ "sidebar-primary": "oklch(0.488 0.243 264.376)",
75
+ "sidebar-primary-foreground": "oklch(0.985 0 0)",
76
+ "sidebar-accent": "oklch(0.269 0 0)",
77
+ "sidebar-accent-foreground": "oklch(0.985 0 0)",
78
+ "sidebar-border": "oklch(1 0 0 / 10%)",
79
+ "sidebar-ring": "oklch(0.556 0 0)"
80
+ }
81
+ },
82
+ "radius": {
83
+ "DEFAULT": "0.625rem",
84
+ "sm": "calc(var(--radius) - 4px)",
85
+ "md": "calc(var(--radius) - 2px)",
86
+ "lg": "var(--radius)",
87
+ "xl": "calc(var(--radius) + 4px)"
88
+ },
89
+ "font": {
90
+ "sans": [
91
+ "DM Sans",
92
+ "ui-sans-serif",
93
+ "system-ui",
94
+ "sans-serif",
95
+ "Apple Color Emoji",
96
+ "Segoe UI Emoji",
97
+ "Segoe UI Symbol",
98
+ "Noto Color Emoji"
99
+ ],
100
+ "mono": [
101
+ "ui-monospace",
102
+ "SFMono-Regular",
103
+ "Menlo",
104
+ "Monaco",
105
+ "Consolas",
106
+ "Liberation Mono",
107
+ "Courier New",
108
+ "monospace"
109
+ ]
110
+ },
111
+ "size": {
112
+ "2xs": {
113
+ "fontSize": "0.6875rem",
114
+ "lineHeight": "1rem"
115
+ }
116
+ }
117
+ }