@jant/core 0.2.17 → 0.2.18

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 (81) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +319 -115
  4. package/dist/i18n/context.d.ts +2 -2
  5. package/dist/i18n/context.js +1 -1
  6. package/dist/i18n/i18n.d.ts +1 -1
  7. package/dist/i18n/i18n.js +1 -1
  8. package/dist/i18n/index.d.ts +1 -1
  9. package/dist/i18n/index.js +1 -1
  10. package/dist/i18n/locales/en.d.ts.map +1 -1
  11. package/dist/i18n/locales/en.js +1 -1
  12. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  13. package/dist/i18n/locales/zh-Hans.js +1 -1
  14. package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
  15. package/dist/i18n/locales/zh-Hant.js +1 -1
  16. package/dist/lib/config.d.ts +44 -10
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/config.js +69 -44
  19. package/dist/lib/constants.d.ts +2 -1
  20. package/dist/lib/constants.d.ts.map +1 -1
  21. package/dist/lib/constants.js +5 -2
  22. package/dist/lib/sse.d.ts +15 -0
  23. package/dist/lib/sse.d.ts.map +1 -1
  24. package/dist/lib/sse.js +13 -0
  25. package/dist/lib/theme.d.ts +44 -0
  26. package/dist/lib/theme.d.ts.map +1 -0
  27. package/dist/lib/theme.js +65 -0
  28. package/dist/routes/dash/appearance.d.ts +13 -0
  29. package/dist/routes/dash/appearance.d.ts.map +1 -0
  30. package/dist/routes/dash/appearance.js +164 -0
  31. package/dist/routes/dash/settings.d.ts.map +1 -1
  32. package/dist/routes/dash/settings.js +38 -37
  33. package/dist/services/settings.d.ts +1 -0
  34. package/dist/services/settings.d.ts.map +1 -1
  35. package/dist/services/settings.js +3 -0
  36. package/dist/theme/color-themes.d.ts +30 -0
  37. package/dist/theme/color-themes.d.ts.map +1 -0
  38. package/dist/theme/color-themes.js +268 -0
  39. package/dist/theme/layouts/BaseLayout.d.ts +5 -0
  40. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  41. package/dist/theme/layouts/BaseLayout.js +70 -3
  42. package/dist/theme/layouts/DashLayout.d.ts +2 -0
  43. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  44. package/dist/theme/layouts/DashLayout.js +10 -1
  45. package/dist/theme/layouts/index.d.ts +1 -1
  46. package/dist/theme/layouts/index.d.ts.map +1 -1
  47. package/dist/types.d.ts +53 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +52 -0
  50. package/package.json +1 -1
  51. package/src/app.tsx +272 -55
  52. package/src/db/migrations/{0000_solid_moon_knight.sql → 0000_square_wallflower.sql} +3 -3
  53. package/src/db/migrations/meta/0000_snapshot.json +9 -9
  54. package/src/db/migrations/meta/_journal.json +2 -30
  55. package/src/i18n/context.tsx +2 -2
  56. package/src/i18n/i18n.ts +1 -1
  57. package/src/i18n/index.ts +1 -1
  58. package/src/i18n/locales/en.po +328 -252
  59. package/src/i18n/locales/en.ts +1 -1
  60. package/src/i18n/locales/zh-Hans.po +315 -278
  61. package/src/i18n/locales/zh-Hans.ts +1 -1
  62. package/src/i18n/locales/zh-Hant.po +315 -278
  63. package/src/i18n/locales/zh-Hant.ts +1 -1
  64. package/src/lib/config.ts +73 -47
  65. package/src/lib/constants.ts +3 -0
  66. package/src/lib/sse.ts +38 -0
  67. package/src/lib/theme.ts +86 -0
  68. package/src/preset.css +9 -0
  69. package/src/routes/dash/appearance.tsx +180 -0
  70. package/src/routes/dash/settings.tsx +50 -52
  71. package/src/services/settings.ts +5 -0
  72. package/src/styles/components.css +93 -0
  73. package/src/theme/color-themes.ts +321 -0
  74. package/src/theme/layouts/BaseLayout.tsx +61 -1
  75. package/src/theme/layouts/DashLayout.tsx +13 -2
  76. package/src/theme/layouts/index.ts +5 -1
  77. package/src/types.ts +62 -1
  78. package/src/db/migrations/0001_add_search_fts.sql +0 -40
  79. package/src/db/migrations/0002_collection_path.sql +0 -2
  80. package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
  81. package/src/db/migrations/0004_media_uuid.sql +0 -35
