@treeseed/core 0.8.8 → 0.8.9

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.
Files changed (73) hide show
  1. package/dist/components/content/ContentStatusLegend.astro +4 -4
  2. package/dist/components/docs/BookFontControls.astro +9 -9
  3. package/dist/components/docs/DesktopSidebarToggle.astro +8 -8
  4. package/dist/components/docs/Footer.astro +6 -6
  5. package/dist/components/docs/PageTitle.astro +1 -1
  6. package/dist/components/docs/ThemeSelect.astro +3 -1
  7. package/dist/components/forms/ContactForm.astro +21 -21
  8. package/dist/components/forms/FooterSubscribeForm.astro +9 -9
  9. package/dist/components/site/BookList.astro +7 -7
  10. package/dist/components/site/CTASection.astro +4 -4
  11. package/dist/components/site/ChronicleList.astro +6 -6
  12. package/dist/components/site/Hero.astro +3 -3
  13. package/dist/components/site/PathCard.astro +5 -5
  14. package/dist/components/site/ProfileList.astro +5 -5
  15. package/dist/components/site/RouteNotFound.astro +5 -5
  16. package/dist/components/site/SectionIntro.astro +3 -3
  17. package/dist/components/site/StageBanner.astro +2 -2
  18. package/dist/components/site/TrustCallout.astro +3 -3
  19. package/dist/components/ui/data/ActionList.astro +51 -0
  20. package/dist/components/ui/data/Badge.astro +19 -0
  21. package/dist/components/ui/data/DataTable.astro +51 -0
  22. package/dist/components/ui/data/KeyValueList.astro +28 -0
  23. package/dist/components/ui/data/MetricCard.astro +25 -0
  24. package/dist/components/ui/data/MetricGrid.astro +27 -0
  25. package/dist/components/ui/data/StatusPill.astro +20 -0
  26. package/dist/components/ui/forms/Button.astro +52 -0
  27. package/dist/components/ui/forms/Field.astro +39 -0
  28. package/dist/components/ui/forms/FormActions.astro +12 -0
  29. package/dist/components/ui/forms/PasswordMeter.astro +80 -0
  30. package/dist/components/ui/forms/RadioGroup.astro +55 -0
  31. package/dist/components/ui/forms/Select.astro +44 -0
  32. package/dist/components/ui/forms/TextInput.astro +58 -0
  33. package/dist/components/ui/forms/Textarea.astro +45 -0
  34. package/dist/components/ui/layout/PageHeader.astro +45 -0
  35. package/dist/components/ui/shell/AppShell.astro +110 -0
  36. package/dist/components/ui/shell/BottomNav.astro +35 -0
  37. package/dist/components/ui/shell/ProjectHeader.astro +66 -0
  38. package/dist/components/ui/shell/PublicShell.astro +108 -0
  39. package/dist/components/ui/shell/RailNav.astro +35 -0
  40. package/dist/components/ui/shell/TopBar.astro +52 -0
  41. package/dist/components/ui/surface/Card.astro +46 -0
  42. package/dist/components/ui/surface/EmptyState.astro +45 -0
  43. package/dist/components/ui/surface/Panel.astro +54 -0
  44. package/dist/components/ui/theme/ThemeMenu.astro +32 -0
  45. package/dist/components/ui/theme/ThemePreviewSwatch.astro +18 -0
  46. package/dist/components/ui/theme/ThemeScript.astro +105 -0
  47. package/dist/components/ui/theme/ThemeSelector.astro +202 -0
  48. package/dist/components/ui/types.js +0 -0
  49. package/dist/dev.js +14 -2
  50. package/dist/layouts/AuthoredEntryLayout.astro +27 -27
  51. package/dist/layouts/BookLayout.astro +10 -10
  52. package/dist/layouts/ContentLayout.astro +4 -4
  53. package/dist/layouts/MainLayout.astro +27 -25
  54. package/dist/layouts/NoteLayout.astro +6 -6
  55. package/dist/layouts/ProfileLayout.astro +17 -17
  56. package/dist/pages/404.astro +6 -6
  57. package/dist/pages/contact.astro +4 -4
  58. package/dist/pages/docs-runtime/[...slug].astro +12 -12
  59. package/dist/pages/docs-runtime/index.astro +13 -13
  60. package/dist/pages/index.astro +28 -28
  61. package/dist/pages/ui/index.astro +216 -0
  62. package/dist/site.js +3 -2
  63. package/dist/styles/app-shell.css +398 -0
  64. package/dist/styles/forms.css +258 -0
  65. package/dist/styles/global.css +119 -119
  66. package/dist/styles/prose.css +11 -11
  67. package/dist/styles/theme.css +177 -0
  68. package/dist/styles/tokens.css +62 -22
  69. package/dist/styles/ui.css +551 -0
  70. package/dist/utils/content-status.js +5 -5
  71. package/dist/utils/site-config.js +2 -2
  72. package/dist/utils/theme.js +352 -40
  73. package/package.json +35 -2
