framecode 1.0.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.
@@ -0,0 +1,354 @@
1
+ import { z } from "zod";
2
+ import { bundledThemes, type ThemeRegistration } from "shiki";
3
+ import React from "react";
4
+
5
+ export const themeSchema = z.enum([
6
+ "vercel-dark",
7
+ "story-gradients",
8
+ "retro-terminal",
9
+ "github-dark",
10
+ "github-light",
11
+ "nord",
12
+ "dracula",
13
+ "monokai",
14
+ "tokyo-night",
15
+ "catppuccin-mocha",
16
+ "rose-pine",
17
+ "synthwave-84",
18
+ "material-theme-darker",
19
+ "one-dark-pro",
20
+ "vitesse-dark",
21
+ "vitesse-light",
22
+ "min-dark",
23
+ "min-light",
24
+ "night-owl",
25
+ "ayu-dark",
26
+ "gruvbox-dark-medium",
27
+ "gruvbox-light-medium",
28
+ "everforest-dark",
29
+ "everforest-light",
30
+ "kanagawa-wave",
31
+ "rose-pine-moon",
32
+ "rose-pine-dawn",
33
+ "catppuccin-latte",
34
+ "catppuccin-frappe",
35
+ "catppuccin-macchiato",
36
+ "github-dark-dimmed",
37
+ "github-dark-high-contrast",
38
+ "github-light-high-contrast",
39
+ "dark-plus",
40
+ "light-plus",
41
+ "poimandres",
42
+ "slack-dark",
43
+ "one-light",
44
+ "material-theme-lighter",
45
+ "material-theme-ocean",
46
+ "material-theme-palenight",
47
+ "dracula-soft",
48
+ "houston",
49
+ ]);
50
+
51
+ export type Theme = z.infer<typeof themeSchema>;
52
+
53
+ export const curatedThemeNames = [
54
+ "vercel-dark",
55
+ "story-gradients",
56
+ "retro-terminal",
57
+ "github-dark",
58
+ "synthwave-84",
59
+ ] as const;
60
+
61
+ type CustomThemeName =
62
+ | "vercel-dark"
63
+ | "story-gradients"
64
+ | "retro-terminal";
65
+
66
+ const customThemes: Record<CustomThemeName, ThemeRegistration> = {
67
+ "vercel-dark": {
68
+ name: "vercel-dark",
69
+ type: "dark",
70
+ semanticHighlighting: true,
71
+ colors: {
72
+ "editor.background": "#050505",
73
+ "editor.foreground": "#efefef",
74
+ "editor.selectionBackground": "#202020",
75
+ "editor.lineHighlightBackground": "#101010",
76
+ "editor.rangeHighlightBackground": "#101010",
77
+ "icon.foreground": "#ffffff",
78
+ },
79
+ tokenColors: [
80
+ { scope: ["comment"], settings: { foreground: "#666666" } },
81
+ { scope: ["keyword"], settings: { foreground: "#f5f5f5" } },
82
+ { scope: ["string"], settings: { foreground: "#d2d2d2" } },
83
+ {
84
+ scope: ["entity.name.function", "support.function"],
85
+ settings: { foreground: "#ffffff" },
86
+ },
87
+ {
88
+ scope: ["variable", "identifier"],
89
+ settings: { foreground: "#d8d8d8" },
90
+ },
91
+ { scope: ["constant.numeric"], settings: { foreground: "#cccccc" } },
92
+ {
93
+ scope: ["storage.type", "entity.name.type"],
94
+ settings: { foreground: "#e7e7e7" },
95
+ },
96
+ { scope: ["punctuation"], settings: { foreground: "#adadad" } },
97
+ ],
98
+ },
99
+ "story-gradients": {
100
+ name: "story-gradients",
101
+ type: "dark",
102
+ semanticHighlighting: true,
103
+ colors: {
104
+ "editor.background": "#14101f",
105
+ "editor.foreground": "#f7f2ff",
106
+ "editor.selectionBackground": "#2a2041",
107
+ "editor.lineHighlightBackground": "#221a34",
108
+ "editor.rangeHighlightBackground": "#221a34",
109
+ "icon.foreground": "#ffffff",
110
+ },
111
+ tokenColors: [
112
+ { scope: ["comment"], settings: { foreground: "#a68fbe" } },
113
+ { scope: ["keyword"], settings: { foreground: "#ffae57" } },
114
+ { scope: ["string"], settings: { foreground: "#ffa7d3" } },
115
+ {
116
+ scope: ["entity.name.function", "support.function"],
117
+ settings: { foreground: "#f4f6ff" },
118
+ },
119
+ {
120
+ scope: ["variable", "identifier"],
121
+ settings: { foreground: "#dbc7ff" },
122
+ },
123
+ { scope: ["constant.numeric"], settings: { foreground: "#9ee6ff" } },
124
+ {
125
+ scope: ["storage.type", "entity.name.type"],
126
+ settings: { foreground: "#ffe28f" },
127
+ },
128
+ { scope: ["punctuation"], settings: { foreground: "#cebce6" } },
129
+ ],
130
+ },
131
+ "retro-terminal": {
132
+ name: "retro-terminal",
133
+ type: "dark",
134
+ semanticHighlighting: true,
135
+ colors: {
136
+ "editor.background": "#030603",
137
+ "editor.foreground": "#80ff9a",
138
+ "editor.selectionBackground": "#113f1a",
139
+ "editor.lineHighlightBackground": "#08210e",
140
+ "editor.rangeHighlightBackground": "#08210e",
141
+ "icon.foreground": "#8effa8",
142
+ },
143
+ tokenColors: [
144
+ { scope: ["comment"], settings: { foreground: "#3f8e50" } },
145
+ { scope: ["keyword"], settings: { foreground: "#66ff85" } },
146
+ { scope: ["string"], settings: { foreground: "#b8ffc8" } },
147
+ {
148
+ scope: ["entity.name.function", "support.function"],
149
+ settings: { foreground: "#a7ffbb" },
150
+ },
151
+ {
152
+ scope: ["variable", "identifier"],
153
+ settings: { foreground: "#80ff9a" },
154
+ },
155
+ { scope: ["constant.numeric"], settings: { foreground: "#8cf5ff" } },
156
+ {
157
+ scope: ["storage.type", "entity.name.type"],
158
+ settings: { foreground: "#6cf57f" },
159
+ },
160
+ { scope: ["punctuation"], settings: { foreground: "#5de275" } },
161
+ ],
162
+ },
163
+ };
164
+
165
+ const isCustomThemeName = (themeName: Theme): themeName is CustomThemeName => {
166
+ return Object.prototype.hasOwnProperty.call(customThemes, themeName);
167
+ };
168
+
169
+ export async function loadTheme(themeName: Theme): Promise<ThemeRegistration> {
170
+ if (isCustomThemeName(themeName)) {
171
+ return customThemes[themeName];
172
+ }
173
+
174
+ const themeModule = bundledThemes[themeName as keyof typeof bundledThemes];
175
+ if (!themeModule) {
176
+ throw new Error(`Theme ${themeName} not found in Shiki`);
177
+ }
178
+ const themeData = await themeModule();
179
+ return "default" in themeData ? themeData.default : themeData;
180
+ }
181
+
182
+ export async function getThemeColors(themeName: Theme) {
183
+ const theme = await loadTheme(themeName);
184
+ const colors = theme.colors || {};
185
+ return {
186
+ background: colors["editor.background"] || "#000000",
187
+ foreground: colors["editor.foreground"] || "#ffffff",
188
+ editor: {
189
+ background: colors["editor.background"] || "#000000",
190
+ foreground: colors["editor.foreground"] || "#ffffff",
191
+ lineHighlightBackground: colors["editor.lineHighlightBackground"] || "",
192
+ rangeHighlightBackground: colors["editor.rangeHighlightBackground"] || "",
193
+ selectionBackground: colors["editor.selectionBackground"] || "",
194
+ },
195
+ icon: {
196
+ foreground:
197
+ colors["icon.foreground"] || colors["editor.foreground"] || "#ffffff",
198
+ },
199
+ };
200
+ }
201
+
202
+ export type ThemeColors = Awaited<ReturnType<typeof getThemeColors>>;
203
+
204
+ export type ThemeVisuals = {
205
+ canvasBackground: string;
206
+ canvasBackgroundImage?: string;
207
+ overlayKind: "none" | "grid" | "scanlines" | "guides";
208
+ overlayOpacity: number;
209
+ codeSurface: string;
210
+ codeBorder: string;
211
+ codeRadius: number;
212
+ codeShadow: string;
213
+ showFilename: boolean;
214
+ filenameColor: string;
215
+ progressTrack: string;
216
+ progressHeight: number;
217
+ progressGap: number;
218
+ progressTop: number;
219
+ accent: string;
220
+ };
221
+
222
+ function getDefaultThemeVisuals(themeColors: ThemeColors): ThemeVisuals {
223
+ return {
224
+ canvasBackground: themeColors.background,
225
+ overlayKind: "none",
226
+ overlayOpacity: 0,
227
+ codeSurface: "transparent",
228
+ codeBorder: "transparent",
229
+ codeRadius: 0,
230
+ codeShadow: "none",
231
+ showFilename: false,
232
+ filenameColor: themeColors.foreground,
233
+ progressTrack:
234
+ themeColors.editor.lineHighlightBackground ||
235
+ themeColors.editor.rangeHighlightBackground ||
236
+ "rgba(255, 255, 255, 0.15)",
237
+ progressHeight: 6,
238
+ progressGap: 12,
239
+ progressTop: 48,
240
+ accent: themeColors.icon.foreground,
241
+ };
242
+ }
243
+
244
+ const visualOverrides: Record<string, Partial<ThemeVisuals>> = {
245
+ "vercel-dark": {
246
+ canvasBackground: "#050505",
247
+ canvasBackgroundImage:
248
+ "radial-gradient(140% 120% at 50% 50%, #0f0f0f 0%, #070707 58%, #020202 100%)",
249
+ overlayKind: "guides",
250
+ overlayOpacity: 0.5,
251
+ codeSurface: "rgba(3, 3, 3, 0.94)",
252
+ codeBorder: "rgba(255, 255, 255, 0.14)",
253
+ codeRadius: 0,
254
+ codeShadow: "0 32px 100px rgba(0, 0, 0, 0.78)",
255
+ showFilename: false,
256
+ filenameColor: "#b7b7b7",
257
+ progressTrack: "rgba(255, 255, 255, 0.16)",
258
+ progressHeight: 3,
259
+ progressGap: 10,
260
+ progressTop: 36,
261
+ accent: "#ffffff",
262
+ },
263
+ "story-gradients": {
264
+ canvasBackground: "#120b1d",
265
+ canvasBackgroundImage:
266
+ "radial-gradient(circle at 12% 8%, rgba(255, 136, 0, 0.5), transparent 46%), radial-gradient(circle at 88% 18%, rgba(255, 0, 128, 0.45), transparent 48%), linear-gradient(135deg, #28124e 0%, #0f1f4f 100%)",
267
+ codeSurface: "rgba(8, 10, 18, 0.82)",
268
+ codeBorder: "rgba(255, 255, 255, 0.28)",
269
+ codeRadius: 20,
270
+ codeShadow: "0 18px 60px rgba(7, 4, 18, 0.5)",
271
+ progressTrack: "rgba(255, 255, 255, 0.2)",
272
+ progressHeight: 4,
273
+ progressTop: 42,
274
+ accent: "#ffffff",
275
+ },
276
+ "retro-terminal": {
277
+ canvasBackground: "#010701",
278
+ canvasBackgroundImage:
279
+ "radial-gradient(circle at 50% 50%, rgba(17, 75, 27, 0.35), transparent 75%)",
280
+ overlayKind: "scanlines",
281
+ overlayOpacity: 0.22,
282
+ codeSurface: "rgba(0, 0, 0, 0.82)",
283
+ codeBorder: "rgba(136, 255, 154, 0.35)",
284
+ codeRadius: 10,
285
+ codeShadow: "0 18px 60px rgba(0, 0, 0, 0.7)",
286
+ showFilename: true,
287
+ filenameColor: "#6cf57f",
288
+ accent: "#8effa8",
289
+ },
290
+ "github-dark": {
291
+ canvasBackground: "#0d1117",
292
+ codeSurface: "rgba(13, 17, 23, 0.92)",
293
+ codeBorder: "rgba(240, 246, 252, 0.12)",
294
+ codeRadius: 10,
295
+ codeShadow: "0 20px 70px rgba(1, 4, 9, 0.55)",
296
+ showFilename: true,
297
+ filenameColor: "#8b949e",
298
+ accent: "#58a6ff",
299
+ },
300
+ "synthwave-84": {
301
+ canvasBackground: "#241b2f",
302
+ canvasBackgroundImage:
303
+ "radial-gradient(circle at 18% 20%, rgba(255, 0, 178, 0.34), transparent 52%), radial-gradient(circle at 82% 0%, rgba(58, 255, 255, 0.24), transparent 45%), linear-gradient(180deg, #2f1f4f 0%, #10081f 100%)",
304
+ overlayKind: "grid",
305
+ overlayOpacity: 0.2,
306
+ codeSurface: "rgba(29, 23, 46, 0.86)",
307
+ codeBorder: "rgba(255, 255, 255, 0.18)",
308
+ codeRadius: 14,
309
+ codeShadow: "0 24px 74px rgba(16, 8, 31, 0.6)",
310
+ accent: "#ff7edb",
311
+ },
312
+ };
313
+
314
+ export function getThemeVisuals(
315
+ themeName: Theme,
316
+ themeColors: ThemeColors,
317
+ ): ThemeVisuals {
318
+ const defaults = getDefaultThemeVisuals(themeColors);
319
+ const override = visualOverrides[themeName];
320
+
321
+ if (!override) {
322
+ return defaults;
323
+ }
324
+
325
+ return {
326
+ ...defaults,
327
+ ...override,
328
+ };
329
+ }
330
+
331
+ export const ThemeColorsContext = React.createContext<ThemeColors | null>(null);
332
+
333
+ export const useThemeColors = () => {
334
+ const themeColors = React.useContext(ThemeColorsContext);
335
+ if (!themeColors) {
336
+ throw new Error("ThemeColorsContext not found");
337
+ }
338
+
339
+ return themeColors;
340
+ };
341
+
342
+ export const ThemeProvider = ({
343
+ children,
344
+ themeColors,
345
+ }: {
346
+ readonly children: React.ReactNode;
347
+ readonly themeColors: ThemeColors;
348
+ }) => {
349
+ return (
350
+ <ThemeColorsContext.Provider value={themeColors}>
351
+ {children}
352
+ </ThemeColorsContext.Provider>
353
+ );
354
+ };
@@ -0,0 +1,28 @@
1
+ import { Command } from "commander";
2
+ import { mkdir, writeFile } from "fs/promises";
3
+ import { dirname } from "path";
4
+ import { logger } from "../utils/logger";
5
+ import { getConfigPath } from "../utils/config";
6
+
7
+ export const initCommand = new Command("init")
8
+ .description("Create framecode config file")
9
+ .option("--local", "Create config in current directory")
10
+ .action(async (options) => {
11
+ const isLocal = options.local ?? false;
12
+ const configPath = getConfigPath(isLocal);
13
+ const schemaUrl =
14
+ "https://raw.githubusercontent.com/esau-morais/framecode/main/framecode.schema.json";
15
+
16
+ const defaultConfig = {
17
+ $schema: schemaUrl,
18
+ theme: "vercel-dark",
19
+ preset: "tutorial",
20
+ animation: "cascade",
21
+ fps: 30,
22
+ };
23
+
24
+ await mkdir(dirname(configPath), { recursive: true });
25
+ await writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
26
+
27
+ logger.success(`Created ${configPath}`);
28
+ });