@adea-ai/themes 0.1.1 → 0.2.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.
@@ -15,57 +15,61 @@ export const generatedThemes: readonly AdeaThemeRecord[] = Object.freeze([
15
15
  "name": "Adea Dark",
16
16
  "appearance": "dark",
17
17
  "colors": {
18
- "background": "oklch(0.2645 0 0)",
19
- "foreground": "oklch(0.9911 0 0)",
20
- "surface": "oklch(0.2972 0 0)",
21
- "surfaceElevated": "oklch(0.325 0 0)",
22
- "surfaceHover": "oklch(0.3867 0 0)",
23
- "surfaceActive": "oklch(0.4202 0 0)",
24
- "border": "oklch(0.36 0 0)",
25
- "borderMuted": "oklch(0.3211 0 0)",
26
- "text": "oklch(0.9911 0 0)",
27
- "textMuted": "oklch(0.7155 0 0)",
28
- "textSubtle": "oklch(0.6334 0 0)",
29
- "accent": "oklch(0.9401 0 0)",
30
- "accentForeground": "oklch(0.325 0 0)",
18
+ "background": "oklch(0.1918 0.0238 265.37)",
19
+ "foreground": "oklch(0.7933 0.0217 261.76)",
20
+ "surface": "oklch(0.2268 0.0288 265.37)",
21
+ "surfaceElevated": "oklch(0.2568 0.033 265.37)",
22
+ "surfaceHover": "oklch(0.2918 0.038 265.37)",
23
+ "surfaceActive": "oklch(0.3268 0.038 265.37)",
24
+ "border": "oklch(0.3418 0.0214 265.37)",
25
+ "borderMuted": "oklch(0.2868 0.0143 265.37)",
26
+ "text": "oklch(0.7933 0.0217 261.76)",
27
+ "textMuted": "oklch(0.6308 0.0224 262.91)",
28
+ "textSubtle": "oklch(0.5339 0.0307 267.4)",
29
+ "accent": "oklch(0.7153 0.1518 253.31)",
30
+ "accentForeground": "oklch(0.1918 0.0238 265.37)",
31
31
  "success": "oklch(0.6951 0.1809 145.62)",
32
- "warning": "oklch(0.7904 0.1386 85.24)",
33
- "error": "oklch(0.7118 0.1434 29.38)",
32
+ "warning": "oklch(0.7196 0.1401 79.91)",
33
+ "error": "oklch(0.7345 0.1626 25.78)",
34
34
  "info": "oklch(0.7555 0.1155 202.14)"
35
35
  },
36
36
  "ansi": {
37
- "black": "oklch(0.3339 0.0223 256.4)",
38
- "red": "oklch(0.7465 0.1536 20.48)",
39
- "green": "oklch(0.7717 0.188 145.48)",
40
- "yellow": "oklch(0.7904 0.1386 85.24)",
41
- "blue": "oklch(0.7162 0.1369 258.28)",
42
- "magenta": "oklch(0.662 0.168 349.52)",
37
+ "black": "oklch(0.2737 0.0253 268.32)",
38
+ "red": "oklch(0.7345 0.1626 25.78)",
39
+ "green": "oklch(0.6951 0.1809 145.62)",
40
+ "yellow": "oklch(0.7196 0.1401 79.91)",
41
+ "blue": "oklch(0.7153 0.1518 253.31)",
42
+ "magenta": "oklch(0.7323 0.1668 301.69)",
43
43
  "cyan": "oklch(0.7555 0.1155 202.14)",
44
- "white": "oklch(0.8938 0.014 247.98)",
45
- "brightBlack": "oklch(0.4849 0.0196 251.02)",
46
- "brightRed": "oklch(0.7793 0.1291 20.43)",
47
- "brightGreen": "oklch(0.8159 0.1496 147.95)",
48
- "brightYellow": "oklch(0.8363 0.1238 83.41)",
49
- "brightBlue": "oklch(0.7814 0.1113 259.45)",
50
- "brightMagenta": "oklch(0.723 0.1473 349.04)",
51
- "brightCyan": "oklch(0.8063 0.1005 202.61)",
52
- "brightWhite": "oklch(0.9593 0.0069 247.9)"
53
- },
54
- "cursor": "oklch(0.9425 0.0111 243.66)",
55
- "selection": "oklch(0.4184 0.0829 250.44)",
44
+ "white": "oklch(0.4995 0.0339 266.07)",
45
+ "brightBlack": "oklch(0.3759 0.0307 267.4)",
46
+ "brightRed": "oklch(0.8014 0.1134 25.81)",
47
+ "brightGreen": "oklch(0.7717 0.188 145.48)",
48
+ "brightYellow": "oklch(0.7904 0.1386 85.24)",
49
+ "brightBlue": "oklch(0.7857 0.1153 246.66)",
50
+ "brightMagenta": "oklch(0.8005 0.1275 305.86)",
51
+ "brightCyan": "oklch(0.8036 0.1107 201.78)",
52
+ "brightWhite": "oklch(0.92 0.0138 258.35)"
53
+ },
54
+ "cursor": "oklch(0.7933 0.0217 261.76)",
55
+ "selection": "oklch(0.3286 0.0313 254.25)",
56
56
  "family": "adea",
57
57
  "familyLabel": "Adea",
58
58
  "label": "Dark",
59
- "description": "The default dark theme. A soft grey, never a near-black.",
59
+ "description": "The default dark theme. Aardvark Ink's quiet canvas, with GitHub's vivid hues on it.",
60
60
  "provenance": {
61
61
  "project": "Adea",
62
62
  "url": "https://github.com/adea-ai/themes",
63
- "license": "Apache-2.0"
63
+ "license": "Apache-2.0",
64
+ "bootstrappedFrom": [
65
+ "Aardvark Ink (iTerm2-Color-Schemes)",
66
+ "GitHub Dark Default (iTerm2-Color-Schemes)"
67
+ ]
64
68
  },
65
69
  "tags": [
66
70
  "dark",
67
- "neutral",
68
- "default"
71
+ "default",
72
+ "vivid"
69
73
  ]
70
74
  },
