@cavuno/board 1.25.0 → 1.27.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.
Files changed (49) hide show
  1. package/README.md +88 -0
  2. package/dist/bin.mjs +67 -34
  3. package/dist/filters.d.mts +55 -0
  4. package/dist/filters.d.ts +55 -0
  5. package/dist/filters.js +193 -0
  6. package/dist/filters.mjs +170 -0
  7. package/dist/format.d.mts +240 -0
  8. package/dist/format.d.ts +240 -0
  9. package/dist/format.js +453 -0
  10. package/dist/format.mjs +430 -0
  11. package/dist/index.d.mts +81 -3965
  12. package/dist/index.d.ts +81 -3965
  13. package/dist/index.js +154 -7
  14. package/dist/index.mjs +151 -4
  15. package/dist/jobs-DK5mPBgq.d.mts +3836 -0
  16. package/dist/jobs-DK5mPBgq.d.ts +3836 -0
  17. package/dist/salaries-CXt6Vkrp.d.ts +130 -0
  18. package/dist/salaries-CrJsaZe6.d.mts +130 -0
  19. package/dist/seo.d.mts +288 -0
  20. package/dist/seo.d.ts +288 -0
  21. package/dist/seo.js +1102 -0
  22. package/dist/seo.mjs +1079 -0
  23. package/dist/sitemap.d.mts +63 -0
  24. package/dist/sitemap.d.ts +63 -0
  25. package/dist/sitemap.js +353 -0
  26. package/dist/sitemap.mjs +330 -0
  27. package/dist/theme.d.mts +63 -0
  28. package/dist/theme.d.ts +63 -0
  29. package/dist/theme.js +149 -0
  30. package/dist/theme.mjs +128 -0
  31. package/package.json +57 -2
  32. package/skills/cavuno-board-account/SKILL.md +147 -0
  33. package/skills/cavuno-board-applications/SKILL.md +110 -0
  34. package/skills/cavuno-board-blog/SKILL.md +99 -0
  35. package/skills/cavuno-board-companies/SKILL.md +99 -0
  36. package/skills/cavuno-board-filters/SKILL.md +89 -0
  37. package/skills/cavuno-board-format/SKILL.md +104 -0
  38. package/skills/cavuno-board-job-alerts/SKILL.md +115 -0
  39. package/skills/cavuno-board-job-posting/SKILL.md +130 -0
  40. package/skills/cavuno-board-jobs/SKILL.md +15 -0
  41. package/skills/cavuno-board-messaging/SKILL.md +121 -0
  42. package/skills/cavuno-board-paywall/SKILL.md +108 -0
  43. package/skills/cavuno-board-salaries/SKILL.md +87 -0
  44. package/skills/cavuno-board-seo/SKILL.md +180 -0
  45. package/skills/cavuno-board-setup/SKILL.md +26 -2
  46. package/skills/cavuno-board-sitemap/SKILL.md +147 -0
  47. package/skills/cavuno-board-smoke-test/SKILL.md +84 -0
  48. package/skills/cavuno-board-theme/SKILL.md +69 -0
  49. package/skills/manifest.json +106 -1