@@ -0,0 +1,105 @@
1
+ ---
2
+ import {
3
+ buildTreeseedThemeCss,
4
+ getBuiltInColorSchemes,
5
+ normalizeThemePreference,
6
+ type ThemePreference,
7
+ } from '../../../utils/theme.js';
8
+
9
+ interface Props {
10
+ defaultScheme?: ThemePreference['scheme'];
11
+ defaultMode?: ThemePreference['mode'];
12
+ includeCss?: boolean;
13
+ preferDefaultPreference?: boolean;
14
+ }
15
+
16
+ const preference = normalizeThemePreference({
17
+ scheme: (Astro.props as Props).defaultScheme,
18
+ mode: (Astro.props as Props).defaultMode,
19
+ });
20
+ const { includeCss = true, preferDefaultPreference = false } = Astro.props as Props;
21
+ const builtInSchemeIds = getBuiltInColorSchemes().map((scheme) => scheme.id);
22
+ const themeCss = buildTreeseedThemeCss();
23
+ ---
24
+
25
+ {includeCss ? <style is:inline set:html={themeCss}></style> : null}
26
+
27
+ <script
28
+ is:inline
29
+ define:vars={{
30
+ builtInSchemeIds,
31
+ defaultScheme: preference.scheme,
32
+ defaultMode: preference.mode,
33
+ preferDefaultPreference,
34
+ }}
35
+ >
36
+ (() => {
37
+ const schemeKey = 'treeseed_color_scheme';
38
+ const modeKey = 'treeseed_theme_mode';
39
+ const modes = new Set(['light', 'dark', 'system']);
40
+ const schemes = new Set(builtInSchemeIds);
41
+ const root = document.documentElement;
42
+
43
+ function readCookie(name) {
44
+ const pair = document.cookie
45
+ .split('; ')
46
+ .find((entry) => entry.startsWith(`${name}=`));
47
+ return pair ? decodeURIComponent(pair.slice(name.length + 1)) : '';
48
+ }
49
+
50
+ function readStored(name) {
51
+ try {
52
+ return window.sessionStorage.getItem(name) || window.localStorage.getItem(name) || '';
53
+ } catch {
54
+ return '';
55
+ }
56
+ }
57
+
58
+ function store(name, value) {
59
+ try {
60
+ window.sessionStorage.setItem(name, value);
61
+ } catch {
62
+ // Session storage can be disabled; local storage/cookies still carry the preference.
63
+ }
64
+ try {
65
+ window.localStorage.setItem(name, value);
66
+ } catch {
67
+ // Local storage can be disabled; cookies still carry the preference for SSR.
68
+ }
69
+ }
70
+
71
+ function resolveScheme(value) {
72
+ return schemes.has(value) ? value : defaultScheme;
73
+ }
74
+
75
+ function resolveMode(value) {
76
+ return modes.has(value) ? value : defaultMode;
77
+ }
78
+
79
+ function resolveRenderedMode(mode) {
80
+ if (mode !== 'system') return mode;
81
+ return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
82
+ }
83
+
84
+ const storedScheme = readStored(schemeKey) || readCookie(schemeKey);
85
+ const storedModeValue = readStored(modeKey) || readCookie(modeKey);
86
+ const scheme = preferDefaultPreference ? resolveScheme(defaultScheme) : resolveScheme(storedScheme || defaultScheme);
87
+ const storedMode = preferDefaultPreference ? resolveMode(defaultMode) : resolveMode(storedModeValue || defaultMode);
88
+ const renderedMode = resolveRenderedMode(storedMode);
89
+
90
+ root.dataset.tsScheme = scheme;
91
+ root.dataset.tsMode = renderedMode;
92
+ if (storedMode === 'system') {
93
+ root.dataset.tsModeSource = 'system';
94
+ } else {
95
+ delete root.dataset.tsModeSource;
96
+ }
97
+ store(schemeKey, scheme);
98
+ store(modeKey, storedMode);
99
+ window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener?.('change', () => {
100
+ if (root.dataset.tsModeSource === 'system') {
101
+ root.dataset.tsMode = resolveRenderedMode('system');
102
+ }
103
+ });
104
+ })();
105
+ </script>
@@ -0,0 +1,202 @@
1
+ ---
2
+ import {
3
+ getBuiltInColorSchemes,
4
+ normalizeThemePreference,
5
+ type ThemePreference,
6
+ } from '../../../utils/theme.js';
7
+ import ThemePreviewSwatch from './ThemePreviewSwatch.astro';
8
+
9
+ interface Props {
10
+ label?: string;
11
+ selectedScheme?: ThemePreference['scheme'];
12
+ selectedMode?: ThemePreference['mode'];
13
+ includeHiddenFields?: boolean;
14
+ schemeFieldName?: string;
15
+ modeFieldName?: string;
16
+ compact?: boolean;
17
+ }
18
+
19
+ const {
20
+ label = 'Appearance',
21
+ selectedScheme,
22
+ selectedMode,
23
+ includeHiddenFields = false,
24
+ schemeFieldName = 'colorScheme',
25
+ modeFieldName = 'themeMode',
26
+ compact = true,
27
+ } = Astro.props as Props;
28
+
29
+ const schemes = getBuiltInColorSchemes();
30
+ const preference = normalizeThemePreference({ scheme: selectedScheme, mode: selectedMode });
31
+ const activeScheme = schemes.find((scheme) => scheme.id === preference.scheme) ?? schemes[0];
32
+ const initialSwatches = preference.mode === 'dark'
33
+ ? activeScheme.modeSwatches.dark
34
+ : activeScheme.modeSwatches.light;
35
+ const modes = [
36
+ { value: 'system', label: 'System' },
37
+ { value: 'light', label: 'Light' },
38
+ { value: 'dark', label: 'Dark' },
39
+ ];
40
+ ---
41
+
42
+ <div
43
+ class:list={['ts-theme-selector', compact && 'ts-theme-selector--compact']}
44
+ data-ts-theme-selector
45
+ data-selected-scheme={preference.scheme}
46
+ data-selected-mode={preference.mode}
47
+ data-scheme-field={schemeFieldName}
48
+ data-mode-field={modeFieldName}
49
+ >
50
+ <div class="ts-theme-selector__summary">
51
+ <ThemePreviewSwatch swatches={initialSwatches} label={`${activeScheme.name} preview`} />
52
+ <span class="ts-theme-selector__label">
53
+ <span>{label}</span>
54
+ <small data-ts-theme-preview-label>{activeScheme.name} · {preference.mode}</small>
55
+ </span>
56
+ </div>
57
+ <div class="ts-theme-selector__controls">
58
+ <label class="ts-theme-selector__field">
59
+ <span>Color scheme</span>
60
+ <select data-ts-theme-scheme-select aria-label="Color scheme">
61
+ {schemes.map((scheme) => (
62
+ <option value={scheme.id} selected={scheme.id === preference.scheme}>
63
+ {scheme.name}
64
+ </option>
65
+ ))}
66
+ </select>
67
+ </label>
68
+ <label class="ts-theme-selector__field">
69
+ <span>Mode</span>
70
+ <select data-ts-theme-mode-select aria-label="Theme mode">
71
+ {modes.map((mode) => (
72
+ <option value={mode.value} selected={mode.value === preference.mode}>
73
+ {mode.label}
74
+ </option>
75
+ ))}
76
+ </select>
77
+ </label>
78
+ </div>
79
+ {includeHiddenFields ? (
80
+ <>
81
+ <input type="hidden" name={schemeFieldName} value={preference.scheme} data-ts-theme-scheme-field />
82
+ <input type="hidden" name={modeFieldName} value={preference.mode} data-ts-theme-mode-field />
83
+ </>
84
+ ) : null}
85
+ </div>
86
+
87
+ <script is:inline define:vars={{ schemes }}>
88
+ (() => {
89
+ const schemeKey = 'treeseed_color_scheme';
90
+ const modeKey = 'treeseed_theme_mode';
91
+ const maxAge = 60 * 60 * 24 * 365;
92
+ const modeValues = new Set(['light', 'dark', 'system']);
93
+ const schemeValues = new Set(schemes.map((scheme) => scheme.id));
94
+ const schemeMap = new Map(schemes.map((scheme) => [scheme.id, scheme]));
95
+ const media = window.matchMedia?.('(prefers-color-scheme: dark)');
96
+
97
+ function cookie(name, value) {
98
+ const secure = window.location.protocol === 'https:' ? '; Secure' : '';
99
+ document.cookie = `${name}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; SameSite=Lax${secure}`;
100
+ }
101
+
102
+ function store(name, value) {
103
+ try {
104
+ window.sessionStorage.setItem(name, value);
105
+ } catch {
106
+ // Session storage can be disabled; local storage/cookies still carry the preference.
107
+ }
108
+ try {
109
+ window.localStorage.setItem(name, value);
110
+ } catch {
111
+ // Storage can be disabled; cookies still carry the preference for SSR.
112
+ }
113
+ }
114
+
115
+ function renderedMode(mode) {
116
+ if (mode !== 'system') return mode;
117
+ return media?.matches ? 'dark' : 'light';
118
+ }
119
+
120
+ function applyTheme(scheme, mode, persist = true) {
121
+ const root = document.documentElement;
122
+ const safeScheme = schemeValues.has(scheme) ? scheme : 'fern';
123
+ const safeMode = modeValues.has(mode) ? mode : 'system';
124
+ root.dataset.tsScheme = safeScheme;
125
+ root.dataset.tsMode = renderedMode(safeMode);
126
+ if (safeMode === 'system') {
127
+ root.dataset.tsModeSource = 'system';
128
+ } else {
129
+ delete root.dataset.tsModeSource;
130
+ }
131
+ if (persist) {
132
+ cookie(schemeKey, safeScheme);
133
+ cookie(modeKey, safeMode);
134
+ store(schemeKey, safeScheme);
135
+ store(modeKey, safeMode);
136
+ }
137
+ window.dispatchEvent(new CustomEvent('treeseed:theme-change', {
138
+ detail: { scheme: safeScheme, mode: safeMode, renderedMode: renderedMode(safeMode) },
139
+ }));
140
+ }
141
+
142
+ function modeSwatches(scheme, mode) {
143
+ const rendered = renderedMode(mode);
144
+ return scheme.modeSwatches?.[rendered] || scheme.swatches || [];
145
+ }
146
+
147
+ function updateSwatch(selector, schemeId, mode) {
148
+ const scheme = schemeMap.get(schemeId);
149
+ const swatch = selector.querySelector('.ts-theme-swatch');
150
+ const previewLabel = selector.querySelector('[data-ts-theme-preview-label]');
151
+ if (!scheme || !swatch) return;
152
+ swatch.setAttribute('aria-label', `${scheme.name} preview`);
153
+ modeSwatches(scheme, mode).slice(0, 4).forEach((value, index) => {
154
+ swatch.style.setProperty(`--ts-preview-swatch-${index + 1}`, value);
155
+ });
156
+ if (previewLabel) {
157
+ previewLabel.textContent = `${scheme.name} · ${mode === 'system' ? `System ${renderedMode(mode)}` : mode}`;
158
+ }
159
+ }
160
+
161
+ function currentPreference() {
162
+ const root = document.documentElement;
163
+ const scheme = schemeValues.has(root.dataset.tsScheme) ? root.dataset.tsScheme : '';
164
+ const mode = root.dataset.tsModeSource === 'system' ? 'system' : root.dataset.tsMode;
165
+ return {
166
+ scheme,
167
+ mode: modeValues.has(mode) ? mode : '',
168
+ };
169
+ }
170
+
171
+ function bind(selector) {
172
+ const schemeSelect = selector.querySelector('[data-ts-theme-scheme-select]');
173
+ const modeSelect = selector.querySelector('[data-ts-theme-mode-select]');
174
+ const schemeField = selector.querySelector('[data-ts-theme-scheme-field]');
175
+ const modeField = selector.querySelector('[data-ts-theme-mode-field]');
176
+ if (!(schemeSelect instanceof HTMLSelectElement) || !(modeSelect instanceof HTMLSelectElement)) return;
177
+
178
+ const activePreference = currentPreference();
179
+ if (activePreference.scheme) schemeSelect.value = activePreference.scheme;
180
+ if (activePreference.mode) modeSelect.value = activePreference.mode;
181
+
182
+ function sync(persist = true) {
183
+ const scheme = schemeSelect.value;
184
+ const mode = modeSelect.value;
185
+ if (schemeField instanceof HTMLInputElement) schemeField.value = scheme;
186
+ if (modeField instanceof HTMLInputElement) modeField.value = mode;
187
+ updateSwatch(selector, scheme, mode);
188
+ applyTheme(scheme, mode, persist);
189
+ }
190
+
191
+ schemeSelect.addEventListener('change', () => sync(true));
192
+ modeSelect.addEventListener('change', () => sync(true));
193
+ sync(false);
194
+ }
195
+
196
+ document.querySelectorAll('[data-ts-theme-selector]').forEach(bind);
197
+ media?.addEventListener?.('change', () => {
198
+ const mode = document.documentElement.dataset.tsModeSource === 'system' ? 'system' : document.documentElement.dataset.tsMode;
199
+ applyTheme(document.documentElement.dataset.tsScheme || 'fern', mode || 'system', false);
200
+ });
201
+ })();
202
+ </script>
File without changes
package/dist/dev.js CHANGED
@@ -33,6 +33,7 @@ const TREESEED_DEFAULT_MAILPIT_UI_PORT = 8025;
33
33
  const DEV_RELOAD_FILE = "public/__treeseed/dev-reload.json";