71
75
  {
package/src/normalize.ts CHANGED
@@ -51,6 +51,7 @@ import type {
51
51
  AdeaThemeRecord,
52
52
  AnsiKey,
53
53
  ThemeAppearance,
54
+ ThemeProvenance,
54
55
  } from './schema'
55
56
  import type { Base24Scheme, Base24Slot } from './adapters/base24'
56
57
  import { parseBase24Palette } from './adapters/base24'
@@ -182,6 +183,13 @@ export interface ThemeSourceSpec {
182
183
  ansi?: Partial<AdeaAnsi>
183
184
  cursor?: string
184
185
  selection?: string
186
+ /**
187
+ * A provenance that overrides the family's.
188
+ *
189
+ * For a theme that is not simply a member of a family — Adea's composed dark theme
190
+ * belongs to the `adea` family but has two upstream parents to name.
191
+ */
192
+ provenance?: ThemeProvenance
185
193
  }
186
194
 
187
195
  /** Something the normalizer did that a reviewer should know about. */
@@ -951,11 +959,7 @@ export function normalizeTheme(source: ThemeSourceSpec): NormalizedTheme {
951
959
  familyLabel: source.familyLabel,
952
960
  label: source.label,
953
961
  description: source.description,
954
- provenance: {
955
- project: '',
956
- url: '',
957
- license: '',
958
- },
962
+ provenance: source.provenance ?? { project: '', url: '', license: '' },
959
963
  tags: source.tags,
960
964
  },
961
965
  findings,
package/src/sources.ts CHANGED
@@ -58,6 +58,155 @@ export const ADEA_PROVENANCE: ThemeProvenance = Object.freeze({
58
58
  license: 'Apache-2.0',
59
59
  })
60
60
 
61
+ /**
62
+ * A theme composed from **two** upstream palettes.
63
+ *
64
+ * Adea's own dark theme is not a copy of any published palette, and it is not
65
+ * hand-authored either. It is a deliberate composition: the canvas, the greys, the
66
+ * cursor and the selection come from a palette chosen for exactly those — Aardvark
67
+ * Ink, whose near-black navy and muted blue-grey foreground are why it was picked —
68
+ * and the sixteen ANSI hues come from a second palette chosen for its saturation,
69
+ * GitHub's dark scheme.
70
+ *
71
+ * The split exists because the two properties are in tension inside one palette. A
72
+ * palette tuned for a comfortable canvas mutes its hues to agree with it; a palette
73
+ * tuned for vivid syntax colours tends to sit on a canvas that is very dark or very
74
+ * flat. Taking one of each yields a calm ground with legible colour on it.
75
+ *
76
+ * ## Why it is composed rather than merged by hand
77
+ *
78
+ * The alternative was to read both palettes and paste the merged hex into
79
+ * `palettes/adea-dark.json`. That works once and is wrong afterwards: nothing then
80
+ * records which donor each value came from, so neither donor could ever be
81
+ * refreshed. Composing in the vendor step keeps both parents named, keeps the values
82
+ * upstream's own bytes rather than a transcription, and makes "GitHub changed its
83
+ * blue" a one-line diff plus a rebuild.
84
+ */
85
+ export interface ComposedSource {
86
+ id: string
87
+ name: string
88
+ family: 'adea'
89
+ familyLabel: string
90
+ label: string
91
+ description: string
92
+ appearance: ThemeAppearance
93
+ tags: readonly string[]
94
+ /** The two donors' slugs in the dataset, and which half each supplies. */
95
+ donors: Readonly<Record<'structure' | 'hues', string>>
96
+ /**
97
+ * The Base24 slot map.
98
+ *
99
+ * Each entry is `[donor, key]`, where the donor is `structure` or `hues` and the
100
+ * key is a role name in that donor's published palette — the dataset's own names,
101
+ * so `purple` is Base24's magenta slot and `brightPurple` its bright magenta.
102
+ */
103
+ slots: Readonly<Partial<Record<Base24Slot, readonly ['structure' | 'hues', string]>>>
104
+ /**
105
+ * Slots neither donor supplies, derived from one of them.
106
+ *
107
+ * `slots` and `synthesise` are jointly total: between them they must cover every
108
+ * Base24 slot, and the vendor step asserts that rather than trusting it. The two
109
+ * are separate maps rather than one so that "this value came from a donor" and
110
+ * "this value was computed" stay distinguishable in review.
111
+ */
112
+ synthesise: Readonly<
113
+ Partial<
114
+ Record<
115
+ Base24Slot,
116
+ readonly ['structure' | 'hues', string, { rotate?: number; lighten?: number }]
117
+ >
118
+ >
119
+ >
120
+ /**
121
+ * ANSI roles pinned to the structure donor's own values.
122
+ *
123
+ * The greyscale ramp is normally *derived* by ranking a scheme's grey slots by
124
+ * measured contrast, because Base24 does not name `black`, `white` or
125
+ * `brightBlack` and light schemes invert them. That derivation is right for an
126
+ * imported palette and wrong here: Aardvark Ink publishes a greyscale it chose,
127
+ * the greyscale is the reason it was chosen as the structure donor, and
128
+ * re-deriving it gives back something close but not equal. So the four roles are
129
+ * pinned to the donor's own values instead.
130
+ */
131
+ ansiFromStructure: readonly ('black' | 'brightBlack' | 'white' | 'brightWhite')[]
132
+ /** The role supplying the accent, when the family's identity demands one. */
133
+ accentSlot?: 'blue' | 'magenta' | 'cyan' | 'green'
134
+ palette?: Partial<Record<Base24Slot, string>>
135
+ provenance: ThemeProvenance
136
+ }
137
+
138
+ /**
139
+ * Adea's composed dark theme.
140
+ *
141
+ * This was previously authored here as a neutral grey ramp with a monochrome accent.
142
+ * It is now a composition, so the default dark theme is derived from named upstream
143
+ * palettes and re-derivable, and it gains the saturated hues the earlier ramp did not
144
+ * have.
145
+ */
146
+ export const COMPOSED_SOURCES: readonly ComposedSource[] = Object.freeze([
147
+ {
148
+ id: 'adea-dark',
149
+ name: 'Adea Dark',
150
+ family: 'adea',
151
+ familyLabel: 'Adea',
152
+ label: 'Dark',
153
+ description:
154
+ "The default dark theme. Aardvark Ink's quiet canvas, with GitHub's vivid hues on it.",
155
+ appearance: 'dark',
156
+ tags: ['dark', 'default', 'vivid'],
157
+ donors: { structure: 'aardvark-ink', hues: 'github-dark-default' },
158
+ slots: {
159
+ // The structure donor. `base01` receives its `black` rather than a background
160
+ // step: an ANSI black equal to the background is invisible as foreground text,
161
+ // which is the defect the normalizer's ramp notes describe.
162
+ base00: ['structure', 'background'],
163
+ base01: ['structure', 'black'],
164
+ base02: ['structure', 'selection'],
165
+ base03: ['structure', 'brightBlack'],
166
+ base04: ['structure', 'white'],
167
+ base05: ['structure', 'foreground'],
168
+ base06: ['structure', 'brightWhite'],
169
+ base07: ['structure', 'brightWhite'],
170
+ // The hue donor. Base24's bright slots run red, yellow, green, cyan, blue,
171
+ // magenta — not the ANSI order of yellow before green.
172
+ base08: ['hues', 'red'],
173
+ base0A: ['hues', 'yellow'],
174
+ base0B: ['hues', 'green'],
175
+ base0C: ['hues', 'cyan'],
176
+ base0D: ['hues', 'blue'],
177
+ base0E: ['hues', 'purple'],
178
+ base12: ['hues', 'brightRed'],
179
+ base13: ['hues', 'brightYellow'],
180
+ base14: ['hues', 'brightGreen'],
181
+ base15: ['hues', 'brightCyan'],
182
+ base16: ['hues', 'brightBlue'],
183
+ base17: ['hues', 'brightPurple'],
184
+ },
185
+ synthesise: {
186
+ // Neither donor's ANSI set has an orange or a brown, and Base24 defines both.
187
+ // A hue rotation off red and yellow is how the export adapter fills them too,
188
+ // so a composed scheme and an exported one are synthesised identically.
189
+ base09: ['hues', 'red', { rotate: 26 }],
190
+ base0F: ['hues', 'yellow', { rotate: -34 }],
191
+ // The two rungs below the canvas, which the specification defines as "dark
192
+ // black" and "darker than that". Lightness slices of the structure donor's own
193
+ // canvas, so the theme keeps its hue as it darkens.
194
+ base10: ['structure', 'background', { lighten: -0.03 }],
195
+ base11: ['structure', 'background', { lighten: -0.06 }],
196
+ },
197
+ ansiFromStructure: ['black', 'brightBlack', 'white', 'brightWhite'],
198
+ provenance: {
199
+ project: 'Adea',
200
+ url: 'https://github.com/adea-ai/themes',
201
+ license: 'Apache-2.0',
202
+ bootstrappedFrom: [
203
+ 'Aardvark Ink (iTerm2-Color-Schemes)',
204
+ 'GitHub Dark Default (iTerm2-Color-Schemes)',
205
+ ],
206
+ },
207
+ },
208
+ ])
209
+
61
210
  /**
62
211
  * Per-family provenance.
63
212
  *
@@ -505,9 +654,9 @@ export const VENDORED_SOURCES: readonly VendoredSource[] = Object.freeze([
505
654
  * accent — the design system's default has to work as a default for everyone, which
506
655
  * means it cannot be anybody's favourite colour.
507
656
  *
508
- * Both appearances are generated from the accent-and-grey ramp below, so the two
509
- * variants cannot drift apart: a change to the ramp changes both, and the contrast
510
- * suite measures both.
657
+ * Only the light variant is authored here. Its dark counterpart was previously the
658
+ * second entry in this list and is now a {@link ComposedSource} built from two
659
+ * upstream palettes, which is where the default dark theme gets its saturated hues.
511
660
  */
512
661
  export interface AuthoredSource {
513
662
  id: string
@@ -626,57 +775,4 @@ export const AUTHORED_SOURCES: readonly AuthoredSource[] = Object.freeze([
626
775
  cursor: '#24292f',
627
776
  selection: '#b6c7ff',
628
777
  },
629
- {
630
- id: 'adea-dark',
631
- family: 'adea',
632
- familyLabel: 'Adea',
633
- label: 'Dark',
634
- name: 'Adea Dark',
635
- description: "The default dark theme. A soft grey, never a near-black.",
636
- appearance: 'dark',
637
- tags: ['dark', 'neutral', 'default'],
638
- ramp: {
639
- background: '#252525',
640
- foreground: '#fcfcfc',
641
- surface: '#2d2d2d',
642
- surfaceElevated: '#343434',
643
- surfaceHover: '#444444',
644
- surfaceActive: '#4d4d4d',
645
- border: '#3d3d3d',
646
- borderMuted: '#333333',
647
- textMuted: '#a3a3a3',
648
- textSubtle: '#8a8a8a',
649
- },
650
- accent: '#ebebeb',
651
- accentForeground: '#343434',
652
- status: {
653
- success: '#3fb950',
654
- warning: '#e3b341',
655
- // Lighter than the value the product shipped: `#e07060` measures 4.37:1 on
656
- // this theme's raised surface and 4.09:1 on a popover, which is where an
657
- // alert is most likely to sit. Same hue, same chroma, four steps up.
658
- error: '#ee7c6c',
659
- info: '#39c5cf',
660
- },
661
- ansi: {
662
- black: '#2f3742',
663
- red: '#ff8183',
664
- green: '#56d364',
665
- yellow: '#e3b341',
666
- blue: '#6ca4f8',
667
- magenta: '#db61a2',
668
- cyan: '#39c5cf',
669
- white: '#d5dde5',
670
- brightBlack: '#57606a',
671
- brightRed: '#ff9494',
672
- brightGreen: '#79dd8a',
673
- brightYellow: '#f0c264',
674
- brightBlue: '#8db9ff',
675
- brightMagenta: '#e87cb4',
676
- brightCyan: '#66d3dc',
677
- brightWhite: '#eef2f6',
678
- },
679
- cursor: '#e6edf3',
680
- selection: '#264f78',
681
- },
682
778
  ])