@acms-subir/core 0.2.0 → 0.3.1

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 (254) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/cli/common.mjs +35 -0
  3. package/cli/legacy.mjs +317 -0
  4. package/cli/main.mjs +66 -0
  5. package/cli/palette.mjs +4 -0
  6. package/cli/preview.mjs +50 -0
  7. package/cli/remote.mjs +55 -0
  8. package/cli/themes.mjs +127 -0
  9. package/cli.mjs +2 -369
  10. package/deploy/Caddyfile +8 -0
  11. package/deploy/Dockerfile +34 -0
  12. package/deploy/compose.https.yml +21 -0
  13. package/deploy/compose.yml +46 -0
  14. package/docs/live-video.md +91 -0
  15. package/docs/theme-authoring.md +103 -0
  16. package/email-runner.mjs +32 -0
  17. package/integration.mjs +189 -2
  18. package/ops-check.mjs +27 -0
  19. package/package.json +27 -5
  20. package/src/actions/index.ts +578 -49
  21. package/src/components/AiCleaner.tsx +291 -0
  22. package/src/components/AiSettings.tsx +575 -0
  23. package/src/components/CommandPalette.tsx +200 -0
  24. package/src/components/ContentList.astro +246 -0
  25. package/src/components/DesignAccess.tsx +182 -0
  26. package/src/components/DesignManager.tsx +418 -46
  27. package/src/components/EmailSettings.tsx +521 -0
  28. package/src/components/EventPage.astro +178 -0
  29. package/src/components/LegalPage.astro +27 -0
  30. package/src/components/LinksManager.tsx +119 -58
  31. package/src/components/LiveCard.astro +174 -0
  32. package/src/components/LiveManager.tsx +910 -0
  33. package/src/components/NavManager.tsx +42 -43
  34. package/src/components/ThemeScript.astro +50 -0
  35. package/src/components/ThemeSections.astro +58 -0
  36. package/src/components/ThemeSectionsEditor.tsx +513 -0
  37. package/src/components/WidgetRenderer.astro +3 -0
  38. package/src/components/booking/BookingFlow.astro +150 -0
  39. package/src/components/booking/HoursEditor.astro +63 -0
  40. package/src/components/builder/PageBuilder.tsx +10 -2
  41. package/src/components/editor/EventPanel.tsx +401 -0
  42. package/src/components/editor/FormatDock.tsx +81 -0
  43. package/src/components/editor/LivePane.tsx +122 -0
  44. package/src/components/editor/MobileToolbar.tsx +93 -0
  45. package/src/components/editor/PostEditor.tsx +605 -223
  46. package/src/components/editor/PreviewOverlay.tsx +242 -43
  47. package/src/components/editor/extensions/calendar.tsx +51 -7
  48. package/src/components/editor/extensions/events.tsx +153 -0
  49. package/src/components/editor/extensions/slash.tsx +7 -1
  50. package/src/components/site/Pager.astro +24 -0
  51. package/src/components/site/PostCard.astro +64 -0
  52. package/src/components/site/TagLabel.astro +11 -0
  53. package/src/layouts/Admin.astro +289 -49
  54. package/src/layouts/Site.astro +352 -59
  55. package/src/lib/agent-api.ts +13 -0
  56. package/src/lib/agent-discovery.ts +40 -0
  57. package/src/lib/agent.ts +114 -0
  58. package/src/lib/ai-config.ts +58 -0
  59. package/src/lib/ai-schema.ts +22 -0
  60. package/src/lib/ai.ts +90 -0
  61. package/src/lib/api-keys.ts +65 -0
  62. package/src/lib/api.ts +56 -0
  63. package/src/lib/auth.ts +25 -5
  64. package/src/lib/backup/content.ts +36 -0
  65. package/src/lib/backup/unzip.ts +12 -1
  66. package/src/lib/backup/zip.ts +1 -1
  67. package/src/lib/booking/agent.ts +108 -0
  68. package/src/lib/booking/availability.ts +221 -0
  69. package/src/lib/booking/emails.ts +115 -0
  70. package/src/lib/booking/forms.ts +77 -0
  71. package/src/lib/booking/service.ts +277 -0
  72. package/src/lib/booking/view.ts +54 -0
  73. package/src/lib/calendar/agent-schema.ts +48 -0
  74. package/src/lib/calendar/agent.ts +136 -0
  75. package/src/lib/calendar/api.ts +81 -0
  76. package/src/lib/calendar/cards.ts +102 -0
  77. package/src/lib/calendar/config.ts +52 -0
  78. package/src/lib/calendar/data.ts +130 -0
  79. package/src/lib/calendar/events.ts +261 -0
  80. package/src/lib/calendar/feeds.ts +43 -0
  81. package/src/lib/calendar/hooks.ts +38 -0
  82. package/src/lib/calendar/http.ts +30 -0
  83. package/src/lib/calendar/ics-write.ts +120 -0
  84. package/src/lib/calendar/ics.ts +15 -5
  85. package/src/lib/calendar/import.ts +203 -0
  86. package/src/lib/calendar/render.ts +120 -37
  87. package/src/lib/calendar/rrule.ts +149 -0
  88. package/src/lib/calendar/seo.ts +48 -0
  89. package/src/lib/design-service.ts +58 -0
  90. package/src/lib/designDiff.ts +145 -0
  91. package/src/lib/designPreview.ts +110 -12
  92. package/src/lib/documents/cleanup.ts +31 -0
  93. package/src/lib/documents/extract.ts +154 -0
  94. package/src/lib/documents/vendor.d.ts +4 -0
  95. package/src/lib/drafts.ts +60 -6
  96. package/src/lib/dynamicBlocks.ts +40 -8
  97. package/src/lib/email/accounts.ts +55 -0
  98. package/src/lib/email/config.ts +86 -0
  99. package/src/lib/email/migration.ts +24 -0
  100. package/src/lib/email/providers.ts +89 -0
  101. package/src/lib/email/service.ts +78 -0
  102. package/src/lib/email/store.ts +167 -0
  103. package/src/lib/email/templates.ts +18 -0
  104. package/src/lib/email/types.ts +32 -0
  105. package/src/lib/email/webhooks.ts +87 -0
  106. package/src/lib/email.ts +15 -0
  107. package/src/lib/env.ts +1 -1
  108. package/src/lib/legal/index.ts +25 -0
  109. package/src/lib/live/client.ts +88 -0
  110. package/src/lib/live/crypto.ts +28 -0
  111. package/src/lib/live/migration.ts +15 -0
  112. package/src/lib/live/providers.ts +152 -0
  113. package/src/lib/live/service.ts +170 -0
  114. package/src/lib/live/signals.ts +89 -0
  115. package/src/lib/live/store.ts +49 -0
  116. package/src/lib/live/types.ts +52 -0
  117. package/src/lib/live/view.ts +78 -0
  118. package/src/lib/mobile/bookings.ts +73 -0
  119. package/src/lib/mobile/calendar.ts +85 -0
  120. package/src/lib/mobile/content.ts +85 -0
  121. package/src/lib/mobile/http.ts +63 -0
  122. package/src/lib/mobile/route.ts +20 -0
  123. package/src/lib/ops.ts +8 -0
  124. package/src/lib/preview.ts +21 -1
  125. package/src/lib/repo/migrations.ts +221 -0
  126. package/src/lib/repo/sql.ts +907 -68
  127. package/src/lib/repo/types.ts +125 -5
  128. package/src/lib/scheduler.ts +27 -0
  129. package/src/lib/schema.ts +316 -1
  130. package/src/lib/search-i18n.ts +3 -0
  131. package/src/lib/socialIcons.ts +32 -0
  132. package/src/lib/tagColor.ts +11 -0
  133. package/src/lib/theme.ts +59 -40
  134. package/src/lib/themeTokens.ts +183 -0
  135. package/src/lib/umami.ts +108 -0
  136. package/src/lib/webhook-sql.ts +68 -0
  137. package/src/lib/webhooks.ts +147 -0
  138. package/src/lib/widgets.ts +1 -0
  139. package/src/middleware.ts +45 -3
  140. package/src/pages/404.astro +23 -0
  141. package/src/pages/[...slug].astro +14 -5
  142. package/src/pages/account/[action].astro +53 -0
  143. package/src/pages/admin/ai.astro +12 -0
  144. package/src/pages/admin/c/[collection]/[id].astro +4 -1
  145. package/src/pages/admin/c/[collection]/index.astro +127 -92
  146. package/src/pages/admin/calendar/bookings.astro +400 -0
  147. package/src/pages/admin/calendar/calendars.astro +265 -0
  148. package/src/pages/admin/calendar/events/[id].astro +56 -0
  149. package/src/pages/admin/calendar/events/index.astro +408 -0
  150. package/src/pages/admin/calendar/index.ts +4 -0
  151. package/src/pages/admin/calendar/integrations.astro +4 -0
  152. package/src/pages/admin/calendar/resources/[id].astro +180 -0
  153. package/src/pages/admin/calendar/resources/index.astro +106 -0
  154. package/src/pages/admin/calendar/resources/link.astro +196 -0
  155. package/src/pages/admin/design.astro +10 -3
  156. package/src/pages/admin/email.astro +13 -0
  157. package/src/pages/admin/index.astro +232 -85
  158. package/src/pages/admin/integrations.astro +237 -0
  159. package/src/pages/admin/links.astro +3 -3
  160. package/src/pages/admin/live.astro +12 -0
  161. package/src/pages/admin/login.astro +14 -10
  162. package/src/pages/admin/navigation.astro +8 -2
  163. package/src/pages/admin/pages/[id].astro +4 -1
  164. package/src/pages/admin/pages/index.astro +41 -48
  165. package/src/pages/admin/settings.astro +418 -367
  166. package/src/pages/admin/tags.astro +4 -4
  167. package/src/pages/admin/users.astro +38 -28
  168. package/src/pages/api/agent/docs.ts +33 -0
  169. package/src/pages/api/agent/index.ts +21 -0
  170. package/src/pages/api/ai/clean.ts +13 -0
  171. package/src/pages/api/ai/draft.ts +14 -0
  172. package/src/pages/api/ai/settings.ts +53 -0
  173. package/src/pages/api/ai/test.ts +12 -0
  174. package/src/pages/api/backup.ts +8 -21
  175. package/src/pages/api/calendar/availability.ts +35 -0
  176. package/src/pages/api/calendar/bookings/[id].ts +49 -0
  177. package/src/pages/api/calendar/bookings/index.ts +73 -0
  178. package/src/pages/api/calendar/calendars.ts +35 -0
  179. package/src/pages/api/calendar/docs.ts +66 -0
  180. package/src/pages/api/calendar/events/[id].ts +63 -0
  181. package/src/pages/api/calendar/events/index.ts +112 -0
  182. package/src/pages/api/calendar/resources.ts +34 -0
  183. package/src/pages/api/design/capabilities.ts +10 -0
  184. package/src/pages/api/design/index.ts +58 -0
  185. package/src/pages/api/design/keys.ts +24 -0
  186. package/src/pages/api/design/media.ts +32 -0
  187. package/src/pages/api/email/run.ts +11 -0
  188. package/src/pages/api/email/settings.ts +61 -0
  189. package/src/pages/api/email/webhooks/[provider].ts +4 -0
  190. package/src/pages/api/live/oauth/youtube/callback.ts +19 -0
  191. package/src/pages/api/live/settings.ts +45 -0
  192. package/src/pages/api/live/status.ts +7 -0
  193. package/src/pages/api/live/thumb.ts +48 -0
  194. package/src/pages/api/mcp.ts +59 -0
  195. package/src/pages/api/media/upload.ts +2 -2
  196. package/src/pages/api/ops/status.ts +39 -0
  197. package/src/pages/api/restore.ts +27 -37
  198. package/src/pages/api/search.ts +18 -4
  199. package/src/pages/api/v1/availability.ts +7 -0
  200. package/src/pages/api/v1/bookings.ts +17 -0
  201. package/src/pages/api/v1/calendars.ts +7 -0
  202. package/src/pages/api/v1/content/[id].ts +7 -0
  203. package/src/pages/api/v1/content/index.ts +7 -0
  204. package/src/pages/api/v1/events.ts +7 -0
  205. package/src/pages/api/v1/resources.ts +7 -0
  206. package/src/pages/api/webhooks/docs.ts +61 -0
  207. package/src/pages/author/[slug].astro +43 -35
  208. package/src/pages/blog/[slug].astro +137 -66
  209. package/src/pages/blog/index.astro +95 -30
  210. package/src/pages/blog/tag/[slug].astro +45 -40
  211. package/src/pages/book/[resource]/[link].astro +70 -0
  212. package/src/pages/book/[resource]/index.astro +63 -0
  213. package/src/pages/book/[resource].ics.ts +38 -0
  214. package/src/pages/book/index.astro +47 -0
  215. package/src/pages/book/manage/[token].astro +108 -0
  216. package/src/pages/calendar/[slug].ics.ts +27 -0
  217. package/src/pages/calendar/feed.ics.ts +15 -0
  218. package/src/pages/calendar/index.astro +191 -0
  219. package/src/pages/embed/book/[resource]/[link].astro +33 -0
  220. package/src/pages/embed/calendar/[slug].astro +43 -0
  221. package/src/pages/events/[slug].astro +74 -0
  222. package/src/pages/events/[slug].ics.ts +25 -0
  223. package/src/pages/index.astro +12 -3
  224. package/src/pages/legal/privacy.astro +4 -0
  225. package/src/pages/legal/terms.astro +4 -0
  226. package/src/pages/links.astro +29 -3
  227. package/src/pages/llms.txt.ts +4 -0
  228. package/src/pages/preview/[type]/[id].astro +27 -6
  229. package/src/pages/search.astro +40 -20
  230. package/src/pages/sitemap.xml.ts +16 -1
  231. package/src/styles/admin.css +924 -105
  232. package/src/styles/cards.css +92 -74
  233. package/src/styles/editor.css +492 -114
  234. package/src/styles/email-settings.css +117 -0
  235. package/src/styles/live-admin.css +126 -0
  236. package/src/styles/live.css +199 -0
  237. package/src/styles/sections.css +12 -0
  238. package/src/styles/site.css +938 -281
  239. package/src/styles/theme-editor.css +135 -0
  240. package/src/theme-sdk/contract.d.mts +619 -0
  241. package/src/theme-sdk/contract.mjs +81 -0
  242. package/src/theme-sdk/index.ts +15 -0
  243. package/src/theme-sdk/tokens.d.mts +148 -0
  244. package/src/theme-sdk/tokens.mjs +55 -0
  245. package/src/themes/editorial/templates/home.astro +4 -4
  246. package/src/themes/editorial/templates/post-minimal.astro +2 -2
  247. package/src/themes/editorial/theme.css +27 -37
  248. package/src/themes/editorial/theme.json +25 -3
  249. package/src/themes/field/theme.json +27 -9
  250. package/src/themes/ink/theme.json +30 -12
  251. package/src/themes/night/theme.css +4 -7
  252. package/src/themes/night/theme.json +28 -10
  253. package/src/virtual.d.ts +14 -0
  254. package/src/worker.ts +5 -1
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ import { ThemeTokens } from './tokens.mjs';
3
+ export { ThemeTokens } from './tokens.mjs';
4
+ export const THEME_API_VERSION = 1;
5
+ export const Slug = z.string().regex(/^[a-z][a-z0-9-]{0,49}$/);
6
+ const text = z.string().max(20000);
7
+ export const Field = z.object({
8
+ label: z.string().min(1).max(100), type: z.enum(['text', 'textarea', 'image', 'url', 'select', 'number']),
9
+ default: z.union([text, z.number()]).optional(), options: z.array(z.string().max(100)).max(30).optional(),
10
+ }).strict();
11
+ export const SectionDefinition = z.object({
12
+ name: z.string().min(1).max(100), version: z.number().int().positive().default(1),
13
+ component: z.string().regex(/^sections\/[a-zA-Z0-9-]+\.astro$/).optional(),
14
+ variants: z.array(Slug).min(1).max(20).default(['default']),
15
+ fields: z.record(z.string().regex(/^[a-zA-Z][a-zA-Z0-9]*$/), Field).default({}),
16
+ }).strict();
17
+ const f = (label, type = 'text', value = '') => ({ label, type, default: value });
18
+ const base = { heading: f('Heading'), text: f('Supporting text', 'textarea') };
19
+ /** @type {Record<string, import("zod").infer<typeof SectionDefinition>>} */
20
+ export const BUILTIN_SECTIONS = {
21
+ live: { name: 'Live video', version: 1, variants: ['card','player'], fields: {} },
22
+ 'page-content': { name: 'Existing page content', version: 1, variants: ['default'], fields: {} },
23
+ hero: { name: 'Hero', version: 1, variants: ['split', 'centered'], fields: { ...base, image: f('Image', 'image'), alt: f('Image description'), label: f('Button label'), href: f('Button link', 'url') } },
24
+ features: { name: 'Features', version: 1, variants: ['grid', 'list'], fields: { ...base, items: f('Features (one per line: title | description)', 'textarea') } },
25
+ testimonials: { name: 'Testimonials', version: 1, variants: ['grid', 'list'], fields: { ...base, items: f('Quotes (one per line: name | quote)', 'textarea') } },
26
+ faq: { name: 'FAQ', version: 1, variants: ['default'], fields: { ...base, items: f('Questions (one per line: question | answer)', 'textarea') } },
27
+ posts: { name: 'Posts', version: 1, variants: ['grid', 'list'], fields: { heading: f('Heading', 'text', 'Latest posts'), collection: f('Collection', 'text', 'posts'), limit: f('Number of posts', 'number', 3) } },
28
+ cta: { name: 'Call to action', version: 1, variants: ['default', 'panel'], fields: { ...base, label: f('Button label'), href: f('Button link', 'url') } },
29
+ };
30
+ export const SectionInstance = z.object({
31
+ id: z.string().min(1).max(100), type: Slug, version: z.number().int().positive().default(1),
32
+ variant: Slug.default('default'), hidden: z.boolean().default(false),
33
+ data: z.record(z.string().max(100), z.union([text, z.number().finite()])).default({}),
34
+ }).strict();
35
+ export const SectionLayout = z.object({ mode: z.enum(['content', 'sections']).default('sections'), sections: z.array(SectionInstance).max(50) }).strict()
36
+ .refine(v => new Set(v.sections.map(s => s.id)).size === v.sections.length, 'Section IDs must be unique');
37
+ export const SectionPages = z.record(z.string().min(1).max(100), SectionLayout);
38
+ export const PostOptions = z.object({
39
+ cover: z.enum(['normal', 'full', 'overlay', 'hidden']).default('normal'),
40
+ showDate: z.boolean().default(true),
41
+ showReadingTime: z.boolean().default(true),
42
+ byline: z.boolean().default(true),
43
+ authorCard: z.boolean().default(true),
44
+ share: z.boolean().default(true),
45
+ related: z.boolean().default(true),
46
+ // reading-progress bar across the top, and the sticky "On this page" rail
47
+ progress: z.boolean().default(true),
48
+ toc: z.boolean().default(true),
49
+ });
50
+
51
+ export const ThemeManifest = z.object({
52
+ name: z.string().min(1).max(120), description: z.string().max(2000).default(''), slug: Slug.optional(),
53
+ version: z.string().regex(/^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$/).default('1.0.0'),
54
+ apiVersion: z.literal(1).default(1), core: z.string().default('>=0.2.0 <1.0.0'),
55
+ author: z.string().max(200).optional(), license: z.string().max(100).optional(),
56
+ tokens: ThemeTokens.default({}), post: PostOptions.default({}),
57
+ templates: z.array(z.string().regex(/^(home|event|post-[a-z0-9-]+|page-[a-z0-9-]+)\.astro$/)).default([]),
58
+ sections: z.record(Slug, SectionDefinition).default({}),
59
+ assets: z.array(z.string().regex(/^assets\/(?!.*\.\.)[a-zA-Z0-9_./-]+$/)).default([]),
60
+ }).strict();
61
+ /** @param {Partial<import("zod").infer<typeof ThemeManifest>>} [manifest] */
62
+ export function sectionDefinitions(manifest = {}) {
63
+ return { ...BUILTIN_SECTIONS, ...(manifest.sections ?? {}) };
64
+ }
65
+ /** @param {import("zod").infer<typeof SectionLayout>} layout
66
+ * @param {Record<string, import("zod").infer<typeof SectionDefinition>>} definitions */
67
+ export function sectionIssues(layout, definitions) {
68
+ return layout.sections.flatMap(s => {
69
+ const d = definitions[s.type];
70
+ if (!d) return [{ id: s.id, message: `Unavailable section: ${s.type}. Its data is preserved.` }];
71
+ if (s.version !== d.version) return [{ id: s.id, message: `Section ${s.type} needs a migration from v${s.version} to v${d.version}.` }];
72
+ if (!d.variants.includes(s.variant)) return [{ id: s.id, message: `Unsupported variant: ${s.variant}` }];
73
+ return Object.entries(d.fields).flatMap(([key, field]) => {
74
+ const value = s.data[key] ?? field.default ?? (field.type === 'number' ? 0 : '');
75
+ const valid = field.type === 'number' ? typeof value === 'number' && Number.isFinite(value)
76
+ : typeof value === 'string' && (field.type !== 'select' || field.options?.includes(value));
77
+ const safeLink = !['url','image'].includes(field.type) || value === '' || (typeof value === 'string' && /^(https?:\/\/|mailto:|tel:|\/(?!\/)|#)/i.test(value.trim()));
78
+ return valid && safeLink ? [] : [{ id: s.id, message: `Invalid ${field.label}` }];
79
+ });
80
+ });
81
+ }
@@ -0,0 +1,15 @@
1
+ export { ThemeTokens, ThemeManifest, PostOptions, SectionDefinition, SectionInstance, SectionLayout, SectionPages, BUILTIN_SECTIONS, sectionDefinitions, sectionIssues, THEME_API_VERSION } from './contract.mjs';
2
+ export { CONTENT, postsIndexUrl, postUrl, tagUrl, collectionUrl } from '../lib/content';
3
+ export { formatDate } from '../lib/dates';
4
+ export type { Page, Post, Settings, Tag, CalEvent, Calendar, User } from '../lib/schema';
5
+ import type { Page, Post, Settings, Tag, CalEvent, Calendar, User } from '../lib/schema';
6
+ export interface HomeProps { settings: Settings; page: Page | null; posts: Post[]; tags: Tag[] }
7
+ export interface PageProps { settings: Settings; page: Page; html: string }
8
+ export interface PostProps { settings: Settings; post: Post; html: string; authors: User[]; related: Post[] }
9
+ import type { Occurrence } from '../lib/calendar/events';
10
+ export interface EventProps { settings: Settings; event: CalEvent; html: string; calendars: Calendar[]; origin: string; occurrence: Occurrence; upcoming: Pick<Occurrence, 'event' | 'start' | 'end' | 'allDay' | 'days'>[]; preview?: boolean; rsvp?: {going:number;left:number|null;maxSeats:number;error:string;open:boolean}|null }
11
+ export interface SectionProps { data: Record<string, string | number>; variant: string; settings: Settings; page: Page }
12
+
13
+ export { slugify, authorSlug } from '../lib/schema';
14
+
15
+ export { localeFromPath, localeHomeUrl } from '../lib/i18n';
@@ -0,0 +1,148 @@
1
+ export const DarkTokens: z.ZodObject<{
2
+ bg: z.ZodOptional<z.ZodString>;
3
+ ink: z.ZodOptional<z.ZodString>;
4
+ muted: z.ZodOptional<z.ZodString>;
5
+ line: z.ZodOptional<z.ZodString>;
6
+ accent: z.ZodOptional<z.ZodString>;
7
+ panel: z.ZodOptional<z.ZodString>;
8
+ soft: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ panel?: string | undefined;
11
+ bg?: string | undefined;
12
+ ink?: string | undefined;
13
+ muted?: string | undefined;
14
+ line?: string | undefined;
15
+ accent?: string | undefined;
16
+ soft?: string | undefined;
17
+ }, {
18
+ panel?: string | undefined;
19
+ bg?: string | undefined;
20
+ ink?: string | undefined;
21
+ muted?: string | undefined;
22
+ line?: string | undefined;
23
+ accent?: string | undefined;
24
+ soft?: string | undefined;
25
+ }>;
26
+ export const ThemeTokens: z.ZodObject<{
27
+ bg: z.ZodDefault<z.ZodString>;
28
+ ink: z.ZodDefault<z.ZodString>;
29
+ muted: z.ZodDefault<z.ZodString>;
30
+ line: z.ZodDefault<z.ZodString>;
31
+ accent: z.ZodDefault<z.ZodString>;
32
+ panel: z.ZodDefault<z.ZodString>;
33
+ soft: z.ZodDefault<z.ZodString>;
34
+ accentInk: z.ZodDefault<z.ZodString>;
35
+ button: z.ZodDefault<z.ZodEnum<["ink", "accent"]>>;
36
+ dark: z.ZodDefault<z.ZodObject<{
37
+ bg: z.ZodOptional<z.ZodString>;
38
+ ink: z.ZodOptional<z.ZodString>;
39
+ muted: z.ZodOptional<z.ZodString>;
40
+ line: z.ZodOptional<z.ZodString>;
41
+ accent: z.ZodOptional<z.ZodString>;
42
+ panel: z.ZodOptional<z.ZodString>;
43
+ soft: z.ZodOptional<z.ZodString>;
44
+ }, "strip", z.ZodTypeAny, {
45
+ panel?: string | undefined;
46
+ bg?: string | undefined;
47
+ ink?: string | undefined;
48
+ muted?: string | undefined;
49
+ line?: string | undefined;
50
+ accent?: string | undefined;
51
+ soft?: string | undefined;
52
+ }, {
53
+ panel?: string | undefined;
54
+ bg?: string | undefined;
55
+ ink?: string | undefined;
56
+ muted?: string | undefined;
57
+ line?: string | undefined;
58
+ accent?: string | undefined;
59
+ soft?: string | undefined;
60
+ }>>;
61
+ scheme: z.ZodDefault<z.ZodEnum<["light", "dark", "auto"]>>;
62
+ radius: z.ZodDefault<z.ZodEnum<["sharp", "soft", "round", "smooth"]>>;
63
+ elevation: z.ZodDefault<z.ZodEnum<["flat", "soft", "lifted"]>>;
64
+ density: z.ZodDefault<z.ZodEnum<["compact", "cozy", "airy"]>>;
65
+ headingFont: z.ZodDefault<z.ZodString>;
66
+ bodyFont: z.ZodDefault<z.ZodString>;
67
+ monoFont: z.ZodDefault<z.ZodString>;
68
+ googleFonts: z.ZodDefault<z.ZodString>;
69
+ displayWeight: z.ZodDefault<z.ZodNumber>;
70
+ displayTracking: z.ZodDefault<z.ZodString>;
71
+ baseSize: z.ZodDefault<z.ZodNumber>;
72
+ width: z.ZodDefault<z.ZodNumber>;
73
+ wideWidth: z.ZodDefault<z.ZodNumber>;
74
+ header: z.ZodDefault<z.ZodEnum<["floating", "bar", "minimal", "centered"]>>;
75
+ footer: z.ZodDefault<z.ZodEnum<["columns", "simple"]>>;
76
+ blogLayout: z.ZodDefault<z.ZodEnum<["auto", "cards", "list"]>>;
77
+ }, "strip", z.ZodTypeAny, {
78
+ panel: string;
79
+ bg: string;
80
+ ink: string;
81
+ muted: string;
82
+ line: string;
83
+ accent: string;
84
+ soft: string;
85
+ accentInk: string;
86
+ button: "ink" | "accent";
87
+ dark: {
88
+ panel?: string | undefined;
89
+ bg?: string | undefined;
90
+ ink?: string | undefined;
91
+ muted?: string | undefined;
92
+ line?: string | undefined;
93
+ accent?: string | undefined;
94
+ soft?: string | undefined;
95
+ };
96
+ scheme: "dark" | "light" | "auto";
97
+ radius: "soft" | "sharp" | "round" | "smooth";
98
+ elevation: "flat" | "soft" | "lifted";
99
+ density: "compact" | "cozy" | "airy";
100
+ headingFont: string;
101
+ bodyFont: string;
102
+ monoFont: string;
103
+ googleFonts: string;
104
+ displayWeight: number;
105
+ displayTracking: string;
106
+ baseSize: number;
107
+ width: number;
108
+ wideWidth: number;
109
+ header: "centered" | "floating" | "bar" | "minimal";
110
+ footer: "columns" | "simple";
111
+ blogLayout: "list" | "auto" | "cards";
112
+ }, {
113
+ panel?: string | undefined;
114
+ bg?: string | undefined;
115
+ ink?: string | undefined;
116
+ muted?: string | undefined;
117
+ line?: string | undefined;
118
+ accent?: string | undefined;
119
+ soft?: string | undefined;
120
+ accentInk?: string | undefined;
121
+ button?: "ink" | "accent" | undefined;
122
+ dark?: {
123
+ panel?: string | undefined;
124
+ bg?: string | undefined;
125
+ ink?: string | undefined;
126
+ muted?: string | undefined;
127
+ line?: string | undefined;
128
+ accent?: string | undefined;
129
+ soft?: string | undefined;
130
+ } | undefined;
131
+ scheme?: "dark" | "light" | "auto" | undefined;
132
+ radius?: "soft" | "sharp" | "round" | "smooth" | undefined;
133
+ elevation?: "flat" | "soft" | "lifted" | undefined;
134
+ density?: "compact" | "cozy" | "airy" | undefined;
135
+ headingFont?: string | undefined;
136
+ bodyFont?: string | undefined;
137
+ monoFont?: string | undefined;
138
+ googleFonts?: string | undefined;
139
+ displayWeight?: number | undefined;
140
+ displayTracking?: string | undefined;
141
+ baseSize?: number | undefined;
142
+ width?: number | undefined;
143
+ wideWidth?: number | undefined;
144
+ header?: "centered" | "floating" | "bar" | "minimal" | undefined;
145
+ footer?: "columns" | "simple" | undefined;
146
+ blogLayout?: "list" | "auto" | "cards" | undefined;
147
+ }>;
148
+ import { z } from 'zod';
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ const hex = z.string();
3
+
4
+ // the dark-scheme palette; any key left out is generated from the light one
5
+ export const DarkTokens = z.object({
6
+ bg: hex.optional(),
7
+ ink: hex.optional(),
8
+ muted: hex.optional(),
9
+ line: hex.optional(),
10
+ accent: hex.optional(),
11
+ panel: hex.optional(),
12
+ soft: hex.optional(),
13
+ live: hex.optional(),
14
+ });
15
+
16
+ export const ThemeTokens = z.object({
17
+ // color (light scheme)
18
+ bg: hex.default('#f6f5f2'),
19
+ ink: hex.default('#17181c'),
20
+ muted: hex.default('#62646c'),
21
+ line: hex.default('#e5e2db'),
22
+ accent: hex.default('#3355ff'),
23
+ panel: hex.default('#fdfcfa'),
24
+ soft: hex.default('#efede8'),
25
+ // text on an accent fill; '' = pick white or near-black by contrast
26
+ accentInk: hex.default(''),
27
+ // the live-video signal color (LIVE tag, markers); '' = a tuned red
28
+ live: hex.default(''),
29
+ // primary buttons: ink-filled (default) or accent-filled
30
+ button: z.enum(['ink', 'accent']).default('ink'),
31
+ // dark scheme
32
+ dark: DarkTokens.default({}),
33
+ scheme: z.enum(['light', 'dark', 'auto']).default('auto'),
34
+ // shape + depth + rhythm
35
+ radius: z.enum(['sharp', 'soft', 'round', 'smooth']).default('round'),
36
+ elevation: z.enum(['flat', 'soft', 'lifted']).default('soft'),
37
+ density: z.enum(['compact', 'cozy', 'airy']).default('cozy'),
38
+ // type
39
+ headingFont: z.string().default("'Geist', ui-sans-serif, system-ui, sans-serif"),
40
+ bodyFont: z.string().default("'Geist', ui-sans-serif, system-ui, sans-serif"),
41
+ monoFont: z.string().default("'Geist Mono', ui-monospace, 'SF Mono', monospace"),
42
+ // the `family=` query for fonts.googleapis.com/css2; empty = no webfont
43
+ googleFonts: z.string().default('family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500'),
44
+ displayWeight: z.coerce.number().default(650),
45
+ displayTracking: z.string().default('-0.04em'),
46
+ baseSize: z.coerce.number().default(17),
47
+ // layout
48
+ width: z.coerce.number().default(720),
49
+ wideWidth: z.coerce.number().default(1080),
50
+ header: z.enum(['floating', 'bar', 'minimal', 'centered']).default('floating'),
51
+ footer: z.enum(['columns', 'simple']).default('columns'),
52
+ // 'auto' = cards when at least half the listed posts have a cover, else list
53
+ blogLayout: z.enum(['auto', 'cards', 'list']).default('auto'),
54
+ });
55
+
@@ -3,10 +3,10 @@
3
3
  // (and theme.css) to redesign the homepage; the words stay UI-editable because
4
4
  // the isHome page's rendered body is injected as the hero. Sections are driven
5
5
  // by data: `featured` posts, latest, and the `projects` / `log` tags.
6
- import type { Page, Post, Settings, Tag } from '../../../lib/schema';
7
- import { slugify } from '../../../lib/schema';
8
- import { formatDate } from '../../../lib/dates';
9
- import { CONTENT, postsIndexUrl, postUrl, tagUrl } from '../../../lib/content';
6
+ import type { Page, Post, Settings, Tag } from '@acms-subir/core/theme';
7
+ import { slugify } from '@acms-subir/core/theme';
8
+ import { formatDate } from '@acms-subir/core/theme';
9
+ import { CONTENT, postsIndexUrl, postUrl, tagUrl } from '@acms-subir/core/theme';
10
10
 
11
11
  interface Props {
12
12
  settings: Settings;
@@ -9,8 +9,8 @@
9
9
  // authors — ordered User[] (may be empty)
10
10
  // settings — site Settings
11
11
  // related — up to 3 related Post[]
12
- import type { Post, Settings, User } from '../../../lib/schema';
13
- import { authorSlug } from '../../../lib/schema';
12
+ import type { Post, Settings, User } from '@acms-subir/core/theme';
13
+ import { authorSlug } from '@acms-subir/core/theme';
14
14
 
15
15
  interface Props {
16
16
  post: Post;
@@ -4,15 +4,18 @@ body { font-size: 16.5px; }
4
4
  h1, h2, h3 { font-weight: 550; letter-spacing: -0.015em; }
5
5
  article h1 { font-size: 42px; line-height: 1.12; }
6
6
 
7
- .site-header { padding-top: 34px; }
8
- .site-header .brand { font-family: var(--font-heading); font-size: 20px; font-weight: 550; }
7
+ .site-header .brand { font-family: var(--font-heading); font-size: 22px; font-weight: 550; letter-spacing: -0.01em; }
9
8
  .site-nav a { text-transform: uppercase; font-size: 12px; letter-spacing: 0.1em; }
10
- .site-nav a[aria-current='page'] { border-bottom: 1.5px solid var(--ink); padding-bottom: 3px; }
9
+ .site-nav a:hover { background: none; }
10
+ .site-nav a[aria-current='page'] { background: none; box-shadow: inset 0 -1.5px 0 var(--ink); border-radius: 0; font-weight: 400; }
11
11
 
12
12
  .post-list a { font-family: var(--font-heading); font-weight: 550; }
13
13
  .post-meta { text-transform: uppercase; font-size: 11.5px; letter-spacing: 0.08em; }
14
14
  .post-cover { border-radius: 4px; }
15
- .tag { border-radius: 3px; text-transform: uppercase; font-size: 11px; letter-spacing: 0.06em; }
15
+ .tag { text-transform: uppercase; font-size: 11px; letter-spacing: 0.08em; }
16
+ .post-card-title { font-family: var(--font-heading); font-weight: 550; font-size: 22px; }
17
+ .post-card.is-feature .post-card-title { font-weight: 500; }
18
+ .filter-tabs a { text-transform: uppercase; font-size: 12px; letter-spacing: 0.08em; }
16
19
 
17
20
  .site-footer { text-align: center; text-transform: uppercase; font-size: 12px; letter-spacing: 0.08em; }
18
21
 
@@ -63,46 +66,33 @@ article h1 { font-size: 42px; line-height: 1.12; }
63
66
  .ed-feature .ed-excerpt { max-width: 58ch; }
64
67
 
65
68
  /* ---- post page: Ghost-style editorial treatment ---- */
66
- .post-header { text-align: center; padding-top: 18px; }
67
- .post-kicker { margin-bottom: 10px; }
68
- .post-header .post-kicker .tag { background: none; padding: 0 6px; color: var(--muted); }
69
- .post-header .post-kicker a.tag:hover { background: none; color: var(--accent); }
70
- article > .post-header h1 { font-size: clamp(34px, 6vw, 52px); font-weight: 500; margin: 6px 0 12px; }
71
- .post-header .post-meta { justify-content: center; }
72
- .post-header .byline a { color: var(--muted); text-decoration: none; }
73
- .post-header .byline a:hover { color: var(--accent); }
74
-
75
- /* cover spacing — full-bleed itself is data-driven (.is-full, set by the
76
- theme's post.cover default or the Design screen override) */
77
- .post-cover { margin-top: 26px; margin-bottom: 40px; }
78
-
79
- /* share as a floating rail beside the text on desktop */
80
- @media (min-width: 1000px) {
81
- .post-layout { display: grid; grid-template-columns: minmax(0, 1fr) 44px; gap: 40px; align-items: start; }
82
- .post-share { flex-direction: column; align-items: center; gap: 12px; margin: 4px 0 0; position: sticky; top: 40px; }
83
- .post-share-label { margin: 0 0 2px; }
84
- }
85
-
86
- /* author card: serif italic name, centered block like the reference */
87
- .author-card { gap: 22px; margin-top: 44px; padding-top: 32px; }
69
+ .post-header { text-align: center; }
70
+ .post-kicker { justify-content: center; }
71
+ .post-header h1 { font-weight: 500; letter-spacing: -0.02em; }
72
+ .post-dek { max-width: 56ch; margin-left: auto; margin-right: auto; }
73
+ .post-byline-row { flex-direction: column; gap: 14px; }
74
+ .post-byline { flex-direction: column; gap: 8px; }
75
+ .post-byline > div { align-items: center; }
76
+ .post-byline .post-meta { text-transform: uppercase; font-size: 11.5px; letter-spacing: 0.08em; }
77
+
78
+ /* author card: flat, serif italic name, like the reference */
79
+ .author-card { background: none; border: 0; border-top: 1px solid var(--line); border-radius: 0; padding: 32px 0 0; gap: 22px; }
88
80
  .author-card .author-avatar { width: 76px; height: 76px; }
89
81
  .author-card-name { font-family: var(--font-heading); font-style: italic; font-weight: 500; font-size: 30px; margin: 2px 0 6px; }
90
82
  .author-card-bio { font-size: 15.5px; line-height: 1.6; }
91
- .author-card-more { color: var(--muted); border-bottom: 1px solid var(--line); padding-bottom: 2px; }
92
- .author-card-more:hover { color: var(--ink); }
93
83
 
94
84
  /* related reading */
95
- .related { margin-top: 48px; }
96
85
  .related-title { font-style: italic; font-weight: 500; font-size: 30px; }
97
- .related-card h3 { font-family: var(--font-heading); font-weight: 550; font-size: 20px; }
98
- .related-thumb img { border-radius: 4px; }
99
86
 
100
- /* author page hero */
101
- .author-hero { flex-direction: column; text-align: center; gap: 14px; padding-top: 24px; }
102
- .author-hero .author-avatar { width: 96px; height: 96px; }
103
- .author-hero h1 { font-size: 40px; font-weight: 500; }
104
- .author-hero .tag-desc { max-width: 52ch; margin: 0 auto; }
87
+ /* archive heads */
88
+ .page-head h1, .profile-head h1 { font-weight: 500; letter-spacing: -0.02em; }
89
+ .profile-head { flex-direction: column; text-align: center; }
90
+ .profile-head .page-lede { margin: 0 auto; }
105
91
 
106
92
  /* overlay hero: keep the serif display sizing on the image variant too */
107
93
  .post-hero-content h1 { font-size: clamp(34px, 6vw, 52px); font-weight: 500; }
108
- .post-hero-content .post-kicker .tag { background: none; padding: 0 6px; }
94
+
95
+ /* live video: square-cut tag and spaced caps to match the masthead */
96
+ :root { --live-tag-radius: 2px; }
97
+ .pc-live-tag, .pc-live-kicker, .pc-live-mark { letter-spacing: 0.14em; }
98
+ .pc-live[data-layout='split'] .pc-live-title-text { font-weight: 500; }
@@ -1,18 +1,40 @@
1
1
  {
2
2
  "name": "Editorial",
3
- "description": "Serif editorial magazine — Fraunces display headings, warm paper, crimson accents. Ships a coded homepage with Featured / Latest / tag sections.",
3
+ "description": "Serif editorial magazine: Fraunces display headings, warm paper, crimson accents, flat surfaces. Ships a coded homepage with Featured / Latest / tag sections.",
4
4
  "tokens": {
5
5
  "bg": "#f7f5f0",
6
6
  "ink": "#181712",
7
7
  "muted": "#6d685e",
8
8
  "line": "#e4e0d5",
9
+ "live": "#b8261b",
9
10
  "accent": "#b33a2e",
10
11
  "panel": "#fdfcf9",
11
12
  "soft": "#edeae1",
12
13
  "headingFont": "'Fraunces', Georgia, serif",
13
14
  "bodyFont": "'Inter', system-ui, sans-serif",
14
- "googleFonts": "family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,550;1,9..144,400;1,9..144,550&family=Inter:wght@400;500;600",
15
- "width": 720
15
+ "googleFonts": "family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,550;1,9..144,400;1,9..144,500&family=Inter:wght@400;500;600&family=Geist+Mono:wght@400;500",
16
+ "width": 720,
17
+ "dark": {
18
+ "bg": "#16140f",
19
+ "ink": "#ece6da",
20
+ "muted": "#a39c8e",
21
+ "line": "#2d2a24",
22
+ "live": "#f26b57",
23
+ "accent": "#e0735f",
24
+ "panel": "#1d1b16",
25
+ "soft": "#25221c"
26
+ },
27
+ "button": "accent",
28
+ "radius": "soft",
29
+ "elevation": "flat",
30
+ "density": "airy",
31
+ "monoFont": "'Geist Mono', ui-monospace, monospace",
32
+ "displayWeight": 500,
33
+ "displayTracking": "-0.02em",
34
+ "baseSize": 17.5,
35
+ "header": "centered",
36
+ "footer": "simple",
37
+ "wideWidth": 1040
16
38
  },
17
39
  "post": {
18
40
  "cover": "full"
@@ -1,17 +1,35 @@
1
1
  {
2
2
  "name": "Field Notes",
3
- "description": "Warm green-tinted paper with Lora serif headings — calm, notebook-like.",
3
+ "description": "Calm green-tinted paper with a humanist sans, generous spacing and smooth corners. Notebook-like.",
4
4
  "tokens": {
5
- "bg": "#f4f6f2",
6
- "ink": "#232a22",
7
- "muted": "#68715f",
5
+ "bg": "#f2f5f0",
6
+ "ink": "#1f261e",
7
+ "muted": "#5f6a58",
8
8
  "line": "#dde3d6",
9
+ "live": "#c2412d",
9
10
  "accent": "#3d6b45",
10
- "panel": "#fdfefc",
11
+ "panel": "#fbfdf9",
11
12
  "soft": "#e6ebdf",
12
- "headingFont": "'Lora', Georgia, serif",
13
- "bodyFont": "'Inter', system-ui, sans-serif",
14
- "googleFonts": "family=Lora:ital,wght@0,500;0,600;1,500&family=Inter:wght@400;500;600",
15
- "width": 720
13
+ "headingFont": "'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif",
14
+ "bodyFont": "'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif",
15
+ "googleFonts": "family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500",
16
+ "width": 720,
17
+ "dark": {
18
+ "bg": "#121611",
19
+ "ink": "#e6ebe2",
20
+ "muted": "#9aa596",
21
+ "line": "#283026",
22
+ "accent": "#7cc28a",
23
+ "panel": "#181d17",
24
+ "soft": "#1f261e"
25
+ },
26
+ "button": "accent",
27
+ "radius": "smooth",
28
+ "elevation": "soft",
29
+ "density": "airy",
30
+ "displayWeight": 650,
31
+ "displayTracking": "-0.035em",
32
+ "header": "bar",
33
+ "footer": "columns"
16
34
  }
17
35
  }
@@ -1,17 +1,35 @@
1
1
  {
2
2
  "name": "Ink",
3
- "description": "The original Acms look — quiet Inter sans on warm paper with an indigo accent.",
3
+ "description": "The default. Soft-modern Geist sans on a warm canvas, ink buttons, a cobalt accent, rounded media, and automatic light and dark.",
4
4
  "tokens": {
5
- "bg": "#faf9f7",
6
- "ink": "#1e1f24",
7
- "muted": "#6b6e78",
8
- "line": "#e5e3de",
9
- "accent": "#4f46e5",
10
- "panel": "#ffffff",
11
- "soft": "#eceae5",
12
- "headingFont": "'Inter', system-ui, sans-serif",
13
- "bodyFont": "'Inter', system-ui, sans-serif",
14
- "googleFonts": "family=Inter:wght@400;600;700",
15
- "width": 760
5
+ "bg": "#f6f5f2",
6
+ "ink": "#17181c",
7
+ "muted": "#62646c",
8
+ "line": "#e5e2db",
9
+ "accent": "#3355ff",
10
+ "panel": "#fdfcfa",
11
+ "soft": "#efede8",
12
+ "dark": {
13
+ "bg": "#121316",
14
+ "ink": "#eceae5",
15
+ "muted": "#9a9ca4",
16
+ "line": "#2a2b31",
17
+ "accent": "#7d93ff",
18
+ "panel": "#1a1b1f",
19
+ "soft": "#222328"
20
+ },
21
+ "scheme": "auto",
22
+ "button": "ink",
23
+ "radius": "round",
24
+ "elevation": "soft",
25
+ "density": "cozy",
26
+ "headingFont": "'Geist', ui-sans-serif, system-ui, sans-serif",
27
+ "bodyFont": "'Geist', ui-sans-serif, system-ui, sans-serif",
28
+ "googleFonts": "family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500",
29
+ "width": 720,
30
+ "wideWidth": 1080,
31
+ "header": "floating",
32
+ "footer": "columns",
33
+ "blogLayout": "auto"
16
34
  }
17
35
  }
@@ -1,7 +1,4 @@
1
- /* Night Studio — dark-mode fixups beyond what tokens cover. */
2
- pre { background: #0d0e12; }
3
- .link-btn:hover { box-shadow: 0 6px 18px rgb(0 0 0 / 45%); }
4
- .links-avatar { box-shadow: 0 4px 16px rgb(0 0 0 / 50%); }
5
- .post-cover { background: var(--soft); }
6
- a.tag:hover { color: #14151a; }
7
- .w-btn.primary { color: #14151a; }
1
+ /* Night Studio — personality beyond tokens (the old dark-mode patches are
2
+ gone: accent text, button ink and shadows now come from the tokens). */
3
+ h1, h2, h3 { letter-spacing: -0.03em; }
4
+ .brand { font-family: var(--font-heading); letter-spacing: -0.03em; }
@@ -1,17 +1,35 @@
1
1
  {
2
2
  "name": "Night Studio",
3
- "description": "Dark charcoal studio with Space Grotesk headings and a warm amber accent.",
3
+ "description": "Dark-first studio: charcoal surfaces, Space Grotesk display and a warm amber accent. Set scheme to auto to also offer its light palette.",
4
4
  "tokens": {
5
- "bg": "#14151a",
6
- "ink": "#e8e7e2",
7
- "muted": "#9a9aa3",
8
- "line": "#2b2c33",
9
- "accent": "#e8a849",
10
- "panel": "#1d1e25",
11
- "soft": "#25262e",
5
+ "bg": "#f6f3ec",
6
+ "ink": "#1c1a16",
7
+ "muted": "#6a655b",
8
+ "line": "#e4ded2",
9
+ "accent": "#b87512",
10
+ "panel": "#fdfbf6",
11
+ "soft": "#eee9df",
12
12
  "headingFont": "'Space Grotesk', system-ui, sans-serif",
13
13
  "bodyFont": "'Inter', system-ui, sans-serif",
14
- "googleFonts": "family=Space+Grotesk:wght@500;700&family=Inter:wght@400;500;600",
15
- "width": 760
14
+ "googleFonts": "family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&family=Geist+Mono:wght@400;500",
15
+ "width": 760,
16
+ "dark": {
17
+ "bg": "#121316",
18
+ "ink": "#ebe9e3",
19
+ "muted": "#9b9ba4",
20
+ "line": "#2a2b32",
21
+ "live": "#ff6a55",
22
+ "accent": "#e8a849",
23
+ "panel": "#1a1b21",
24
+ "soft": "#23242b"
25
+ },
26
+ "scheme": "dark",
27
+ "button": "accent",
28
+ "radius": "round",
29
+ "elevation": "lifted",
30
+ "displayWeight": 650,
31
+ "displayTracking": "-0.03em",
32
+ "header": "floating",
33
+ "footer": "columns"
16
34
  }
17
35
  }