34
34
  const DEV_RUNTIME_FILE = ".treeseed/generated/dev/runtime.json";
35
35
  const DEFAULT_READINESS_TIMEOUT_MS = 9e4;
36
+ const DEFAULT_SETUP_STEP_TIMEOUT_MS = 3e5;
36
37
  const DEFAULT_PROCESS_READY_GRACE_MS = 1200;
37
38
  const DEFAULT_SHUTDOWN_GRACE_MS = 2500;
38
39
  const DEFAULT_KILL_GRACE_MS = 500;
@@ -825,7 +826,8 @@ function runSetupStep(step, plan, deps) {
825
826
  TREESEED_LOCAL_DEV_MODE: "cloudflare",
826
827
  TREESEED_PUBLIC_DEV_WATCH_RELOAD: plan.feedbackMode === "live" ? "true" : process.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD
827
828
  },
828
- encoding: "utf8"
829
+ encoding: "utf8",
830
+ timeout: DEFAULT_SETUP_STEP_TIMEOUT_MS
829
831
  });
830
832
  if ((result.status ?? 1) === 0) {
831
833
  return {
@@ -834,10 +836,12 @@ function runSetupStep(step, plan, deps) {
834
836
  detail: [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || step.detail
835
837
  };
836
838
  }
839
+ const timedOut = result.error && "code" in result.error && result.error.code === "ETIMEDOUT";
840
+ const timeoutDetail = timedOut ? `${step.label} timed out after ${Math.round(DEFAULT_SETUP_STEP_TIMEOUT_MS / 1e3)} seconds.` : null;
837
841
  return {
838
842
  ...step,
839
843
  status: step.required ? "failed" : "degraded",
840
- detail: [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || `Exited with ${result.status ?? 1}.`
844
+ detail: [timeoutDetail, result.stdout, result.stderr].filter(Boolean).join("\n").trim() || `Exited with ${result.status ?? 1}.`
841
845
  };
842
846
  }
843
847
  function runLocalSetup(plan, options, deps) {
@@ -851,6 +855,14 @@ function runLocalSetup(plan, options, deps) {
851
855
  }
852
856
  for (const step of plan.setupSteps) {
853
857
  let result = step;
858
+ if (step.status === "planned") {
859
+ emitEvent(options, deps.write, {
860
+ type: "setup",
861
+ status: "running",
862
+ message: `${step.label}: running`,
863
+ detail: step.detail
864
+ });
865
+ }
854
866
  if (step.id === "workspace-links") {
855
867
  if (plan.setupMode === "check") {
856
868
  result = { ...step, status: "skipped", detail: "Workspace links were checked in non-mutating mode." };
@@ -52,80 +52,80 @@ const {
52
52
 
53
53
  <MainLayout title={entry.title} description={entry.description} currentPath={currentPath}>
54
54
  <article class="max-w-4xl space-y-8">
55
- <div class="space-y-4 border-b border-[color:var(--site-border)] pb-8">
55
+ <div class="space-y-4 border-b border-[color:var(--ts-color-border)] pb-8">
56
56
  <div class="flex flex-wrap items-center gap-3">
57
57
  <StatusBadge status={entry.status} />
58
- <p class="text-sm font-medium text-[color:var(--site-text-soft)]">{entry.date.toISOString().slice(0, 10)}</p>
59
- {contributor && <p class="text-sm text-[color:var(--site-text-soft)]">{contributor.data.name}</p>}
60
- {metaLabel && metaValue && <p class="text-sm text-[color:var(--site-text-soft)]">{metaLabel}: {metaValue}</p>}
58
+ <p class="text-sm font-medium text-[color:var(--ts-color-text-subtle)]">{entry.date.toISOString().slice(0, 10)}</p>
59
+ {contributor && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{contributor.data.name}</p>}
60
+ {metaLabel && metaValue && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{metaLabel}: {metaValue}</p>}
61
61
  </div>
62
- <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--site-text)] md:text-6xl">{entry.title}</h1>
63
- <p class="max-w-3xl text-xl leading-10 text-[color:var(--site-text-muted)]">{entry.summary}</p>
64
- {(introText ?? entry.motivation) && <p class="max-w-3xl text-base leading-8 text-[color:var(--site-text-muted)]">{introText ?? entry.motivation}</p>}
62
+ <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--ts-color-text)] md:text-6xl">{entry.title}</h1>
63
+ <p class="max-w-3xl text-xl leading-10 text-[color:var(--ts-color-text-muted)]">{entry.summary}</p>
64
+ {(introText ?? entry.motivation) && <p class="max-w-3xl text-base leading-8 text-[color:var(--ts-color-text-muted)]">{introText ?? entry.motivation}</p>}
65
65
  {entry.tags.length > 0 && (
66
- <p class="text-sm uppercase tracking-[0.14em] text-[color:var(--site-accent-strong)]">{entry.tags.join(' / ')}</p>
66
+ <p class="text-sm uppercase tracking-[0.14em] text-[color:var(--ts-color-accent-strong)]">{entry.tags.join(' / ')}</p>
67
67
  )}
68
68
  </div>
69
69
  <div class="prose-karyon">
70
70
  <slot />
71
71
  </div>
72
- <div class="grid gap-6 border-t border-[color:var(--site-border)] pt-8 md:grid-cols-3">
72
+ <div class="grid gap-6 border-t border-[color:var(--ts-color-border)] pt-8 md:grid-cols-3">
73
73
  {relatedQuestions.length > 0 && (
74
74
  <div>
75
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Related questions</p>
76
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
75
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related questions</p>
76
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
77
77
  {relatedQuestions.map((question) => (
78
- <li><a href={`/questions/${question.id}/`} class="hover:text-[color:var(--site-text)]">{entryTitle(question)}</a></li>
78
+ <li><a href={`/questions/${question.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(question)}</a></li>
79
79
  ))}
80
80
  </ul>
81
81
  </div>
82
82
  )}
83
83
  {relatedObjectives.length > 0 && (
84
84
  <div>
85
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Related objectives</p>
86
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
85
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related objectives</p>
86
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
87
87
  {relatedObjectives.map((objective) => (
88
- <li><a href={`/objectives/${objective.id}/`} class="hover:text-[color:var(--site-text)]">{entryTitle(objective)}</a></li>
88
+ <li><a href={`/objectives/${objective.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(objective)}</a></li>
89
89
  ))}
90
90
  </ul>
91
91
  </div>
92
92
  )}
93
93
  {relatedNotes.length > 0 && (
94
94
  <div>
95
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Related notes</p>
96
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
95
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related notes</p>
96
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
97
97
  {relatedNotes.map((note) => (
98
- <li><a href={`/notes/${note.id}/`} class="hover:text-[color:var(--site-text)]">{entryTitle(note)}</a></li>
98
+ <li><a href={`/notes/${note.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(note)}</a></li>
99
99
  ))}
100
100
  </ul>
101
101
  </div>
102
102
  )}
103
103
  {relatedProposals.length > 0 && (
104
104
  <div>
105
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Related proposals</p>
106
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
105
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related proposals</p>
106
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
107
107
  {relatedProposals.map((proposal) => (
108
- <li><a href={`/proposals/${proposal.id}/`} class="hover:text-[color:var(--site-text)]">{entryTitle(proposal)}</a></li>
108
+ <li><a href={`/proposals/${proposal.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(proposal)}</a></li>
109
109
  ))}
110
110
  </ul>
111
111
  </div>
112
112
  )}
113
113
  {relatedDecisions.length > 0 && (
114
114
  <div>
115
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Related decisions</p>
116
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
115
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related decisions</p>
116
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
117
117
  {relatedDecisions.map((decision) => (
118
- <li><a href={`/decisions/${decision.id}/`} class="hover:text-[color:var(--site-text)]">{entryTitle(decision)}</a></li>
118
+ <li><a href={`/decisions/${decision.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(decision)}</a></li>
119
119
  ))}
120
120
  </ul>
121
121
  </div>
122
122
  )}
123
123
  {relatedBooks.length > 0 && (
124
124
  <div>
125
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Related books</p>
126
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
125
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related books</p>
126
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
127
127
  {relatedBooks.map((book) => (
128
- <li><a href={`/books/${book.id}/`} class="hover:text-[color:var(--site-text)]">{entryTitle(book)}</a></li>
128
+ <li><a href={`/books/${book.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(book)}</a></li>
129
129
  ))}
130
130
  </ul>
131
131
  </div>
@@ -6,25 +6,25 @@ const { entry, currentPath } = Astro.props;
6
6
 
7
7
  <MainLayout title={entry.title} description={entry.description} currentPath={currentPath}>
8
8
  <article class="max-w-5xl space-y-8">
9
- <div class="space-y-4 border-b border-[color:var(--site-border)] pb-8">
10
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">{entry.sectionLabel}</p>
11
- <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--site-text)] md:text-6xl">{entry.title}</h1>
12
- <p class="max-w-4xl text-xl leading-10 text-[color:var(--site-text-muted)]">{entry.summary}</p>
9
+ <div class="space-y-4 border-b border-[color:var(--ts-color-border)] pb-8">
10
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">{entry.sectionLabel}</p>
11
+ <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--ts-color-text)] md:text-6xl">{entry.title}</h1>
12
+ <p class="max-w-4xl text-xl leading-10 text-[color:var(--ts-color-text-muted)]">{entry.summary}</p>
13
13
  {entry.tags.length > 0 && (
14
- <p class="text-sm uppercase tracking-[0.14em] text-[color:var(--site-accent-strong)]">{entry.tags.join(' / ')}</p>
14
+ <p class="text-sm uppercase tracking-[0.14em] text-[color:var(--ts-color-accent-strong)]">{entry.tags.join(' / ')}</p>
15
15
  )}
16
16
  <div class="flex flex-wrap gap-3 pt-2">
17
- <a href={entry.landingPath} class="border border-[color:var(--site-border-strong)] px-4 py-2 text-sm font-semibold text-[color:var(--site-text)] transition hover:border-[color:var(--site-blue)] hover:bg-[color:var(--site-blue-soft)]">Open knowledge</a>
18
- <a href={entry.downloadHref} class="border border-[color:var(--site-accent)] bg-[color:var(--site-accent)] px-4 py-2 text-sm font-semibold text-[color:var(--site-text)] transition hover:border-[color:var(--site-blue)] hover:bg-[color:var(--site-blue-soft)]">Download book</a>
17
+ <a href={entry.landingPath} class="border border-[color:var(--ts-color-border-strong)] px-4 py-2 text-sm font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">Open knowledge</a>
18
+ <a href={entry.downloadHref} class="border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-4 py-2 text-sm font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">Download book</a>
19
19
  </div>
20
20
  </div>
21
21
  <div class="grid gap-6 md:grid-cols-[1.25fr_0.75fr]">
22
22
  <div class="prose-karyon">
23
23
  <slot />
24
24
  </div>
25
- <div class="border border-[color:var(--site-border)] bg-[color:var(--site-surface)] p-5">
26
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-blue-strong)]">Book paths</p>
27
- <ul class="mt-3 space-y-2 text-[color:var(--site-text-muted)]">
25
+ <div class="border border-[color:var(--ts-color-border)] bg-[color:var(--ts-color-surface)] p-5">
26
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Book paths</p>
27
+ <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
28
28
  <li>Landing path: <code>{entry.landingPath}</code></li>
29
29
  <li>Base path: <code>{entry.basePath}</code></li>
30
30
  <li>Download file: <code>{entry.downloadFileName}</code></li>
@@ -9,13 +9,13 @@ const { entry, currentPath } = Astro.props;
9
9
  <MainLayout title={entry.title} description={entry.description} currentPath={currentPath}>
10
10
  <div class="space-y-8">
11
11
  <StageBanner />
12
- <section class="max-w-5xl space-y-5 border-b border-[color:var(--site-border)] pb-8">
12
+ <section class="max-w-5xl space-y-5 border-b border-[color:var(--ts-color-border)] pb-8">
13
13
  <div class="flex flex-wrap items-center gap-3">
14
14
  <StatusBadge status={entry.status} />
15
- <p class="text-sm font-medium text-[color:var(--site-text-soft)]">Updated {entry.updated.toISOString().slice(0, 10)}</p>
15
+ <p class="text-sm font-medium text-[color:var(--ts-color-text-subtle)]">Updated {entry.updated.toISOString().slice(0, 10)}</p>
16
16
  </div>
17
- <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--site-text)] md:text-7xl">{entry.title}</h1>
18
- <p class="max-w-4xl text-xl leading-10 text-[color:var(--site-text-muted)]">{entry.summary}</p>
17
+ <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--ts-color-text)] md:text-7xl">{entry.title}</h1>
18
+ <p class="max-w-4xl text-xl leading-10 text-[color:var(--ts-color-text-muted)]">{entry.summary}</p>
19
19
  </section>
20
20
  <article class="prose-karyon max-w-4xl">
21
21
  <slot />