@@ -8,11 +8,7 @@ import type { Bindings } from "../../types.js";
8
8
  import type { AppVariables } from "../../app.js";
9
9
  import { DashLayout } from "../../theme/layouts/index.js";
10
10
  import { sse } from "../../lib/sse.js";
11
- import {
12
- getSiteName,
13
- getSiteDescription,
14
- getSiteLanguage,
15
- } from "../../lib/config.js";
11
+ import { getSiteLanguage, getConfigFallback } from "../../lib/config.js";
16
12
 
17
13
  type Env = { Bindings: Bindings; Variables: AppVariables };
18
14
 
@@ -22,12 +18,14 @@ function SettingsContent({
22
18
  siteName,
23
19
  siteDescription,
24
20
  siteLanguage,
25
- saved,
21
+ siteNameFallback,
22
+ siteDescriptionFallback,
26
23
  }: {
27
24
  siteName: string;
28
25
  siteDescription: string;
29
26
  siteLanguage: string;
30
- saved: boolean;
27
+ siteNameFallback: string;
28
+ siteDescriptionFallback: string;
31
29
  }) {
32
30
  const { t } = useLingui();
33
31
 
@@ -43,27 +41,11 @@ function SettingsContent({
43
41
  {t({ message: "Settings", comment: "@context: Dashboard heading" })}
44
42
  </h1>
45
43
 
46
- {saved && (
47
- <div
48
- id="settings-saved-toast"
49
- class="alert mb-4 max-w-lg transition-opacity duration-300"
50
- data-init={`console.log('[toast] init fired at', Date.now()); history.replaceState({}, '', '/dash/settings'); setTimeout(() => { console.log('[toast] hiding at', Date.now()); const el = document.getElementById('settings-saved-toast'); if (el) { el.style.opacity = '0'; setTimeout(() => el.remove(), 300) } }, 3000)`}
51
- >
52
- <h2>
53
- {t({
54
- message: "Settings saved successfully.",
55
- comment: "@context: Toast message after saving settings",
56
- })}
57
- </h2>
58
- </div>
59
- )}
60
-
61
44
  <div class="flex flex-col gap-6 max-w-lg">
62
45
  <form
63
46
  data-signals={generalSignals}
64
47
  data-on:submit__prevent="@post('/dash/settings')"
65
48
  >
66
- <div id="settings-message"></div>
67
49
  <div class="card">
68
50
  <header>
69
51
  <h2>
@@ -85,7 +67,7 @@ function SettingsContent({
85
67
  type="text"
86
68
  data-bind="siteName"
87
69
  class="input"
88
- required
70
+ placeholder={siteNameFallback}
89
71
  />
90
72
  </div>
91
73
 
@@ -96,7 +78,12 @@ function SettingsContent({
96
78
  comment: "@context: Settings form field",
97
79
  })}
98
80
  </label>
99
- <textarea data-bind="siteDescription" class="textarea" rows={3}>
81
+ <textarea
82
+ data-bind="siteDescription"
83
+ class="textarea"
84
+ rows={3}
85
+ placeholder={siteDescriptionFallback}
86
+ >
100
87
  {siteDescription}
101
88
  </textarea>
102
89
  </div>
@@ -135,7 +122,6 @@ function SettingsContent({
135
122
  data-signals="{currentPassword: '', newPassword: '', confirmPassword: ''}"
136
123
  data-on:submit__prevent="@post('/dash/settings/password')"
137
124
  >
138
- <div id="password-message"></div>
139
125
  <div class="card">
140
126
  <header>
141
127
  <h2>
@@ -212,23 +198,33 @@ function SettingsContent({
212
198
 
213
199
  // Settings page
214
200
  settingsRoutes.get("/", async (c) => {
215
- const siteName = await getSiteName(c);
216
- const siteDescription = await getSiteDescription(c);
201
+ const { settings } = c.var.services;
202
+
203
+ // Fetch raw DB values (null if not set)
204
+ const dbSiteName = await settings.get("SITE_NAME");
205
+ const dbSiteDescription = await settings.get("SITE_DESCRIPTION");
217
206
  const siteLanguage = await getSiteLanguage(c);
207
+
208
+ // Fallback values (ENV > Default) for placeholders
209
+ const siteNameFallback = getConfigFallback(c, "SITE_NAME");
210
+ const siteDescriptionFallback = getConfigFallback(c, "SITE_DESCRIPTION");
211
+
218
212
  const saved = c.req.query("saved") !== undefined;
219
213
 
220
214
  return c.html(
221
215
  <DashLayout
222
216
  c={c}
223
217
  title="Settings"
224
- siteName={siteName}
218
+ siteName={dbSiteName || siteNameFallback}
225
219
  currentPath="/dash/settings"
220
+ toast={saved ? { message: "Settings saved successfully." } : undefined}
226
221
  >
227
222
  <SettingsContent
228
- siteName={siteName}
229
- siteDescription={siteDescription}
223
+ siteName={dbSiteName || ""}
224
+ siteDescription={dbSiteDescription || ""}
230
225
  siteLanguage={siteLanguage}
231
- saved={saved}
226
+ siteNameFallback={siteNameFallback}
227
+ siteDescriptionFallback={siteDescriptionFallback}
232
228
  />
233
229
  </DashLayout>,
234
230
  );
@@ -242,14 +238,25 @@ settingsRoutes.post("/", async (c) => {
242
238
  siteLanguage: string;
243
239
  }>();
244
240
 
245
- const oldLanguage =
246
- (await c.var.services.settings.get("SITE_LANGUAGE")) ?? "en";
241
+ const { settings } = c.var.services;
247
242
 
248
- await c.var.services.settings.setMany({
249
- SITE_NAME: body.siteName,
250
- SITE_DESCRIPTION: body.siteDescription,
251
- SITE_LANGUAGE: body.siteLanguage,
252
- });
243
+ const oldLanguage = (await settings.get("SITE_LANGUAGE")) ?? "en";
244
+
245
+ // For text fields: empty = remove from DB (fall back to ENV > Default)
246
+ if (body.siteName.trim()) {
247
+ await settings.set("SITE_NAME", body.siteName.trim());
248
+ } else {
249
+ await settings.remove("SITE_NAME");
250
+ }
251
+
252
+ if (body.siteDescription.trim()) {
253
+ await settings.set("SITE_DESCRIPTION", body.siteDescription.trim());
254
+ } else {
255
+ await settings.remove("SITE_DESCRIPTION");
256
+ }
257
+
258
+ // Language always has a value from the select
259
+ await settings.set("SITE_LANGUAGE", body.siteLanguage);
253
260
 
254
261
  const languageChanged = oldLanguage !== body.siteLanguage;
255
262
 
@@ -258,10 +265,7 @@ settingsRoutes.post("/", async (c) => {
258
265
  // Language changed - full reload needed to update all UI text
259
266
  await stream.redirect("/dash/settings?saved");
260
267
  } else {
261
- // No language change - show inline success message
262
- await stream.patchElements(
263
- '<div id="settings-message"><div class="alert mb-4 transition-opacity duration-300" data-init="setTimeout(() => { el.style.opacity = \'0\'; setTimeout(() => el.remove(), 300) }, 3000)"><h2>Settings saved successfully.</h2></div></div>',
264
- );
268
+ await stream.toast("Settings saved successfully.");
265
269
  }
266
270
  });
267
271
  });
@@ -276,9 +280,7 @@ settingsRoutes.post("/password", async (c) => {
276
280
 
277
281
  if (body.newPassword !== body.confirmPassword) {
278
282
  return sse(c, async (stream) => {
279
- await stream.patchElements(
280
- '<div id="password-message"><div class="alert-destructive mb-4"><h2>Passwords do not match.</h2></div></div>',
281
- );
283
+ await stream.toast("Passwords do not match.", "error");
282
284
  });
283
285
  }
284
286
 
@@ -293,16 +295,12 @@ settingsRoutes.post("/password", async (c) => {
293
295
  });
294
296
  } catch {
295
297
  return sse(c, async (stream) => {
296
- await stream.patchElements(
297
- '<div id="password-message"><div class="alert-destructive mb-4"><h2>Current password is incorrect.</h2></div></div>',
298
- );
298
+ await stream.toast("Current password is incorrect.", "error");
299
299
  });
300
300
  }
301
301
 
302
302
  return sse(c, async (stream) => {
303
- await stream.patchElements(
304
- '<div id="password-message"><div class="alert mb-4"><h2>Password changed successfully.</h2></div></div>',
305
- );
303
+ await stream.toast("Password changed successfully.");
306
304
  await stream.patchSignals({
307
305
  currentPassword: "",
308
306
  newPassword: "",
@@ -19,6 +19,7 @@ export interface SettingsService {
19
19
  getAll(): Promise<Record<string, string>>;
20
20
  set(key: SettingsKey, value: string): Promise<void>;
21
21
  setMany(entries: Partial<Record<SettingsKey, string>>): Promise<void>;
22
+ remove(key: SettingsKey): Promise<void>;
22
23
  isOnboardingComplete(): Promise<boolean>;
23
24
  completeOnboarding(): Promise<void>;
24
25
  }
@@ -54,6 +55,10 @@ export function createSettingsService(db: Database): SettingsService {
54
55
  });
55
56
  },
56
57
 
58
+ async remove(key) {
59
+ await db.delete(settings).where(eq(settings.key, key));
60
+ },
61
+
57
62
  async setMany(entries) {
58
63
  const timestamp = now();
59
64
  const keys = Object.keys(entries) as SettingsKey[];
@@ -12,6 +12,32 @@
12
12
  }
13
13
  }
14
14
 
15
+ /* Alert variants */
16
+ @layer components {
17
+ .alert-success {
18
+ @apply relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current;
19
+ @apply text-success bg-card [&>svg]:text-current;
20
+
21
+ > h2,
22
+ > h3,
23
+ > h4,
24
+ > h5,
25
+ > h6,
26
+ > strong,
27
+ > [data-title] {
28
+ @apply col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight;
29
+ }
30
+
31
+ > section {
32
+ @apply text-success col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed;
33
+
34
+ ul {
35
+ @apply list-inside list-disc text-sm;
36
+ }
37
+ }
38
+ }
39
+ }
40
+
15
41
  /* Badge components */
16
42
  @layer components {
17
43
  .badge {
@@ -45,3 +71,70 @@
45
71
  color: white;
46
72
  }
47
73
  }
74
+
75
+ /* Toast notifications */
76
+ @layer components {
77
+ .toast-container {
78
+ @apply fixed top-4 right-4 z-50 flex flex-col gap-2 pointer-events-none;
79
+ }
80
+
81
+ .toast {
82
+ @apply pointer-events-auto rounded-lg border px-4 py-3 text-sm shadow-lg;
83
+ @apply flex items-start gap-2.5 min-w-64 max-w-sm;
84
+ background-color: var(--color-card);
85
+ animation: toast-in 0.3s ease-out;
86
+
87
+ > svg:first-child {
88
+ @apply size-4 shrink-0 translate-y-0.5;
89
+ }
90
+
91
+ > span {
92
+ @apply flex-1;
93
+ }
94
+ }
95
+
96
+ .toast-success {
97
+ color: var(--color-success);
98
+ }
99
+
100
+ .toast-error {
101
+ color: var(--color-destructive);
102
+ }
103
+
104
+ .toast-close {
105
+ @apply shrink-0 translate-y-0.5 cursor-pointer rounded-sm p-0 border-0 bg-transparent;
106
+ color: var(--color-muted-foreground);
107
+
108
+ &:hover {
109
+ color: var(--color-foreground);
110
+ }
111
+
112
+ > svg {
113
+ @apply size-3.5;
114
+ }
115
+ }
116
+
117
+ .toast-out {
118
+ animation: toast-out 0.3s ease-in forwards;
119
+ }
120
+ }
121
+
122
+ @keyframes toast-in {
123
+ from {
124
+ opacity: 0;
125
+ transform: translateY(-0.5rem);
126
+ }
127
+ to {
128
+ opacity: 1;
129
+ transform: translateY(0);
130
+ }
131
+ }
132
+
133
+ @keyframes toast-out {
134
+ from {
135
+ opacity: 1;
136
+ }
137
+ to {
138
+ opacity: 0;
139
+ }
140
+ }
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Built-in Color Themes
3
+ *
4
+ * Each theme defines CSS variable overrides for light and dark modes,
5
+ * plus preview colors for the theme picker UI.
6
+ */
7
+
8
+ /**
9
+ * A color theme definition with light and dark mode CSS variable overrides.
10
+ */
11
+ export interface ColorTheme {
12
+ /** Stored in DB settings, e.g. "beach" */
13
+ id: string;
14
+ /** Display name, e.g. "Beach" */
15
+ name: string;
16
+ /** CSS variable overrides for :root (light mode) */
17
+ light: Record<string, string>;
18
+ /** CSS variable overrides for .dark (dark mode) */
19
+ dark: Record<string, string>;
20
+ /** Preview colors (hex) for theme picker cards */
21
+ preview: {
22
+ lightBg: string;
23
+ lightText: string;
24
+ lightLink: string;
25
+ darkBg: string;
26
+ darkText: string;
27
+ darkLink: string;
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Create a comprehensive color theme from key colors.
33
+ * Derives card, popover, muted, secondary, accent, and sidebar variables.
34
+ */
35
+ function defineTheme(opts: {
36
+ id: string;
37
+ name: string;
38
+ preview: ColorTheme["preview"];
39
+ light: {
40
+ bg: string;
41
+ fg: string;
42
+ primary: string;
43
+ primaryFg: string;
44
+ muted: string;
45
+ mutedFg: string;
46
+ border: string;
47
+ };
48
+ dark: {
49
+ bg: string;
50
+ fg: string;
51
+ primary: string;
52
+ primaryFg: string;
53
+ muted: string;
54
+ mutedFg: string;
55
+ border: string;
56
+ };
57
+ }): ColorTheme {
58
+ const { light, dark } = opts;
59
+ return {
60
+ id: opts.id,
61
+ name: opts.name,
62
+ preview: opts.preview,
63
+ light: {
64
+ "--background": light.bg,
65
+ "--foreground": light.fg,
66
+ "--card": light.bg,
67
+ "--card-foreground": light.fg,
68
+ "--popover": light.bg,
69
+ "--popover-foreground": light.fg,
70
+ "--primary": light.primary,
71
+ "--primary-foreground": light.primaryFg,
72
+ "--secondary": light.muted,
73
+ "--secondary-foreground": light.fg,
74
+ "--muted": light.muted,
75
+ "--muted-foreground": light.mutedFg,
76
+ "--accent": light.muted,
77
+ "--accent-foreground": light.fg,
78
+ "--border": light.border,
79
+ "--input": light.border,
80
+ "--ring": light.primary,
81
+ "--sidebar": light.bg,
82
+ "--sidebar-foreground": light.fg,
83
+ "--sidebar-primary": light.primary,
84
+ "--sidebar-primary-foreground": light.primaryFg,
85
+ "--sidebar-accent": light.muted,
86
+ "--sidebar-accent-foreground": light.fg,
87
+ "--sidebar-border": light.border,
88
+ "--sidebar-ring": light.primary,
89
+ },
90
+ dark: {
91
+ "--background": dark.bg,
92
+ "--foreground": dark.fg,
93
+ "--card": dark.bg,
94
+ "--card-foreground": dark.fg,
95
+ "--popover": dark.bg,
96
+ "--popover-foreground": dark.fg,
97
+ "--primary": dark.primary,
98
+ "--primary-foreground": dark.primaryFg,
99
+ "--secondary": dark.muted,
100
+ "--secondary-foreground": dark.fg,
101
+ "--muted": dark.muted,
102
+ "--muted-foreground": dark.mutedFg,
103
+ "--accent": dark.muted,
104
+ "--accent-foreground": dark.fg,
105
+ "--border": dark.border,
106
+ "--input": dark.border,
107
+ "--ring": dark.primary,
108
+ "--sidebar": dark.bg,
109
+ "--sidebar-foreground": dark.fg,
110
+ "--sidebar-primary": dark.primary,
111
+ "--sidebar-primary-foreground": dark.primaryFg,
112
+ "--sidebar-accent": dark.muted,
113
+ "--sidebar-accent-foreground": dark.fg,
114
+ "--sidebar-border": dark.border,
115
+ "--sidebar-ring": dark.primary,
116
+ },
117
+ };
118
+ }
119
+
120
+ export const BUILTIN_COLOR_THEMES: ColorTheme[] = [
121
+ {
122
+ id: "default",
123
+ name: "Default",
124
+ light: {},
125
+ dark: {},
126
+ preview: {
127
+ lightBg: "#ffffff",
128
+ lightText: "#1e1e1e",
129
+ lightLink: "#1e1e1e",
130
+ darkBg: "#262626",
131
+ darkText: "#fafafa",
132
+ darkLink: "#eaeaea",
133
+ },
134
+ },
135
+
136
+ defineTheme({
137
+ id: "beach",
138
+ name: "Beach",
139
+ preview: {
140
+ lightBg: "#f9f3ea",
141
+ lightText: "#3d3527",
142
+ lightLink: "#2d6a59",
143
+ darkBg: "#2d4553",
144
+ darkText: "#e2d6c4",
145
+ darkLink: "#7cc5a2",
146
+ },
147
+ light: {
148
+ bg: "oklch(0.97 0.01 85)",
149
+ fg: "oklch(0.28 0.02 65)",
150
+ primary: "oklch(0.46 0.1 170)",
151
+ primaryFg: "oklch(0.98 0.005 85)",
152
+ muted: "oklch(0.93 0.015 85)",
153
+ mutedFg: "oklch(0.52 0.015 65)",
154
+ border: "oklch(0.88 0.018 85)",
155
+ },
156
+ dark: {
157
+ bg: "oklch(0.27 0.03 210)",
158
+ fg: "oklch(0.88 0.015 80)",
159
+ primary: "oklch(0.72 0.1 165)",
160
+ primaryFg: "oklch(0.22 0.03 210)",
161
+ muted: "oklch(0.33 0.025 210)",
162
+ mutedFg: "oklch(0.65 0.015 80)",
163
+ border: "oklch(0.38 0.02 210)",
164
+ },
165
+ }),
166
+
167
+ defineTheme({
168
+ id: "gameboy",
169
+ name: "Gameboy",
170
+ preview: {
171
+ lightBg: "#d3d7c0",
172
+ lightText: "#2b3326",
173
+ lightLink: "#466740",
174
+ darkBg: "#1b1f18",
175
+ darkText: "#a6b09a",
176
+ darkLink: "#6d9660",
177
+ },
178
+ light: {
179
+ bg: "oklch(0.87 0.03 130)",
180
+ fg: "oklch(0.25 0.04 140)",
181
+ primary: "oklch(0.4 0.08 145)",
182
+ primaryFg: "oklch(0.92 0.02 130)",
183
+ muted: "oklch(0.83 0.035 130)",
184
+ mutedFg: "oklch(0.48 0.03 140)",
185
+ border: "oklch(0.79 0.035 130)",
186
+ },
187
+ dark: {
188
+ bg: "oklch(0.18 0.02 140)",
189
+ fg: "oklch(0.78 0.025 130)",
190
+ primary: "oklch(0.6 0.08 145)",
191
+ primaryFg: "oklch(0.15 0.02 140)",
192
+ muted: "oklch(0.24 0.02 140)",
193
+ mutedFg: "oklch(0.58 0.02 130)",
194
+ border: "oklch(0.3 0.02 140)",
195
+ },
196
+ }),
197
+
198
+ defineTheme({
199
+ id: "grayscale",
200
+ name: "Grayscale",
201
+ preview: {
202
+ lightBg: "#efefef",
203
+ lightText: "#3a3a3a",
204
+ lightLink: "#555555",
205
+ darkBg: "#1e1e1e",
206
+ darkText: "#c8c8c8",
207
+ darkLink: "#999999",
208
+ },
209
+ light: {
210
+ bg: "oklch(0.96 0 0)",
211
+ fg: "oklch(0.3 0 0)",
212
+ primary: "oklch(0.4 0 0)",
213
+ primaryFg: "oklch(0.96 0 0)",
214
+ muted: "oklch(0.92 0 0)",
215
+ mutedFg: "oklch(0.55 0 0)",
216
+ border: "oklch(0.87 0 0)",
217
+ },
218
+ dark: {
219
+ bg: "oklch(0.18 0 0)",
220
+ fg: "oklch(0.82 0 0)",
221
+ primary: "oklch(0.7 0 0)",
222
+ primaryFg: "oklch(0.18 0 0)",
223
+ muted: "oklch(0.24 0 0)",
224
+ mutedFg: "oklch(0.6 0 0)",
225
+ border: "oklch(0.3 0 0)",
226
+ },
227
+ }),
228
+
229
+ defineTheme({
230
+ id: "halloween",
231
+ name: "Halloween",
232
+ preview: {
233
+ lightBg: "#f9f2e3",
234
+ lightText: "#352200",
235
+ lightLink: "#cc5500",
236
+ darkBg: "#1e1000",
237
+ darkText: "#dfc390",
238
+ darkLink: "#ff8c00",
239
+ },
240
+ light: {
241
+ bg: "oklch(0.97 0.015 75)",
242
+ fg: "oklch(0.25 0.04 55)",
243
+ primary: "oklch(0.6 0.2 50)",
244
+ primaryFg: "oklch(0.98 0.01 75)",
245
+ muted: "oklch(0.93 0.02 75)",
246
+ mutedFg: "oklch(0.5 0.025 55)",
247
+ border: "oklch(0.88 0.025 75)",
248
+ },
249
+ dark: {
250
+ bg: "oklch(0.16 0.03 50)",
251
+ fg: "oklch(0.85 0.025 75)",
252
+ primary: "oklch(0.72 0.19 55)",
253
+ primaryFg: "oklch(0.14 0.03 50)",
254
+ muted: "oklch(0.22 0.025 50)",
255
+ mutedFg: "oklch(0.62 0.02 75)",
256
+ border: "oklch(0.28 0.025 50)",
257
+ },
258
+ }),
259
+
260
+ defineTheme({
261
+ id: "notepad",
262
+ name: "Notepad",
263
+ preview: {
264
+ lightBg: "#fdfce8",
265
+ lightText: "#333333",
266
+ lightLink: "#2060b8",
267
+ darkBg: "#2a291a",
268
+ darkText: "#d2d2b8",
269
+ darkLink: "#6695cc",
270
+ },
271
+ light: {
272
+ bg: "oklch(0.985 0.018 95)",
273
+ fg: "oklch(0.27 0 0)",
274
+ primary: "oklch(0.5 0.17 260)",
275
+ primaryFg: "oklch(0.985 0.01 95)",
276
+ muted: "oklch(0.94 0.022 95)",
277
+ mutedFg: "oklch(0.52 0 0)",
278
+ border: "oklch(0.88 0.025 95)",
279
+ },
280
+ dark: {
281
+ bg: "oklch(0.2 0.02 90)",
282
+ fg: "oklch(0.87 0.015 95)",
283
+ primary: "oklch(0.65 0.14 260)",
284
+ primaryFg: "oklch(0.98 0.01 95)",
285
+ muted: "oklch(0.26 0.018 90)",
286
+ mutedFg: "oklch(0.62 0.012 95)",
287
+ border: "oklch(0.32 0.018 90)",
288
+ },
289
+ }),
290
+
291
+ defineTheme({
292
+ id: "sonnet",
293
+ name: "Sonnet",
294
+ preview: {
295
+ lightBg: "#f7eef5",
296
+ lightText: "#2e1e2c",
297
+ lightLink: "#9845c8",
298
+ darkBg: "#1d1428",
299
+ darkText: "#d4c2d0",
300
+ darkLink: "#c080fc",
301
+ },
302
+ light: {
303
+ bg: "oklch(0.97 0.012 325)",
304
+ fg: "oklch(0.25 0.02 310)",
305
+ primary: "oklch(0.55 0.2 300)",
306
+ primaryFg: "oklch(0.98 0.008 325)",
307
+ muted: "oklch(0.93 0.016 325)",
308
+ mutedFg: "oklch(0.52 0.015 310)",
309
+ border: "oklch(0.88 0.016 325)",
310
+ },
311
+ dark: {
312
+ bg: "oklch(0.18 0.025 300)",
313
+ fg: "oklch(0.87 0.012 325)",
314
+ primary: "oklch(0.72 0.18 300)",
315
+ primaryFg: "oklch(0.98 0.008 325)",
316
+ muted: "oklch(0.24 0.022 300)",
317
+ mutedFg: "oklch(0.62 0.012 325)",
318
+ border: "oklch(0.3 0.022 300)",
319
+ },
320
+ }),
321
+ ];