@@ -0,0 +1,330 @@
1
+ // src/sitemap/xml.ts
2
+ var SITEMAP_BUCKETS = [
3
+ "marketing",
4
+ "jobs-categories",
5
+ "jobs-skills",
6
+ "jobs-locations",
7
+ "jobs-details",
8
+ "companies",
9
+ "salaries",
10
+ "blog"
11
+ ];
12
+ var SITEMAP_CHUNK_SIZE = 45e3;
13
+ function xmlEscape(value) {
14
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
15
+ }
16
+ function bucketFilename(bucket, chunkIndex = 0) {
17
+ const suffix = chunkIndex > 0 ? `-${chunkIndex + 1}` : "";
18
+ return `${bucket}${suffix}.xml`;
19
+ }
20
+ function parseBucketFilename(filename) {
21
+ if (!filename.endsWith(".xml")) return null;
22
+ const stem = filename.slice(0, -".xml".length);
23
+ const chunkMatch = stem.match(/^(.+?)-(\d+)$/);
24
+ if (chunkMatch) {
25
+ const candidate = chunkMatch[1];
26
+ const chunkNumber = Number.parseInt(chunkMatch[2] ?? "", 10);
27
+ if (SITEMAP_BUCKETS.includes(candidate) && Number.isFinite(chunkNumber) && chunkNumber >= 2) {
28
+ return { bucket: candidate, chunkIndex: chunkNumber - 1 };
29
+ }
30
+ }
31
+ if (SITEMAP_BUCKETS.includes(stem)) {
32
+ return { bucket: stem, chunkIndex: 0 };
33
+ }
34
+ return null;
35
+ }
36
+ function chunk(items, size) {
37
+ if (items.length === 0) return [];
38
+ if (items.length <= size) return [[...items]];
39
+ const out = [];
40
+ for (let i = 0; i < items.length; i += size) {
41
+ out.push(items.slice(i, i + size));
42
+ }
43
+ return out;
44
+ }
45
+ var XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>\n';
46
+ var SITEMAP_NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
47
+ var IMAGE_NS = "http://www.google.com/schemas/sitemap-image/1.1";
48
+ function lastmod(value) {
49
+ const serialized = value instanceof Date ? value.toISOString() : value;
50
+ return `<lastmod>${xmlEscape(serialized)}</lastmod>
51
+ `;
52
+ }
53
+ function renderUrlset(entries) {
54
+ const normalized = entries.map(
55
+ (entry) => typeof entry === "string" ? { url: entry } : entry
56
+ );
57
+ const hasImages = normalized.some((entry) => (entry.images?.length ?? 0) > 0);
58
+ const namespaces = [`xmlns="${SITEMAP_NS}"`];
59
+ if (hasImages) {
60
+ namespaces.push(`xmlns:image="${IMAGE_NS}"`);
61
+ }
62
+ let xml = XML_DECLARATION + `<urlset ${namespaces.join(" ")}>
63
+ `;
64
+ for (const entry of normalized) {
65
+ xml += "<url>\n";
66
+ xml += `<loc>${xmlEscape(entry.url)}</loc>
67
+ `;
68
+ if (entry.images?.length) {
69
+ for (const image of entry.images) {
70
+ xml += `<image:image>
71
+ <image:loc>${xmlEscape(image)}</image:loc>
72
+ </image:image>
73
+ `;
74
+ }
75
+ }
76
+ if (entry.lastModified) {
77
+ xml += lastmod(entry.lastModified);
78
+ }
79
+ xml += "</url>\n";
80
+ }
81
+ xml += "</urlset>\n";
82
+ return xml;
83
+ }
84
+ function renderSitemapIndex(entries) {
85
+ let xml = XML_DECLARATION + `<sitemapindex xmlns="${SITEMAP_NS}">
86
+ `;
87
+ for (const raw of entries) {
88
+ const entry = typeof raw === "string" ? { url: raw } : raw;
89
+ xml += "<sitemap>\n";
90
+ xml += `<loc>${xmlEscape(entry.url)}</loc>
91
+ `;
92
+ if (entry.lastModified) {
93
+ xml += lastmod(entry.lastModified);
94
+ }
95
+ xml += "</sitemap>\n";
96
+ }
97
+ xml += "</sitemapindex>\n";
98
+ return xml;
99
+ }
100
+
101
+ // src/pagination.ts
102
+ function paginate(listFn, query, options) {
103
+ async function* pages() {
104
+ let current = query;
105
+ for (; ; ) {
106
+ const page = await listFn(current, options);
107
+ yield page;
108
+ if (!page.hasMore || page.nextCursor === null) return;
109
+ const { offset: _offset, ...rest } = current ?? {};
110
+ current = { ...rest, cursor: page.nextCursor };
111
+ }
112
+ }
113
+ return {
114
+ pages,
115
+ async *[Symbol.asyncIterator]() {
116
+ for await (const page of pages()) {
117
+ yield* page.data;
118
+ }
119
+ },
120
+ async toArray({ limit }) {
121
+ const items = [];
122
+ for await (const page of pages()) {
123
+ items.push(...page.data);
124
+ if (items.length >= limit) break;
125
+ }
126
+ return items.slice(0, limit);
127
+ }
128
+ };
129
+ }
130
+
131
+ // src/sitemap/walker.ts
132
+ var MIN_JOBS_PER_INDEXED_PAGE = 5;
133
+ var MAX_PAGES = 200;
134
+ async function drainPages(pages) {
135
+ const acc = [];
136
+ let pageCount = 0;
137
+ for await (const page of pages) {
138
+ acc.push(...page.data);
139
+ pageCount += 1;
140
+ if (pageCount >= MAX_PAGES) {
141
+ if (page.hasMore) {
142
+ console.warn(
143
+ `[sitemap] hit the ${MAX_PAGES}-page pagination cap \u2014 output may be truncated`
144
+ );
145
+ }
146
+ break;
147
+ }
148
+ }
149
+ return acc;
150
+ }
151
+ var PAGE = 100;
152
+ var OFFSET_CONCURRENCY = 8;
153
+ var OFFSET_CEILING = 1e4;
154
+ async function enumerateByOffset(fetchAt, fallback) {
155
+ const first = await fetchAt(0);
156
+ if (first.count === void 0) return fallback();
157
+ const all = [...first.data];
158
+ const ceiling = Math.min(first.count, OFFSET_CEILING);
159
+ const offsets = [];
160
+ for (let o = PAGE; o < ceiling; o += PAGE) offsets.push(o);
161
+ for (let i = 0; i < offsets.length; i += OFFSET_CONCURRENCY) {
162
+ const pages = await Promise.all(
163
+ offsets.slice(i, i + OFFSET_CONCURRENCY).map(fetchAt)
164
+ );
165
+ for (const page of pages) all.push(...page.data);
166
+ }
167
+ if (first.count > OFFSET_CEILING) {
168
+ console.warn(
169
+ `[sitemap] count ${first.count} exceeds the ${OFFSET_CEILING} offset ceiling \u2014 tail not enumerated (needs cursor/bulk support).`
170
+ );
171
+ }
172
+ return all;
173
+ }
174
+ function enumerateJobs(board) {
175
+ return enumerateByOffset(
176
+ (offset) => board.jobs.list({ limit: PAGE, offset }),
177
+ () => drainPages(paginate(board.jobs.list, { limit: PAGE }).pages())
178
+ );
179
+ }
180
+ function enumerateCompanies(board) {
181
+ return enumerateByOffset(
182
+ (offset) => board.companies.list({ limit: PAGE, offset }),
183
+ () => drainPages(paginate(board.companies.list, { limit: PAGE }).pages())
184
+ );
185
+ }
186
+ async function listedBuckets(board) {
187
+ const { features } = await board.context();
188
+ return SITEMAP_BUCKETS.filter((b) => b !== "blog" || features.blog);
189
+ }
190
+ async function buildBucketUrls(board, origin, bucket) {
191
+ switch (bucket) {
192
+ case "marketing":
193
+ return marketing(board, origin);
194
+ case "jobs-categories":
195
+ return jobsTaxonomy(board, origin, "categories");
196
+ case "jobs-skills":
197
+ return jobsTaxonomy(board, origin, "skills");
198
+ case "jobs-locations":
199
+ return jobsLocations(board, origin);
200
+ case "jobs-details":
201
+ return jobDetails(board, origin);
202
+ case "companies":
203
+ return companies(board, origin);
204
+ case "salaries":
205
+ return salaries(board, origin);
206
+ case "blog":
207
+ return blog(board, origin);
208
+ }
209
+ }
210
+ async function marketing(board, origin) {
211
+ const { features } = await board.context();
212
+ const urls = [
213
+ `${origin}/`,
214
+ `${origin}/jobs`,
215
+ `${origin}/about`,
216
+ `${origin}/privacy-policy`,
217
+ `${origin}/terms-of-service`,
218
+ `${origin}/cookie-policy`
219
+ ];
220
+ if (features.impressum) urls.push(`${origin}/impressum`);
221
+ if (features.talentDirectory) urls.push(`${origin}/talent`);
222
+ if (features.employers) urls.push(`${origin}/employers`);
223
+ return urls;
224
+ }
225
+ async function jobsTaxonomy(board, origin, kind) {
226
+ const jobs = await enumerateJobs(board);
227
+ const counts = /* @__PURE__ */ new Map();
228
+ for (const job of jobs) {
229
+ const slugs = new Set(
230
+ (kind === "categories" ? job.categories : job.skills).map((t) => t.slug)
231
+ );
232
+ for (const slug of slugs) counts.set(slug, (counts.get(slug) ?? 0) + 1);
233
+ }
234
+ const prefix = kind === "categories" ? `${origin}/jobs` : `${origin}/jobs/skills`;
235
+ return [...counts.entries()].filter(([, n]) => n >= MIN_JOBS_PER_INDEXED_PAGE).map(([slug]) => slug).sort().map((slug) => `${prefix}/${slug}`);
236
+ }
237
+ async function jobsLocations(board, origin) {
238
+ const { language } = await board.context();
239
+ const { data } = await board.salaries.locations.list({ locale: language });
240
+ return data.filter((l) => l.jobCount >= MIN_JOBS_PER_INDEXED_PAGE).map((l) => l.placeSlug).sort().map((slug) => `${origin}/jobs/locations/${slug}`);
241
+ }
242
+ async function jobDetails(board, origin) {
243
+ const jobs = await enumerateJobs(board);
244
+ const seen = /* @__PURE__ */ new Set();
245
+ for (const job of jobs) {
246
+ if (!job.company?.slug || !job.slug) continue;
247
+ seen.add(`${origin}/companies/${job.company.slug}/jobs/${job.slug}`);
248
+ }
249
+ return [...seen].sort();
250
+ }
251
+ async function companies(board, origin) {
252
+ const [list, markets] = await Promise.all([
253
+ enumerateCompanies(board),
254
+ board.companies.markets()
255
+ ]);
256
+ const urls = [`${origin}/companies`];
257
+ for (const company of list) urls.push(`${origin}/companies/${company.slug}`);
258
+ for (const market of markets.data) {
259
+ urls.push(`${origin}/companies/markets/${market.slug}`);
260
+ }
261
+ return urls;
262
+ }
263
+ async function salaries(board, origin) {
264
+ const { language } = await board.context();
265
+ const [salaryCompanies, titles, skills, locations] = await Promise.all([
266
+ board.salaries.companies.list(),
267
+ board.salaries.titles.list({ locale: language }),
268
+ board.salaries.skills.list({ locale: language }),
269
+ board.salaries.locations.list({ locale: language })
270
+ ]);
271
+ if (!salaryCompanies.data.length && !titles.data.length && !skills.data.length && !locations.data.length) {
272
+ return [];
273
+ }
274
+ const urls = [`${origin}/salaries`];
275
+ if (salaryCompanies.data.length) {
276
+ urls.push(`${origin}/salaries/companies`);
277
+ for (const c of salaryCompanies.data) {
278
+ urls.push(`${origin}/companies/${c.companySlug}/salaries`);
279
+ }
280
+ }
281
+ if (titles.data.length) {
282
+ urls.push(`${origin}/salaries/titles`);
283
+ for (const t of titles.data)
284
+ urls.push(`${origin}/salaries/titles/${t.slug}`);
285
+ }
286
+ if (skills.data.length) {
287
+ urls.push(`${origin}/salaries/skills`);
288
+ for (const s of skills.data)
289
+ urls.push(`${origin}/salaries/skills/${s.slug}`);
290
+ }
291
+ if (locations.data.length) {
292
+ urls.push(`${origin}/salaries/locations`);
293
+ for (const l of locations.data) {
294
+ urls.push(`${origin}/salaries/locations/${l.placeSlug}`);
295
+ }
296
+ }
297
+ return urls;
298
+ }
299
+ async function blog(board, origin) {
300
+ const posts = await drainPages(
301
+ paginate(board.blog.posts.list, { limit: 100 }).pages()
302
+ );
303
+ const urls = [`${origin}/blog`];
304
+ const tagSlugs = /* @__PURE__ */ new Set();
305
+ const authorSlugs = /* @__PURE__ */ new Set();
306
+ for (const post of posts) {
307
+ urls.push(`${origin}/blog/${post.slug}`);
308
+ for (const tag of post.tags) tagSlugs.add(tag.slug);
309
+ for (const author of post.authors) authorSlugs.add(author.slug);
310
+ }
311
+ for (const slug of [...tagSlugs].sort())
312
+ urls.push(`${origin}/blog/tag/${slug}`);
313
+ for (const slug of [...authorSlugs].sort()) {
314
+ urls.push(`${origin}/blog/author/${slug}`);
315
+ }
316
+ return urls;
317
+ }
318
+ export {
319
+ MIN_JOBS_PER_INDEXED_PAGE,
320
+ SITEMAP_BUCKETS,
321
+ SITEMAP_CHUNK_SIZE,
322
+ bucketFilename,
323
+ buildBucketUrls,
324
+ chunk,
325
+ listedBuckets,
326
+ parseBucketFilename,
327
+ renderSitemapIndex,
328
+ renderUrlset,
329
+ xmlEscape
330
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `@cavuno/board/theme` — board theme → shadcn CSS-variable overrides
3
+ * (ADR-0057).
4
+ *
5
+ * The board's stored theme (16 semantic colors × light/dark from
6
+ * `board.context().theme`) is mapped onto the canonical shadcn token
7
+ * vocabulary and emitted as overrides of the app's theme block. One
8
+ * contract, two consumers: the dashboard edits the board theme, and agents
9
+ * restyle through the standard shadcn theme file — both end up in the same
10
+ * tokens. The hosted board renders the same 16 source colors through its
11
+ * own token system; a coverage golden in-monorepo asserts neither side
12
+ * silently drops a color key.
13
+ *
14
+ * A null theme emits nothing — the app's static default theme applies
15
+ * untouched.
16
+ */
17
+ /** The 16 board color keys (the wire ships them as an open record). */
18
+ declare const BOARD_COLOR_KEYS: readonly ["brandColor", "buttonPrimary", "buttonPrimaryText", "buttonDanger", "background", "surfaceBackground", "mutedBackground", "highlightBackground", "text", "textSubtle", "textMuted", "textDisabled", "textError", "border", "contrastBackground", "contrastText"];
19
+ type BoardColorKey = (typeof BOARD_COLOR_KEYS)[number];
20
+ /**
21
+ * Structural input — satisfied by the SDK's `PublicBoardTheme` (whose color
22
+ * records are open on the wire) and any narrowed server copy.
23
+ */
24
+ interface ThemeInput {
25
+ mode: string;
26
+ schemeId: string;
27
+ typography: {
28
+ fontSans: string;
29
+ fontHeading?: string | null;
30
+ };
31
+ colors: {
32
+ light?: Record<string, unknown>;
33
+ dark?: Record<string, unknown>;
34
+ };
35
+ }
36
+ /**
37
+ * Render the board theme as `:root` (+ `.dark`) CSS-variable overrides.
38
+ * Inject once at the app shell, after the static theme stylesheet.
39
+ *
40
+ * @example
41
+ * const css = boardThemeToCss((await board.context()).theme);
42
+ */
43
+ declare function boardThemeToCss(theme: ThemeInput | null): string;
44
+ /** The board's color-scheme preference; unknown/absent → `system`. */
45
+ declare function themeMode(theme: ThemeInput | null): 'light' | 'dark' | 'system';
46
+ /**
47
+ * Theme font key → Google Fonts family slug, transcribed from the hosted
48
+ * board's `theme-fonts-metadata.ts` (golden-tested against it). The wire's
49
+ * `typography.fontSans` is an internal KEY (`'source-sans-pro'`), and the
50
+ * Google family is not always a re-casing of it (`source-sans-pro` →
51
+ * `Source+Sans+3`) — never build a font request from the raw key.
52
+ */
53
+ declare const THEME_FONT_GOOGLE_FAMILIES: Record<string, string>;
54
+ /**
55
+ * The CSS `font-family` name the Google stylesheet registers for a font key
56
+ * — the name `--font-sans` must reference for the loaded font to apply
57
+ * (e.g. `'source-sans-pro'` → `"Source Sans 3"`).
58
+ */
59
+ declare function themeFontFamily(fontKey: string): string;
60
+ /** One Google Fonts request covering the sans + heading families. */
61
+ declare function googleFontsUrl(theme: ThemeInput | null): string | null;
62
+
63
+ export { BOARD_COLOR_KEYS, type BoardColorKey, THEME_FONT_GOOGLE_FAMILIES, type ThemeInput, boardThemeToCss, googleFontsUrl, themeFontFamily, themeMode };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `@cavuno/board/theme` — board theme → shadcn CSS-variable overrides
3
+ * (ADR-0057).
4
+ *
5
+ * The board's stored theme (16 semantic colors × light/dark from
6
+ * `board.context().theme`) is mapped onto the canonical shadcn token
7
+ * vocabulary and emitted as overrides of the app's theme block. One
8
+ * contract, two consumers: the dashboard edits the board theme, and agents
9
+ * restyle through the standard shadcn theme file — both end up in the same
10
+ * tokens. The hosted board renders the same 16 source colors through its
11
+ * own token system; a coverage golden in-monorepo asserts neither side
12
+ * silently drops a color key.
13
+ *
14
+ * A null theme emits nothing — the app's static default theme applies
15
+ * untouched.
16
+ */
17
+ /** The 16 board color keys (the wire ships them as an open record). */
18
+ declare const BOARD_COLOR_KEYS: readonly ["brandColor", "buttonPrimary", "buttonPrimaryText", "buttonDanger", "background", "surfaceBackground", "mutedBackground", "highlightBackground", "text", "textSubtle", "textMuted", "textDisabled", "textError", "border", "contrastBackground", "contrastText"];
19
+ type BoardColorKey = (typeof BOARD_COLOR_KEYS)[number];
20
+ /**
21
+ * Structural input — satisfied by the SDK's `PublicBoardTheme` (whose color
22
+ * records are open on the wire) and any narrowed server copy.
23
+ */
24
+ interface ThemeInput {
25
+ mode: string;
26
+ schemeId: string;
27
+ typography: {
28
+ fontSans: string;
29
+ fontHeading?: string | null;
30
+ };
31
+ colors: {
32
+ light?: Record<string, unknown>;
33
+ dark?: Record<string, unknown>;
34
+ };
35
+ }
36
+ /**
37
+ * Render the board theme as `:root` (+ `.dark`) CSS-variable overrides.
38
+ * Inject once at the app shell, after the static theme stylesheet.
39
+ *
40
+ * @example
41
+ * const css = boardThemeToCss((await board.context()).theme);
42
+ */
43
+ declare function boardThemeToCss(theme: ThemeInput | null): string;
44
+ /** The board's color-scheme preference; unknown/absent → `system`. */
45
+ declare function themeMode(theme: ThemeInput | null): 'light' | 'dark' | 'system';
46
+ /**
47
+ * Theme font key → Google Fonts family slug, transcribed from the hosted
48
+ * board's `theme-fonts-metadata.ts` (golden-tested against it). The wire's
49
+ * `typography.fontSans` is an internal KEY (`'source-sans-pro'`), and the
50
+ * Google family is not always a re-casing of it (`source-sans-pro` →
51
+ * `Source+Sans+3`) — never build a font request from the raw key.
52
+ */
53
+ declare const THEME_FONT_GOOGLE_FAMILIES: Record<string, string>;
54
+ /**
55
+ * The CSS `font-family` name the Google stylesheet registers for a font key
56
+ * — the name `--font-sans` must reference for the loaded font to apply
57
+ * (e.g. `'source-sans-pro'` → `"Source Sans 3"`).
58
+ */
59
+ declare function themeFontFamily(fontKey: string): string;
60
+ /** One Google Fonts request covering the sans + heading families. */
61
+ declare function googleFontsUrl(theme: ThemeInput | null): string | null;
62
+
63
+ export { BOARD_COLOR_KEYS, type BoardColorKey, THEME_FONT_GOOGLE_FAMILIES, type ThemeInput, boardThemeToCss, googleFontsUrl, themeFontFamily, themeMode };
package/dist/theme.js ADDED
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/theme/index.ts
21
+ var theme_exports = {};
22
+ __export(theme_exports, {
23
+ BOARD_COLOR_KEYS: () => BOARD_COLOR_KEYS,
24
+ THEME_FONT_GOOGLE_FAMILIES: () => THEME_FONT_GOOGLE_FAMILIES,
25
+ boardThemeToCss: () => boardThemeToCss,
26
+ googleFontsUrl: () => googleFontsUrl,
27
+ themeFontFamily: () => themeFontFamily,
28
+ themeMode: () => themeMode
29
+ });
30
+ module.exports = __toCommonJS(theme_exports);
31
+ var BOARD_COLOR_KEYS = [
32
+ "brandColor",
33
+ "buttonPrimary",
34
+ "buttonPrimaryText",
35
+ "buttonDanger",
36
+ "background",
37
+ "surfaceBackground",
38
+ "mutedBackground",
39
+ "highlightBackground",
40
+ "text",
41
+ "textSubtle",
42
+ "textMuted",
43
+ "textDisabled",
44
+ "textError",
45
+ "border",
46
+ "contrastBackground",
47
+ "contrastText"
48
+ ];
49
+ function color(colors, key) {
50
+ const value = colors[key];
51
+ return typeof value === "string" && value ? value : void 0;
52
+ }
53
+ function tokenLines(colors) {
54
+ const lines = [];
55
+ const set = (token, value) => {
56
+ if (value) lines.push(` --${token}: ${value};`);
57
+ };
58
+ set("background", color(colors, "background"));
59
+ set("foreground", color(colors, "text"));
60
+ set("card", color(colors, "surfaceBackground"));
61
+ set("card-foreground", color(colors, "text"));
62
+ set("popover", color(colors, "surfaceBackground"));
63
+ set("popover-foreground", color(colors, "text"));
64
+ set("primary", color(colors, "buttonPrimary"));
65
+ set("primary-foreground", color(colors, "buttonPrimaryText"));
66
+ set("secondary", color(colors, "mutedBackground"));
67
+ set("secondary-foreground", color(colors, "text"));
68
+ set("muted", color(colors, "mutedBackground"));
69
+ set("muted-foreground", color(colors, "textMuted"));
70
+ set("accent", color(colors, "highlightBackground"));
71
+ set("accent-foreground", color(colors, "text"));
72
+ set(
73
+ "destructive",
74
+ color(colors, "buttonDanger") ?? color(colors, "textError")
75
+ );
76
+ set("border", color(colors, "border"));
77
+ set("input", color(colors, "border"));
78
+ set("ring", color(colors, "brandColor") ?? color(colors, "buttonPrimary"));
79
+ set("contrast-background", color(colors, "contrastBackground"));
80
+ set("contrast-foreground", color(colors, "contrastText"));
81
+ set("foreground-subtle", color(colors, "textSubtle"));
82
+ set("foreground-disabled", color(colors, "textDisabled"));
83
+ set("foreground-error", color(colors, "textError"));
84
+ return lines;
85
+ }
86
+ function boardThemeToCss(theme) {
87
+ if (!theme) return "";
88
+ const light = tokenLines(theme.colors.light ?? {});
89
+ const dark = tokenLines(theme.colors.dark ?? {});
90
+ const fontSans = theme.typography?.fontSans;
91
+ if (fontSans) {
92
+ light.push(
93
+ ` --font-sans: '${themeFontFamily(fontSans)}', ui-sans-serif, system-ui, sans-serif;`
94
+ );
95
+ }
96
+ const blocks = [];
97
+ if (light.length > 0) blocks.push(`:root {
98
+ ${light.join("\n")}
99
+ }`);
100
+ if (dark.length > 0) blocks.push(`.dark {
101
+ ${dark.join("\n")}
102
+ }`);
103
+ return blocks.join("\n\n");
104
+ }
105
+ function themeMode(theme) {
106
+ if (!theme) return "system";
107
+ return theme.mode === "light" || theme.mode === "dark" ? theme.mode : "system";
108
+ }
109
+ var THEME_FONT_GOOGLE_FAMILIES = {
110
+ "be-vietnam-pro": "Be+Vietnam+Pro",
111
+ inter: "Inter",
112
+ "plus-jakarta-sans": "Plus+Jakarta+Sans",
113
+ "dm-sans": "DM+Sans",
114
+ "public-sans": "Public+Sans",
115
+ figtree: "Figtree",
116
+ "work-sans": "Work+Sans",
117
+ "open-sans": "Open+Sans",
118
+ poppins: "Poppins",
119
+ hind: "Hind",
120
+ lexend: "Lexend",
121
+ "fira-sans": "Fira+Sans",
122
+ manrope: "Manrope",
123
+ "source-sans-pro": "Source+Sans+3",
124
+ outfit: "Outfit",
125
+ "space-grotesk": "Space+Grotesk",
126
+ geist: "Geist",
127
+ "source-serif-4": "Source+Serif+4",
128
+ lora: "Lora",
129
+ "crimson-pro": "Crimson+Pro"
130
+ };
131
+ function googleFamily(fontKey) {
132
+ return THEME_FONT_GOOGLE_FAMILIES[fontKey] ?? encodeURIComponent(fontKey).replaceAll("%20", "+");
133
+ }
134
+ function themeFontFamily(fontKey) {
135
+ return googleFamily(fontKey).replaceAll("+", " ");
136
+ }
137
+ function googleFontsUrl(theme) {
138
+ if (!theme) return null;
139
+ const families = /* @__PURE__ */ new Set();
140
+ if (theme.typography?.fontSans) {
141
+ families.add(googleFamily(theme.typography.fontSans));
142
+ }
143
+ if (theme.typography?.fontHeading) {
144
+ families.add(googleFamily(theme.typography.fontHeading));
145
+ }
146
+ if (families.size === 0) return null;
147
+ const params = [...families].map((f) => `family=${f}:wght@400;500;600;700`).join("&");
148
+ return `https://fonts.googleapis.com/css2?${params}&display=swap`;
149
+ }