@commonpub/layer 0.62.0 → 0.64.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/nuxt.config.ts CHANGED
@@ -37,7 +37,7 @@ export default defineNuxtConfig({
37
37
  },
38
38
  {
39
39
  rel: 'stylesheet',
40
- href: 'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Work+Sans:ital,wght@0,300..800;1,300..800&display=swap',
40
+ href: 'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Newsreader:ital,opsz,wght@0,6..72,300..700;1,6..72,300..700&family=Work+Sans:ital,wght@0,300..800;1,300..800&display=swap',
41
41
  },
42
42
  ],
43
43
  },
@@ -48,6 +48,8 @@ export default defineNuxtConfig({
48
48
  uiTheme('generics.css'),
49
49
  uiTheme('agora.css'),
50
50
  uiTheme('agora-dark.css'),
51
+ uiTheme('stoa.css'),
52
+ uiTheme('stoa-dark.css'),
51
53
  uiTheme('components.css'),
52
54
  uiTheme('prose.css'),
53
55
  uiTheme('layouts.css'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -54,14 +54,14 @@
54
54
  "vue-router": "^4.3.0",
55
55
  "zod": "^4.3.6",
56
56
  "@commonpub/auth": "0.8.0",
57
+ "@commonpub/editor": "0.7.11",
57
58
  "@commonpub/config": "0.19.0",
58
- "@commonpub/explainer": "0.7.15",
59
59
  "@commonpub/docs": "0.6.3",
60
- "@commonpub/protocol": "0.13.0",
61
- "@commonpub/server": "2.82.0",
62
- "@commonpub/editor": "0.7.11",
63
60
  "@commonpub/learning": "0.5.2",
64
- "@commonpub/ui": "0.9.2",
61
+ "@commonpub/server": "2.82.0",
62
+ "@commonpub/protocol": "0.13.0",
63
+ "@commonpub/explainer": "0.7.15",
64
+ "@commonpub/ui": "0.11.0",
65
65
  "@commonpub/schema": "0.35.0"
66
66
  },
67
67
  "devDependencies": {
@@ -155,14 +155,45 @@ async function removeTheme(themeId: string): Promise<void> {
155
155
  // --- Create / Capture / Import ---
156
156
 
157
157
  function createBlank(): void {
158
+ // "New custom theme" forks the CURRENTLY ACTIVE theme so you start from the
159
+ // look on screen, not a blank Classic slate. (It previously seeded an empty,
160
+ // base-parented theme — which is why saving reverted everything to Classic.)
161
+ const active = instanceDefault.value;
162
+ const customId = parseCustomThemeId(active);
163
+
164
+ // Active theme is itself a custom theme → copy its stored tokens directly
165
+ // (computed-style capture can't reconstruct a custom theme's full set).
166
+ if (customId && themesApi.data.value) {
167
+ const src = themesApi.data.value.custom.find((t) => t.id === customId);
168
+ if (src) {
169
+ const seed = {
170
+ id: nextAvailableId(`${src.id}-copy`),
171
+ name: `${src.name} (copy)`,
172
+ description: src.description ?? '',
173
+ family: 'custom',
174
+ isDark: src.isDark,
175
+ parentTheme: src.parentTheme,
176
+ tokens: { ...src.tokens },
177
+ };
178
+ sessionStorage.setItem('cpub-theme-editor-seed', JSON.stringify(seed));
179
+ router.push('/admin/theme/edit/__new');
180
+ return;
181
+ }
182
+ }
183
+
184
+ // Built-in / registered active theme → capture its applied tokens so the new
185
+ // theme reproduces the current look (a custom theme renders as base + tokens,
186
+ // so a complete capture is what keeps it from falling back to Classic).
187
+ const detected = detectAppliedOverrides();
188
+ const isBuiltInParent = themesApi.data.value?.builtIn.some((t) => t.id === active) ?? false;
158
189
  const seed = {
159
190
  id: nextAvailableId('my-theme'),
160
191
  name: 'My theme',
161
- description: '',
192
+ description: detected.count ? `Forked from the active theme (${detected.count} tokens).` : '',
162
193
  family: 'custom',
163
- isDark: false,
164
- parentTheme: 'base',
165
- tokens: {},
194
+ isDark: detected.isDark,
195
+ parentTheme: isBuiltInParent ? active : 'base',
196
+ tokens: detected.tokens,
166
197
  };
167
198
  sessionStorage.setItem('cpub-theme-editor-seed', JSON.stringify(seed));
168
199
  router.push('/admin/theme/edit/__new');
@@ -29,8 +29,12 @@ let cacheTime = 0;
29
29
  async function loadThemeState(): Promise<CachedThemeState> {
30
30
  const db = useDB();
31
31
 
32
- // 1. Default theme ID
33
- let defaultTheme = 'base';
32
+ // 1. Default theme ID.
33
+ // Fallback is 'stoa' — the default CommonPub theme for fresh installs and any
34
+ // instance that has NOT explicitly set `theme.default` in the DB. Instances
35
+ // with an explicit setting (e.g. commonpub.io=agora-dark, branded instances)
36
+ // are unaffected; this only changes what an unconfigured instance shows.
37
+ let defaultTheme = 'stoa';
34
38
  try {
35
39
  const [row] = await db
36
40
  .select({ value: instanceSettings.value })
@@ -0,0 +1,153 @@
1
+ @layer commonpub {
2
+ /* ===========================================
3
+ CommonPub Stoa Theme — Dark
4
+ "The same walk, by lamplight."
5
+
6
+ Deep warm-black paper, brighter moss accent,
7
+ the same Fraunces / Newsreader / Work Sans
8
+ type and soft rounded geometry as Stoa Light.
9
+ Component overrides are shared from stoa.css
10
+ (both data-theme values are targeted there).
11
+ =========================================== */
12
+
13
+ [data-theme="stoa-dark"] {
14
+ /* === SURFACES (warm near-black) === */
15
+ --bg: #15130d;
16
+ --surface: #1f1c14;
17
+ --surface2: #28241a;
18
+ --surface3: #332d20;
19
+
20
+ --color-surface: var(--surface);
21
+ --color-surface-alt: var(--surface2);
22
+ --color-surface-raised: var(--surface);
23
+ --color-surface-overlay: rgba(0, 0, 0, 0.6);
24
+ --color-surface-overlay-light: rgba(0, 0, 0, 0.45);
25
+ --color-surface-scrim: rgba(21, 19, 13, 0.8);
26
+ --color-surface-hover: var(--surface2);
27
+ --color-bg-subtle: var(--bg);
28
+
29
+ /* === TEXT (warm paper ink) === */
30
+ --text: #f1ebda;
31
+ --text-dim: #bcb4a2;
32
+ --text-faint: #8d8675;
33
+
34
+ --color-text: var(--text);
35
+ --color-text-secondary: var(--text-dim);
36
+ --color-text-muted: var(--text-faint);
37
+ --color-text-inverse: #15130d;
38
+
39
+ /* === BORDERS (warm hairlines) === */
40
+ --border: rgba(241, 235, 218, 0.24);
41
+ --border2: rgba(241, 235, 218, 0.13);
42
+
43
+ --color-border: var(--border2);
44
+ --color-border-strong: var(--border);
45
+ --color-border-focus: var(--accent);
46
+
47
+ /* === ACCENT (brighter moss for dark) === */
48
+ --accent: #5aa784;
49
+ --accent-bg: rgba(90, 167, 132, 0.14);
50
+ --accent-bg-strong: rgba(90, 167, 132, 0.24);
51
+ --accent-bg-heavy: rgba(90, 167, 132, 0.42);
52
+ --accent-bg-solid: rgba(90, 167, 132, 0.6);
53
+ --accent-border: rgba(90, 167, 132, 0.34);
54
+ --accent-focus-ring: 0 0 0 3px rgba(90, 167, 132, 0.2);
55
+
56
+ --color-primary: var(--accent);
57
+ --color-primary-hover: #79c4a0;
58
+ --color-primary-text: #0f140f;
59
+ --color-on-primary: #0f140f;
60
+ --color-accent: var(--accent);
61
+ --color-accent-hover: #79c4a0;
62
+ --color-accent-text: #0f140f;
63
+ --color-on-accent: #0f140f;
64
+ --color-accent-bg: var(--accent-bg);
65
+ --color-accent-border: var(--accent-border);
66
+
67
+ /* === SEMANTIC COLORS (dark warm) === */
68
+ --green: #5aa784;
69
+ --green-bg: rgba(90, 167, 132, 0.14);
70
+ --green-border: rgba(90, 167, 132, 0.32);
71
+
72
+ --yellow: #d6a259;
73
+ --yellow-bg: rgba(214, 162, 89, 0.13);
74
+ --yellow-border: rgba(214, 162, 89, 0.3);
75
+
76
+ --red: #d07c69;
77
+ --red-bg: rgba(208, 124, 105, 0.13);
78
+ --red-border: rgba(208, 124, 105, 0.3);
79
+
80
+ --purple: #a98cbe;
81
+ --purple-bg: rgba(169, 140, 190, 0.13);
82
+ --purple-border: rgba(169, 140, 190, 0.3);
83
+
84
+ --teal: #5fb3a6;
85
+ --teal-bg: rgba(95, 179, 166, 0.13);
86
+ --teal-border: rgba(95, 179, 166, 0.3);
87
+
88
+ --pink: #cd8aa0;
89
+ --pink-bg: rgba(205, 138, 160, 0.13);
90
+ --pink-border: rgba(205, 138, 160, 0.3);
91
+
92
+ --color-success: var(--green);
93
+ --color-warning: var(--yellow);
94
+ --color-error: var(--red);
95
+ --color-info: #82a2c0;
96
+ --color-success-bg: var(--green-bg);
97
+ --color-warning-bg: var(--yellow-bg);
98
+ --color-error-bg: var(--red-bg);
99
+ --color-info-bg: rgba(130, 162, 192, 0.13);
100
+
101
+ /* === OVERLAYS === */
102
+ --color-badge-overlay: rgba(241, 235, 218, 0.85);
103
+
104
+ /* === INTERACTIVE === */
105
+ --color-link: #79c4a0;
106
+ --color-link-hover: #9bd6bb;
107
+
108
+ /* === TYPOGRAPHY (same as light) === */
109
+ --font-sans: 'Work Sans', system-ui, -apple-system, sans-serif;
110
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
111
+ --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
112
+ --font-read: 'Newsreader', Georgia, 'Times New Roman', serif;
113
+
114
+ --font-heading: var(--font-display);
115
+ --font-body: var(--font-sans);
116
+
117
+ --text-label: 0.6875rem;
118
+
119
+ --leading-tight: 1.16;
120
+ --leading-snug: 1.36;
121
+ --leading-normal: 1.62;
122
+ --leading-relaxed: 1.78;
123
+
124
+ --tracking-tight: -0.015em;
125
+ --tracking-normal: 0;
126
+ --tracking-wide: 0.04em;
127
+ --tracking-wider: 0.08em;
128
+ --tracking-widest: 0.14em;
129
+
130
+ /* === SHAPE (same rounded geometry) === */
131
+ --radius: 12px;
132
+ --radius-sm: 8px;
133
+ --radius-md: 14px;
134
+ --radius-lg: 20px;
135
+ --radius-xl: 28px;
136
+ --radius-2xl: 28px;
137
+
138
+ /* === SHADOWS (deeper for dark) === */
139
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
140
+ --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.45), 0 10px 24px rgba(0, 0, 0, 0.32);
141
+ --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.52), 0 4px 12px rgba(0, 0, 0, 0.36);
142
+ --shadow-xl: 0 26px 60px rgba(0, 0, 0, 0.6), 0 8px 18px rgba(0, 0, 0, 0.4);
143
+ --shadow-accent: 0 6px 20px rgba(90, 167, 132, 0.4);
144
+
145
+ /* === TRANSITIONS === */
146
+ --transition-fast: 140ms cubic-bezier(0.22, 1, 0.36, 1);
147
+ --transition-default: 200ms cubic-bezier(0.22, 1, 0.36, 1);
148
+ --transition-slow: 320ms cubic-bezier(0.4, 0, 0.2, 1);
149
+
150
+ /* === FOCUS === */
151
+ --focus-ring: 0 0 0 3px rgba(90, 167, 132, 0.28);
152
+ }
153
+ } /* end @layer commonpub — token overrides only */
package/theme/stoa.css ADDED
@@ -0,0 +1,361 @@
1
+ @layer commonpub {
2
+ /* ===========================================
3
+ CommonPub Stoa Theme — Light
4
+ "A covered walk for thinking in public."
5
+
6
+ Warm paper grounds, moss-green accent,
7
+ Fraunces display + Newsreader reading serif,
8
+ Work Sans UI. Soft rounded corners, gentle
9
+ blurred lifts (not the brutalist offset
10
+ shadow), hairline warm borders. A calm,
11
+ bookish, daylight aesthetic.
12
+ =========================================== */
13
+
14
+ [data-theme="stoa"] {
15
+ /* === SURFACES (warm paper) === */
16
+ --bg: #f7f3ea;
17
+ --surface: #fffdf6;
18
+ --surface2: #eee8d9;
19
+ --surface3: #e4ddcb;
20
+
21
+ --color-surface: var(--surface);
22
+ --color-surface-alt: var(--surface2);
23
+ --color-surface-raised: var(--surface);
24
+ --color-surface-overlay: rgba(42, 38, 32, 0.5);
25
+ --color-surface-overlay-light: rgba(42, 38, 32, 0.4);
26
+ --color-surface-scrim: rgba(247, 243, 234, 0.78);
27
+ --color-surface-hover: var(--surface2);
28
+ --color-bg-subtle: var(--bg);
29
+
30
+ /* === TEXT (ink gradations) === */
31
+ --text: #2a2620;
32
+ --text-dim: #5a5247;
33
+ --text-faint: #8a8273;
34
+
35
+ --color-text: var(--text);
36
+ --color-text-secondary: var(--text-dim);
37
+ --color-text-muted: var(--text-faint);
38
+ --color-text-inverse: #fdfbf3;
39
+
40
+ /* === BORDERS (warm hairlines) === */
41
+ --border: rgba(42, 38, 32, 0.22);
42
+ --border2: rgba(42, 38, 32, 0.12);
43
+
44
+ --color-border: var(--border2);
45
+ --color-border-strong: var(--border);
46
+ --color-border-focus: var(--accent);
47
+
48
+ /* === ACCENT (moss green) === */
49
+ --accent: #3c8262;
50
+ --accent-bg: rgba(60, 130, 98, 0.12);
51
+ --accent-bg-strong: rgba(60, 130, 98, 0.2);
52
+ --accent-bg-heavy: rgba(60, 130, 98, 0.4);
53
+ --accent-bg-solid: rgba(60, 130, 98, 0.6);
54
+ --accent-border: rgba(60, 130, 98, 0.32);
55
+ --accent-focus-ring: 0 0 0 3px rgba(60, 130, 98, 0.18);
56
+
57
+ --color-primary: var(--accent);
58
+ --color-primary-hover: #2b6147;
59
+ --color-primary-text: #fdfbf3;
60
+ --color-on-primary: #fdfbf3;
61
+ --color-accent: var(--accent);
62
+ --color-accent-hover: #2b6147;
63
+ --color-accent-text: #fdfbf3;
64
+ --color-on-accent: #fdfbf3;
65
+ --color-accent-bg: var(--accent-bg);
66
+ --color-accent-border: var(--accent-border);
67
+
68
+ /* === SEMANTIC COLORS (warm) === */
69
+ --green: #3c8262;
70
+ --green-bg: rgba(60, 130, 98, 0.12);
71
+ --green-border: rgba(60, 130, 98, 0.3);
72
+
73
+ --yellow: #bd863a;
74
+ --yellow-bg: rgba(189, 134, 58, 0.1);
75
+ --yellow-border: rgba(189, 134, 58, 0.28);
76
+
77
+ --red: #b25a47;
78
+ --red-bg: rgba(178, 90, 71, 0.1);
79
+ --red-border: rgba(178, 90, 71, 0.28);
80
+
81
+ --purple: #7b6190;
82
+ --purple-bg: rgba(123, 97, 144, 0.1);
83
+ --purple-border: rgba(123, 97, 144, 0.28);
84
+
85
+ --teal: #3f8a80;
86
+ --teal-bg: rgba(63, 138, 128, 0.1);
87
+ --teal-border: rgba(63, 138, 128, 0.28);
88
+
89
+ --pink: #b0697f;
90
+ --pink-bg: rgba(176, 105, 127, 0.1);
91
+ --pink-border: rgba(176, 105, 127, 0.28);
92
+
93
+ /* Rank colors (warm-toned for contests) */
94
+ --gold: #c1923a;
95
+ --silver: #9a948a;
96
+ --bronze: #a8704a;
97
+
98
+ --color-success: var(--green);
99
+ --color-warning: var(--yellow);
100
+ --color-error: var(--red);
101
+ --color-info: #5d7e9c;
102
+ --color-success-bg: var(--green-bg);
103
+ --color-warning-bg: var(--yellow-bg);
104
+ --color-error-bg: var(--red-bg);
105
+ --color-info-bg: rgba(93, 126, 156, 0.1);
106
+
107
+ /* === OVERLAYS === */
108
+ --color-badge-overlay: rgba(42, 38, 32, 0.72);
109
+
110
+ /* === INTERACTIVE === */
111
+ --color-link: #2b6147;
112
+ --color-link-hover: #1f4a35;
113
+
114
+ /* === TYPOGRAPHY === */
115
+ --font-sans: 'Work Sans', system-ui, -apple-system, sans-serif;
116
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
117
+ --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
118
+ --font-read: 'Newsreader', Georgia, 'Times New Roman', serif;
119
+
120
+ --font-heading: var(--font-display);
121
+ --font-body: var(--font-sans);
122
+
123
+ --text-label: 0.6875rem; /* 11px monospace UI labels */
124
+
125
+ --leading-tight: 1.16;
126
+ --leading-snug: 1.36;
127
+ --leading-normal: 1.62;
128
+ --leading-relaxed: 1.78;
129
+
130
+ --tracking-tight: -0.015em;
131
+ --tracking-normal: 0;
132
+ --tracking-wide: 0.04em;
133
+ --tracking-wider: 0.08em;
134
+ --tracking-widest: 0.14em;
135
+
136
+ /* === SHAPE (soft, rounded — Stoa's signature departure) === */
137
+ --radius: 12px;
138
+ --radius-sm: 8px;
139
+ --radius-md: 14px;
140
+ --radius-lg: 20px;
141
+ --radius-xl: 28px;
142
+ --radius-2xl: 28px;
143
+
144
+ /* === SHADOWS (soft blurred lifts, not offset) === */
145
+ --shadow-sm: 0 1px 2px rgba(38, 32, 24, 0.06), 0 1px 1px rgba(38, 32, 24, 0.04);
146
+ --shadow-md: 0 2px 6px rgba(38, 32, 24, 0.06), 0 8px 20px rgba(38, 32, 24, 0.06);
147
+ --shadow-lg: 0 12px 34px rgba(38, 32, 24, 0.11), 0 3px 10px rgba(38, 32, 24, 0.06);
148
+ --shadow-xl: 0 22px 50px rgba(38, 32, 24, 0.14), 0 6px 16px rgba(38, 32, 24, 0.08);
149
+ --shadow-accent: 0 6px 18px rgba(60, 130, 98, 0.28);
150
+
151
+ /* === TRANSITIONS (gentle, springy) === */
152
+ --transition-fast: 140ms cubic-bezier(0.22, 1, 0.36, 1);
153
+ --transition-default: 200ms cubic-bezier(0.22, 1, 0.36, 1);
154
+ --transition-slow: 320ms cubic-bezier(0.4, 0, 0.2, 1);
155
+
156
+ /* === FOCUS (moss glow) === */
157
+ --focus-ring: 0 0 0 3px rgba(60, 130, 98, 0.22);
158
+ }
159
+ } /* end @layer commonpub — token overrides only */
160
+
161
+
162
+ /* ═══════════════════════════════════════════
163
+ COMPONENT OVERRIDES (outside @layer so they
164
+ beat Vue scoped styles). Shared by the light
165
+ AND dark variants — both data-theme values
166
+ are targeted so stoa-dark inherits them.
167
+ ═══════════════════════════════════════════ */
168
+
169
+ /* Display serif for headings */
170
+ [data-theme="stoa"] h1,
171
+ [data-theme="stoa"] h2,
172
+ [data-theme="stoa"] h3,
173
+ [data-theme="stoa-dark"] h1,
174
+ [data-theme="stoa-dark"] h2,
175
+ [data-theme="stoa-dark"] h3,
176
+ [data-theme="stoa"] .cpub-section-title-lg,
177
+ [data-theme="stoa"] .cpub-section-title-sm,
178
+ [data-theme="stoa-dark"] .cpub-section-title-lg,
179
+ [data-theme="stoa-dark"] .cpub-section-title-sm,
180
+ [data-theme="stoa"] .admin-page-title,
181
+ [data-theme="stoa-dark"] .admin-page-title {
182
+ font-family: var(--font-display);
183
+ }
184
+
185
+ /* Wordmark + brand use the display serif */
186
+ [data-theme="stoa"] .cpub-logo-name,
187
+ [data-theme="stoa-dark"] .cpub-logo-name,
188
+ [data-theme="stoa"] .admin-brand,
189
+ [data-theme="stoa-dark"] .admin-brand {
190
+ font-family: var(--font-display);
191
+ font-weight: 600;
192
+ letter-spacing: -0.01em;
193
+ }
194
+
195
+ [data-theme="stoa"] .cpub-logo-bracket,
196
+ [data-theme="stoa-dark"] .cpub-logo-bracket {
197
+ color: var(--accent);
198
+ }
199
+
200
+ /* Focus: moss glow instead of a hard outline */
201
+ [data-theme="stoa"] :focus-visible,
202
+ [data-theme="stoa-dark"] :focus-visible {
203
+ outline: none;
204
+ box-shadow: var(--focus-ring);
205
+ }
206
+
207
+ /* Buttons: gentle lift on hover */
208
+ [data-theme="stoa"] .cpub-btn,
209
+ [data-theme="stoa-dark"] .cpub-btn {
210
+ transition: transform var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
211
+ }
212
+
213
+ [data-theme="stoa"] .cpub-btn:hover,
214
+ [data-theme="stoa-dark"] .cpub-btn:hover {
215
+ transform: translateY(-1px);
216
+ box-shadow: var(--shadow-sm);
217
+ }
218
+
219
+ [data-theme="stoa"] .cpub-btn:active,
220
+ [data-theme="stoa-dark"] .cpub-btn:active {
221
+ transform: translateY(0);
222
+ box-shadow: none;
223
+ }
224
+
225
+ [data-theme="stoa"] .cpub-btn-primary,
226
+ [data-theme="stoa-dark"] .cpub-btn-primary {
227
+ background: var(--accent);
228
+ border-color: var(--accent);
229
+ color: var(--color-on-accent);
230
+ }
231
+
232
+ [data-theme="stoa"] .cpub-btn-primary:hover,
233
+ [data-theme="stoa-dark"] .cpub-btn-primary:hover {
234
+ background: var(--color-primary-hover);
235
+ box-shadow: var(--shadow-accent);
236
+ transform: translateY(-1px);
237
+ }
238
+
239
+ [data-theme="stoa"] .cpub-btn-ghost,
240
+ [data-theme="stoa-dark"] .cpub-btn-ghost {
241
+ color: var(--accent);
242
+ border-color: transparent;
243
+ }
244
+
245
+ [data-theme="stoa"] .cpub-btn-ghost:hover,
246
+ [data-theme="stoa-dark"] .cpub-btn-ghost:hover {
247
+ background: var(--accent-bg);
248
+ }
249
+
250
+ /* Cards: soft lift on hover */
251
+ [data-theme="stoa"] .cpub-content-card,
252
+ [data-theme="stoa-dark"] .cpub-content-card,
253
+ [data-theme="stoa"] .cpub-sb-card,
254
+ [data-theme="stoa-dark"] .cpub-sb-card,
255
+ [data-theme="stoa"] .cpub-stat-card,
256
+ [data-theme="stoa-dark"] .cpub-stat-card {
257
+ transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast);
258
+ }
259
+
260
+ [data-theme="stoa"] .cpub-content-card:hover,
261
+ [data-theme="stoa-dark"] .cpub-content-card:hover {
262
+ transform: translateY(-2px);
263
+ box-shadow: var(--shadow-md);
264
+ }
265
+
266
+ /* Card titles use the display serif */
267
+ [data-theme="stoa"] .cpub-content-card h3,
268
+ [data-theme="stoa-dark"] .cpub-content-card h3,
269
+ [data-theme="stoa"] .cpub-card-title,
270
+ [data-theme="stoa-dark"] .cpub-card-title {
271
+ font-family: var(--font-display);
272
+ font-weight: 600;
273
+ }
274
+
275
+ /* Inputs: glow focus */
276
+ [data-theme="stoa"] .cpub-input:focus,
277
+ [data-theme="stoa-dark"] .cpub-input:focus,
278
+ [data-theme="stoa"] .cpub-textarea:focus,
279
+ [data-theme="stoa-dark"] .cpub-textarea:focus,
280
+ [data-theme="stoa"] .cpub-select:focus,
281
+ [data-theme="stoa-dark"] .cpub-select:focus {
282
+ border-color: var(--accent);
283
+ box-shadow: var(--focus-ring);
284
+ outline: none;
285
+ }
286
+
287
+ /* Links: moss with a soft underline */
288
+ [data-theme="stoa"] a:not(.cpub-btn):not(.cpub-topbar-link):not(.cpub-footer-link):not(.admin-nav-link),
289
+ [data-theme="stoa-dark"] a:not(.cpub-btn):not(.cpub-topbar-link):not(.cpub-footer-link):not(.admin-nav-link) {
290
+ text-decoration-color: var(--accent-border);
291
+ text-underline-offset: 2px;
292
+ }
293
+
294
+ [data-theme="stoa"] .cpub-topbar-link.router-link-active,
295
+ [data-theme="stoa-dark"] .cpub-topbar-link.router-link-active {
296
+ color: var(--accent);
297
+ }
298
+
299
+ /* Prose: Newsreader reading serif, moss blockquote */
300
+ [data-theme="stoa"] .cpub-prose,
301
+ [data-theme="stoa-dark"] .cpub-prose {
302
+ font-family: var(--font-read);
303
+ font-size: 1.0625rem;
304
+ line-height: var(--leading-relaxed);
305
+ }
306
+
307
+ [data-theme="stoa"] .cpub-prose h1,
308
+ [data-theme="stoa"] .cpub-prose h2,
309
+ [data-theme="stoa"] .cpub-prose h3,
310
+ [data-theme="stoa"] .cpub-prose h4,
311
+ [data-theme="stoa-dark"] .cpub-prose h1,
312
+ [data-theme="stoa-dark"] .cpub-prose h2,
313
+ [data-theme="stoa-dark"] .cpub-prose h3,
314
+ [data-theme="stoa-dark"] .cpub-prose h4 {
315
+ font-family: var(--font-display);
316
+ }
317
+
318
+ [data-theme="stoa"] .cpub-prose blockquote,
319
+ [data-theme="stoa-dark"] .cpub-prose blockquote {
320
+ border-left-color: var(--accent);
321
+ background: var(--accent-bg);
322
+ }
323
+
324
+ [data-theme="stoa"] .cpub-prose a,
325
+ [data-theme="stoa-dark"] .cpub-prose a {
326
+ color: var(--accent);
327
+ text-decoration-color: var(--accent-border);
328
+ }
329
+
330
+ /* Admin: active nav uses moss */
331
+ [data-theme="stoa"] .admin-nav-link.router-link-exact-active,
332
+ [data-theme="stoa-dark"] .admin-nav-link.router-link-exact-active {
333
+ color: var(--accent);
334
+ background: var(--accent-bg);
335
+ }
336
+
337
+ [data-theme="stoa"] .cpub-footer-col-title,
338
+ [data-theme="stoa-dark"] .cpub-footer-col-title {
339
+ letter-spacing: var(--tracking-widest);
340
+ }
341
+
342
+
343
+ /* ═══════════════════════════════════════════
344
+ LOGO SWITCH
345
+ Stoa shares Agora's Town Square mark (hide
346
+ the Classic logo, show the Agora wordmark +
347
+ hero aside).
348
+ ═══════════════════════════════════════════ */
349
+
350
+ [data-theme="stoa"] .cpub-logo-classic,
351
+ [data-theme="stoa-dark"] .cpub-logo-classic { display: none !important; }
352
+
353
+ [data-theme="stoa"] .cpub-logo-agora,
354
+ [data-theme="stoa-dark"] .cpub-logo-agora { display: flex !important; }
355
+
356
+ [data-theme="stoa"] .cpub-hero-logo-aside,
357
+ [data-theme="stoa-dark"] .cpub-hero-logo-aside {
358
+ display: flex !important;
359
+ align-items: center;
360
+ justify-content: center;
361
+ }
@@ -17,6 +17,8 @@ export const THEME_TO_FAMILY: Record<string, string> = {
17
17
  generics: 'generics',
18
18
  agora: 'agora',
19
19
  'agora-dark': 'agora',
20
+ stoa: 'stoa',
21
+ 'stoa-dark': 'stoa',
20
22
  };
21
23
 
22
24
  /** Light/dark variants for each family */
@@ -24,6 +26,7 @@ export const FAMILY_VARIANTS: Record<string, { light: string; dark: string }> =
24
26
  classic: { light: 'base', dark: 'dark' },
25
27
  agora: { light: 'agora', dark: 'agora-dark' },
26
28
  generics: { light: 'generics', dark: 'generics' },
29
+ stoa: { light: 'stoa', dark: 'stoa-dark' },
27
30
  };
28
31
 
29
32
  /** Whether a theme ID is a dark theme */
@@ -33,6 +36,8 @@ export const IS_DARK: Record<string, boolean> = {
33
36
  generics: true,
34
37
  agora: false,
35
38
  'agora-dark': true,
39
+ stoa: false,
40
+ 'stoa-dark': true,
36
41
  };
37
42
 
38
43
  /** All valid theme IDs */