@vosjs/shared 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vosso
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @vosso/shared
2
+
3
+ Small, dependency-free helpers and types shared across the vosso apps, the
4
+ studio's document model and the vos CLI plugin. Consumed in-source and bundled
5
+ into [`@vosso/vos-plugin`](../vos-plugin). MIT.
6
+
7
+ ## Exports
8
+
9
+ ```ts
10
+ import { formatLabel, generateId, sleep, safeJsonParse } from '@vosso/shared'
11
+ ```
12
+
13
+ | Utility | Description |
14
+ | ------------------------------- | -------------------------------------------------------------------- |
15
+ | `formatLabel(filename)` | Turn a slug/filename into a title (`'basic-fade'` → `'Basic Fade'`). |
16
+ | `generateId()` | A random UUID (`crypto.randomUUID`). |
17
+ | `sleep(ms)` | Promise that resolves after `ms`. |
18
+ | `safeJsonParse(json, fallback)` | Parse JSON, returning `fallback` on error. |
19
+
20
+ Subpaths: `@vosso/shared/types` and `@vosso/shared/utils`.
21
+
22
+ Keep this package lean — it's the lowest layer, so anything with a heavier
23
+ dependency or a product opinion belongs in `@vosso/studio-core` or the app.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Signup attribution (first touch), pure.
3
+ *
4
+ * PostHog already sees every referrer and utm param, but nothing first-party
5
+ * survives to the `user` row, and a first touch is the one fact that can
6
+ * never be backfilled. The seam is deliberately tiny: a write-once cookie set
7
+ * by the web worker on the first arrival that carries a signal (an external
8
+ * referrer or utm params — a direct visit sets nothing), read exactly once
9
+ * when BetterAuth creates the user, then stamped as JSON on
10
+ * `user.acquisition` and never updated.
11
+ *
12
+ * Everything here is pure so the platform's cookie writer and its signup
13
+ * reader can never disagree on the format.
14
+ */
15
+ declare const FIRST_TOUCH_COOKIE = "vosso_ft";
16
+ /** 30 days: long enough to span consider-then-signup, short enough to stay a
17
+ * first touch rather than a biography. */
18
+ declare const FIRST_TOUCH_MAX_AGE: number;
19
+ interface FirstTouch {
20
+ /** utm_source, or the referring domain when the arrival was untagged. */
21
+ source: string;
22
+ /** utm_medium, or 'referral' when derived from a bare referrer. */
23
+ medium?: string;
24
+ /** utm_campaign, verbatim. */
25
+ campaign?: string;
26
+ /** External referring domain, when there was one. */
27
+ referrer?: string;
28
+ /** The path the visit landed on. */
29
+ landing?: string;
30
+ /** ISO timestamp of the first touch. */
31
+ at?: string;
32
+ }
33
+ /**
34
+ * Derive a first touch from a landing request, or null when the visit
35
+ * carries no acquisition signal. Null is the common case and the point:
36
+ * a direct visit gets no cookie, and an absent `user.acquisition` reads
37
+ * honestly as "direct or before the feature", never as a guessed channel.
38
+ */
39
+ declare function firstTouchOf(input: {
40
+ url: URL;
41
+ referer: string | null | undefined;
42
+ }): FirstTouch | null;
43
+ /** Cookie-safe encoding of a first touch. */
44
+ declare function encodeFirstTouch(touch: FirstTouch): string;
45
+ /**
46
+ * Fail-closed parse of first-touch JSON (the shape `user.acquisition`
47
+ * stores): anything oversized, unparseable, or missing a string `source`
48
+ * is null.
49
+ */
50
+ declare function parseFirstTouchJson(raw: string | null | undefined): FirstTouch | null;
51
+ /**
52
+ * Fail-closed cookie decode. The cookie arrives from the wild — a browser
53
+ * extension or a hand-edited jar can put anything under our name.
54
+ */
55
+ declare function decodeFirstTouch(raw: string | null | undefined): FirstTouch | null;
56
+ /** Read one cookie's raw value out of a Cookie header. */
57
+ declare function readCookieValue(header: string | null | undefined, name: string): string | undefined;
58
+
59
+ export { FIRST_TOUCH_COOKIE, FIRST_TOUCH_MAX_AGE, type FirstTouch, decodeFirstTouch, encodeFirstTouch, firstTouchOf, parseFirstTouchJson, readCookieValue };
@@ -0,0 +1,92 @@
1
+ // src/acquisition.ts
2
+ var FIRST_TOUCH_COOKIE = "vosso_ft";
3
+ var FIRST_TOUCH_MAX_AGE = 60 * 60 * 24 * 30;
4
+ var FIELD_MAX = 200;
5
+ var RAW_MAX = 2e3;
6
+ function clean(value) {
7
+ const v = value?.trim().slice(0, FIELD_MAX);
8
+ return v || void 0;
9
+ }
10
+ function sameSite(a, b) {
11
+ const strip = (h) => h.startsWith("www.") ? h.slice(4) : h;
12
+ return strip(a) === strip(b);
13
+ }
14
+ function firstTouchOf(input) {
15
+ const { url, referer } = input;
16
+ const source = clean(url.searchParams.get("utm_source"));
17
+ const medium = clean(url.searchParams.get("utm_medium"));
18
+ const campaign = clean(url.searchParams.get("utm_campaign"));
19
+ let referrerDomain;
20
+ if (referer) {
21
+ try {
22
+ const r = new URL(referer);
23
+ if (r.hostname && !sameSite(r.hostname, url.hostname)) {
24
+ referrerDomain = clean(r.hostname);
25
+ }
26
+ } catch {
27
+ }
28
+ }
29
+ if (!source && !campaign && !referrerDomain) return null;
30
+ const touch = {
31
+ source: source ?? referrerDomain ?? "unknown"
32
+ };
33
+ const derivedMedium = medium ?? (!source && referrerDomain ? "referral" : void 0);
34
+ if (derivedMedium) touch.medium = derivedMedium;
35
+ if (campaign) touch.campaign = campaign;
36
+ if (referrerDomain) touch.referrer = referrerDomain;
37
+ return touch;
38
+ }
39
+ function encodeFirstTouch(touch) {
40
+ return encodeURIComponent(JSON.stringify(touch));
41
+ }
42
+ function parseFirstTouchJson(raw) {
43
+ if (!raw || raw.length > RAW_MAX) return null;
44
+ try {
45
+ const parsed = JSON.parse(raw);
46
+ if (typeof parsed !== "object" || parsed === null) return null;
47
+ const record = parsed;
48
+ if (typeof record.source !== "string" || !record.source) return null;
49
+ const touch = { source: record.source.slice(0, FIELD_MAX) };
50
+ for (const key of [
51
+ "medium",
52
+ "campaign",
53
+ "referrer",
54
+ "landing",
55
+ "at"
56
+ ]) {
57
+ const value = record[key];
58
+ if (typeof value === "string" && value)
59
+ touch[key] = value.slice(0, FIELD_MAX);
60
+ }
61
+ return touch;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ function decodeFirstTouch(raw) {
67
+ if (!raw || raw.length > RAW_MAX) return null;
68
+ try {
69
+ return parseFirstTouchJson(decodeURIComponent(raw));
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ function readCookieValue(header, name) {
75
+ if (!header) return void 0;
76
+ for (const part of header.split(";")) {
77
+ const eq = part.indexOf("=");
78
+ if (eq === -1) continue;
79
+ if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim();
80
+ }
81
+ return void 0;
82
+ }
83
+ export {
84
+ FIRST_TOUCH_COOKIE,
85
+ FIRST_TOUCH_MAX_AGE,
86
+ decodeFirstTouch,
87
+ encodeFirstTouch,
88
+ firstTouchOf,
89
+ parseFirstTouchJson,
90
+ readCookieValue
91
+ };
92
+ //# sourceMappingURL=acquisition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/acquisition.ts"],"sourcesContent":["/**\n * Signup attribution (first touch), pure.\n *\n * PostHog already sees every referrer and utm param, but nothing first-party\n * survives to the `user` row, and a first touch is the one fact that can\n * never be backfilled. The seam is deliberately tiny: a write-once cookie set\n * by the web worker on the first arrival that carries a signal (an external\n * referrer or utm params — a direct visit sets nothing), read exactly once\n * when BetterAuth creates the user, then stamped as JSON on\n * `user.acquisition` and never updated.\n *\n * Everything here is pure so the platform's cookie writer and its signup\n * reader can never disagree on the format.\n */\n\nexport const FIRST_TOUCH_COOKIE = 'vosso_ft'\n\n/** 30 days: long enough to span consider-then-signup, short enough to stay a\n * first touch rather than a biography. */\nexport const FIRST_TOUCH_MAX_AGE = 60 * 60 * 24 * 30\n\n/** Per-field cap. UTM values are short by convention; anything longer is\n * noise or abuse and gets truncated rather than refused. */\nconst FIELD_MAX = 200\n\n/** Decode guard: a cookie bigger than this is not ours. */\nconst RAW_MAX = 2000\n\nexport interface FirstTouch {\n /** utm_source, or the referring domain when the arrival was untagged. */\n source: string\n /** utm_medium, or 'referral' when derived from a bare referrer. */\n medium?: string\n /** utm_campaign, verbatim. */\n campaign?: string\n /** External referring domain, when there was one. */\n referrer?: string\n /** The path the visit landed on. */\n landing?: string\n /** ISO timestamp of the first touch. */\n at?: string\n}\n\nfunction clean(value: string | null | undefined): string | undefined {\n const v = value?.trim().slice(0, FIELD_MAX)\n return v || undefined\n}\n\n/**\n * Is the referrer an internal navigation rather than an arrival? Compares\n * registrable-host-ish: `www.vos.so` referring to `vos.so` is the same site\n * (canonicalHost 301s www onto the apex, so the second request's referrer is\n * our own www host and must not read as a referral).\n */\nfunction sameSite(a: string, b: string): boolean {\n const strip = (h: string) => (h.startsWith('www.') ? h.slice(4) : h)\n return strip(a) === strip(b)\n}\n\n/**\n * Derive a first touch from a landing request, or null when the visit\n * carries no acquisition signal. Null is the common case and the point:\n * a direct visit gets no cookie, and an absent `user.acquisition` reads\n * honestly as \"direct or before the feature\", never as a guessed channel.\n */\nexport function firstTouchOf(input: {\n url: URL\n referer: string | null | undefined\n}): FirstTouch | null {\n const { url, referer } = input\n const source = clean(url.searchParams.get('utm_source'))\n const medium = clean(url.searchParams.get('utm_medium'))\n const campaign = clean(url.searchParams.get('utm_campaign'))\n\n let referrerDomain: string | undefined\n if (referer) {\n try {\n const r = new URL(referer)\n if (r.hostname && !sameSite(r.hostname, url.hostname)) {\n referrerDomain = clean(r.hostname)\n }\n } catch {\n // An unparseable referrer is no signal.\n }\n }\n\n if (!source && !campaign && !referrerDomain) return null\n\n const touch: FirstTouch = {\n source: source ?? referrerDomain ?? 'unknown',\n }\n const derivedMedium =\n medium ?? (!source && referrerDomain ? 'referral' : undefined)\n if (derivedMedium) touch.medium = derivedMedium\n if (campaign) touch.campaign = campaign\n if (referrerDomain) touch.referrer = referrerDomain\n return touch\n}\n\n/** Cookie-safe encoding of a first touch. */\nexport function encodeFirstTouch(touch: FirstTouch): string {\n return encodeURIComponent(JSON.stringify(touch))\n}\n\n/**\n * Fail-closed parse of first-touch JSON (the shape `user.acquisition`\n * stores): anything oversized, unparseable, or missing a string `source`\n * is null.\n */\nexport function parseFirstTouchJson(\n raw: string | null | undefined,\n): FirstTouch | null {\n if (!raw || raw.length > RAW_MAX) return null\n try {\n const parsed: unknown = JSON.parse(raw)\n if (typeof parsed !== 'object' || parsed === null) return null\n const record = parsed as Record<string, unknown>\n if (typeof record.source !== 'string' || !record.source) return null\n const touch: FirstTouch = { source: record.source.slice(0, FIELD_MAX) }\n for (const key of [\n 'medium',\n 'campaign',\n 'referrer',\n 'landing',\n 'at',\n ] as const) {\n const value = record[key]\n if (typeof value === 'string' && value)\n touch[key] = value.slice(0, FIELD_MAX)\n }\n return touch\n } catch {\n return null\n }\n}\n\n/**\n * Fail-closed cookie decode. The cookie arrives from the wild — a browser\n * extension or a hand-edited jar can put anything under our name.\n */\nexport function decodeFirstTouch(\n raw: string | null | undefined,\n): FirstTouch | null {\n if (!raw || raw.length > RAW_MAX) return null\n try {\n return parseFirstTouchJson(decodeURIComponent(raw))\n } catch {\n return null\n }\n}\n\n/** Read one cookie's raw value out of a Cookie header. */\nexport function readCookieValue(\n header: string | null | undefined,\n name: string,\n): string | undefined {\n if (!header) return undefined\n for (const part of header.split(';')) {\n const eq = part.indexOf('=')\n if (eq === -1) continue\n if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim()\n }\n return undefined\n}\n"],"mappings":";AAeO,IAAM,qBAAqB;AAI3B,IAAM,sBAAsB,KAAK,KAAK,KAAK;AAIlD,IAAM,YAAY;AAGlB,IAAM,UAAU;AAiBhB,SAAS,MAAM,OAAsD;AACnE,QAAM,IAAI,OAAO,KAAK,EAAE,MAAM,GAAG,SAAS;AAC1C,SAAO,KAAK;AACd;AAQA,SAAS,SAAS,GAAW,GAAoB;AAC/C,QAAM,QAAQ,CAAC,MAAe,EAAE,WAAW,MAAM,IAAI,EAAE,MAAM,CAAC,IAAI;AAClE,SAAO,MAAM,CAAC,MAAM,MAAM,CAAC;AAC7B;AAQO,SAAS,aAAa,OAGP;AACpB,QAAM,EAAE,KAAK,QAAQ,IAAI;AACzB,QAAM,SAAS,MAAM,IAAI,aAAa,IAAI,YAAY,CAAC;AACvD,QAAM,SAAS,MAAM,IAAI,aAAa,IAAI,YAAY,CAAC;AACvD,QAAM,WAAW,MAAM,IAAI,aAAa,IAAI,cAAc,CAAC;AAE3D,MAAI;AACJ,MAAI,SAAS;AACX,QAAI;AACF,YAAM,IAAI,IAAI,IAAI,OAAO;AACzB,UAAI,EAAE,YAAY,CAAC,SAAS,EAAE,UAAU,IAAI,QAAQ,GAAG;AACrD,yBAAiB,MAAM,EAAE,QAAQ;AAAA,MACnC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,UAAU,CAAC,YAAY,CAAC,eAAgB,QAAO;AAEpD,QAAM,QAAoB;AAAA,IACxB,QAAQ,UAAU,kBAAkB;AAAA,EACtC;AACA,QAAM,gBACJ,WAAW,CAAC,UAAU,iBAAiB,aAAa;AACtD,MAAI,cAAe,OAAM,SAAS;AAClC,MAAI,SAAU,OAAM,WAAW;AAC/B,MAAI,eAAgB,OAAM,WAAW;AACrC,SAAO;AACT;AAGO,SAAS,iBAAiB,OAA2B;AAC1D,SAAO,mBAAmB,KAAK,UAAU,KAAK,CAAC;AACjD;AAOO,SAAS,oBACd,KACmB;AACnB,MAAI,CAAC,OAAO,IAAI,SAAS,QAAS,QAAO;AACzC,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,QAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;AAC1D,UAAM,SAAS;AACf,QAAI,OAAO,OAAO,WAAW,YAAY,CAAC,OAAO,OAAQ,QAAO;AAChE,UAAM,QAAoB,EAAE,QAAQ,OAAO,OAAO,MAAM,GAAG,SAAS,EAAE;AACtE,eAAW,OAAO;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,GAAY;AACV,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,OAAO,UAAU,YAAY;AAC/B,cAAM,GAAG,IAAI,MAAM,MAAM,GAAG,SAAS;AAAA,IACzC;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,iBACd,KACmB;AACnB,MAAI,CAAC,OAAO,IAAI,SAAS,QAAS,QAAO;AACzC,MAAI;AACF,WAAO,oBAAoB,mBAAmB,GAAG,CAAC;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,gBACd,QACA,MACoB;AACpB,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,OAAO,GAAI;AACf,QAAI,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK,MAAM,KAAM,QAAO,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK;AAAA,EACxE;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The backdrop wire contract: what `GET /api/backdrops`
3
+ * hands to the studio panel, the picker, the CLI and agents. Absolute URLs
4
+ * on purpose — a ProjectDoc travels across environments (save-to-vos, CLI
5
+ * takes, server renders), so a backdrop key must resolve everywhere without
6
+ * host-side rewriting.
7
+ */
8
+ declare const BACKDROP_ASSET_BASE = "https://assets.vos.so/";
9
+ interface Backdrop {
10
+ id: string;
11
+ slug: string;
12
+ title: string;
13
+ /** The loop's length in seconds — a fact of the asset, never a knob. */
14
+ duration: number;
15
+ /** The 1080p bake's pixel size (the export note reads it). */
16
+ width: number;
17
+ height: number;
18
+ /** The CSS underlay a pick writes into `frame.background`. */
19
+ ground: string;
20
+ /** Provenance: the vos it was baked from, when it still exists. */
21
+ vosId: string | null;
22
+ urls: {
23
+ '1080p': string | null;
24
+ '2k': string | null;
25
+ poster: string | null;
26
+ };
27
+ }
28
+
29
+ export { BACKDROP_ASSET_BASE, type Backdrop };
@@ -0,0 +1,6 @@
1
+ // src/backdrops.ts
2
+ var BACKDROP_ASSET_BASE = "https://assets.vos.so/";
3
+ export {
4
+ BACKDROP_ASSET_BASE
5
+ };
6
+ //# sourceMappingURL=backdrops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/backdrops.ts"],"sourcesContent":["/**\n * The backdrop wire contract: what `GET /api/backdrops`\n * hands to the studio panel, the picker, the CLI and agents. Absolute URLs\n * on purpose — a ProjectDoc travels across environments (save-to-vos, CLI\n * takes, server renders), so a backdrop key must resolve everywhere without\n * host-side rewriting.\n */\n\nexport const BACKDROP_ASSET_BASE = 'https://assets.vos.so/'\n\nexport interface Backdrop {\n id: string\n slug: string\n title: string\n /** The loop's length in seconds — a fact of the asset, never a knob. */\n duration: number\n /** The 1080p bake's pixel size (the export note reads it). */\n width: number\n height: number\n /** The CSS underlay a pick writes into `frame.background`. */\n ground: string\n /** Provenance: the vos it was baked from, when it still exists. */\n vosId: string | null\n urls: {\n '1080p': string | null\n '2k': string | null\n poster: string | null\n }\n}\n"],"mappings":";AAQO,IAAM,sBAAsB;","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-K7EIJSYQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,25 @@
1
+ // src/utils/index.ts
2
+ function formatLabel(filename) {
3
+ return filename.replace(/\.[^/.]+$/, "").split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
4
+ }
5
+ function generateId() {
6
+ return crypto.randomUUID();
7
+ }
8
+ function sleep(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ function safeJsonParse(json, fallback) {
12
+ try {
13
+ return JSON.parse(json);
14
+ } catch {
15
+ return fallback;
16
+ }
17
+ }
18
+
19
+ export {
20
+ formatLabel,
21
+ generateId,
22
+ sleep,
23
+ safeJsonParse
24
+ };
25
+ //# sourceMappingURL=chunk-RDD5SU6G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/index.ts"],"sourcesContent":["/**\n * Shared Utilities\n * Common utility functions used across frontend and backend\n */\n\n/**\n * Format a filename to a human-readable label\n * @example formatLabel('basic-fade') => 'Basic Fade'\n */\nexport function formatLabel(filename: string): string {\n return filename\n .replace(/\\.[^/.]+$/, '') // Remove extension\n .split(/[-_]/)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n}\n\n/**\n * Generate a unique ID\n */\nexport function generateId(): string {\n return crypto.randomUUID()\n}\n\n/**\n * Sleep for a specified duration\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Safely parse JSON with a fallback\n */\nexport function safeJsonParse<T>(json: string, fallback: T): T {\n try {\n return JSON.parse(json) as T\n } catch {\n return fallback\n }\n}\n"],"mappings":";AASO,SAAS,YAAY,UAA0B;AACpD,SAAO,SACJ,QAAQ,aAAa,EAAE,EACvB,MAAM,MAAM,EACZ,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,EAAE,YAAY,CAAC,EACxE,KAAK,GAAG;AACb;AAKO,SAAS,aAAqB;AACnC,SAAO,OAAO,WAAW;AAC3B;AAKO,SAAS,MAAM,IAA2B;AAC/C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAKO,SAAS,cAAiB,MAAc,UAAgB;AAC7D,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,300 @@
1
+ // src/fontCatalog.ts
2
+ var FONT_CATALOG = [
3
+ {
4
+ family: "Inter",
5
+ slug: "inter",
6
+ category: "sans",
7
+ weights: [400, 500, 600, 700]
8
+ },
9
+ {
10
+ family: "Lexend",
11
+ slug: "lexend",
12
+ category: "sans",
13
+ weights: [400, 600, 700]
14
+ },
15
+ {
16
+ family: "Roboto",
17
+ slug: "roboto",
18
+ category: "sans",
19
+ weights: [400, 500, 700]
20
+ },
21
+ {
22
+ family: "Open Sans",
23
+ slug: "open-sans",
24
+ category: "sans",
25
+ weights: [400, 600, 700]
26
+ },
27
+ {
28
+ family: "Montserrat",
29
+ slug: "montserrat",
30
+ category: "sans",
31
+ weights: [400, 600, 700]
32
+ },
33
+ {
34
+ family: "Poppins",
35
+ slug: "poppins",
36
+ category: "sans",
37
+ weights: [400, 500, 600, 700]
38
+ },
39
+ {
40
+ family: "Raleway",
41
+ slug: "raleway",
42
+ category: "sans",
43
+ weights: [400, 600, 700]
44
+ },
45
+ {
46
+ family: "Work Sans",
47
+ slug: "work-sans",
48
+ category: "sans",
49
+ weights: [400, 600, 700]
50
+ },
51
+ {
52
+ family: "Nunito",
53
+ slug: "nunito",
54
+ category: "sans",
55
+ weights: [400, 600, 700]
56
+ },
57
+ {
58
+ family: "Rubik",
59
+ slug: "rubik",
60
+ category: "sans",
61
+ weights: [400, 500, 700]
62
+ },
63
+ {
64
+ family: "DM Sans",
65
+ slug: "dm-sans",
66
+ category: "sans",
67
+ weights: [400, 500, 700]
68
+ },
69
+ {
70
+ family: "Manrope",
71
+ slug: "manrope",
72
+ category: "sans",
73
+ weights: [400, 600, 800]
74
+ },
75
+ {
76
+ family: "Outfit",
77
+ slug: "outfit",
78
+ category: "sans",
79
+ weights: [400, 500, 700]
80
+ },
81
+ {
82
+ family: "Space Grotesk",
83
+ slug: "space-grotesk",
84
+ category: "sans",
85
+ weights: [400, 500, 700]
86
+ },
87
+ {
88
+ family: "Plus Jakarta Sans",
89
+ slug: "plus-jakarta-sans",
90
+ category: "sans",
91
+ weights: [400, 600, 700]
92
+ },
93
+ {
94
+ family: "Sora",
95
+ slug: "sora",
96
+ category: "sans",
97
+ weights: [400, 600, 700]
98
+ },
99
+ {
100
+ family: "Figtree",
101
+ slug: "figtree",
102
+ category: "sans",
103
+ weights: [400, 600, 700]
104
+ },
105
+ {
106
+ family: "Playfair Display",
107
+ slug: "playfair-display",
108
+ category: "serif",
109
+ weights: [400, 600, 700]
110
+ },
111
+ {
112
+ family: "Merriweather",
113
+ slug: "merriweather",
114
+ category: "serif",
115
+ weights: [400, 700, 900]
116
+ },
117
+ {
118
+ family: "Lora",
119
+ slug: "lora",
120
+ category: "serif",
121
+ weights: [400, 500, 700]
122
+ },
123
+ {
124
+ family: "Libre Baskerville",
125
+ slug: "libre-baskerville",
126
+ category: "serif",
127
+ weights: [400, 700]
128
+ },
129
+ {
130
+ family: "Cormorant Garamond",
131
+ slug: "cormorant-garamond",
132
+ category: "serif",
133
+ weights: [400, 600, 700]
134
+ },
135
+ {
136
+ family: "EB Garamond",
137
+ slug: "eb-garamond",
138
+ category: "serif",
139
+ weights: [400, 600, 700]
140
+ },
141
+ {
142
+ family: "DM Serif Display",
143
+ slug: "dm-serif-display",
144
+ category: "serif",
145
+ weights: [400]
146
+ },
147
+ {
148
+ family: "Fraunces",
149
+ slug: "fraunces",
150
+ category: "serif",
151
+ weights: [400, 600, 700]
152
+ },
153
+ {
154
+ family: "Source Serif 4",
155
+ slug: "source-serif-4",
156
+ category: "serif",
157
+ weights: [400, 600, 700]
158
+ },
159
+ {
160
+ family: "Bebas Neue",
161
+ slug: "bebas-neue",
162
+ category: "display",
163
+ weights: [400]
164
+ },
165
+ {
166
+ family: "Oswald",
167
+ slug: "oswald",
168
+ category: "display",
169
+ weights: [400, 500, 700]
170
+ },
171
+ {
172
+ family: "Anton",
173
+ slug: "anton",
174
+ category: "display",
175
+ weights: [400]
176
+ },
177
+ {
178
+ family: "Archivo Black",
179
+ slug: "archivo-black",
180
+ category: "display",
181
+ weights: [400]
182
+ },
183
+ {
184
+ family: "Abril Fatface",
185
+ slug: "abril-fatface",
186
+ category: "display",
187
+ weights: [400]
188
+ },
189
+ {
190
+ family: "Righteous",
191
+ slug: "righteous",
192
+ category: "display",
193
+ weights: [400]
194
+ },
195
+ {
196
+ family: "Alfa Slab One",
197
+ slug: "alfa-slab-one",
198
+ category: "display",
199
+ weights: [400]
200
+ },
201
+ {
202
+ family: "Caveat",
203
+ slug: "caveat",
204
+ category: "handwriting",
205
+ weights: [400, 700]
206
+ },
207
+ {
208
+ family: "Pacifico",
209
+ slug: "pacifico",
210
+ category: "handwriting",
211
+ weights: [400]
212
+ },
213
+ {
214
+ family: "Dancing Script",
215
+ slug: "dancing-script",
216
+ category: "handwriting",
217
+ weights: [400, 700]
218
+ },
219
+ {
220
+ family: "Satisfy",
221
+ slug: "satisfy",
222
+ category: "handwriting",
223
+ weights: [400]
224
+ },
225
+ {
226
+ family: "Shadows Into Light",
227
+ slug: "shadows-into-light",
228
+ category: "handwriting",
229
+ weights: [400]
230
+ },
231
+ {
232
+ family: "JetBrains Mono",
233
+ slug: "jetbrains-mono",
234
+ category: "mono",
235
+ weights: [400, 500, 700]
236
+ },
237
+ {
238
+ family: "Fira Code",
239
+ slug: "fira-code",
240
+ category: "mono",
241
+ weights: [400, 500, 700]
242
+ },
243
+ {
244
+ family: "Space Mono",
245
+ slug: "space-mono",
246
+ category: "mono",
247
+ weights: [400, 700]
248
+ },
249
+ {
250
+ family: "IBM Plex Mono",
251
+ slug: "ibm-plex-mono",
252
+ category: "mono",
253
+ weights: [400, 500, 700]
254
+ }
255
+ ];
256
+
257
+ // src/fonts.ts
258
+ var FONT_CDN_BASE = "https://assets.vos.so/fonts";
259
+ function fontFaceUrl(slug, weight) {
260
+ return `${FONT_CDN_BASE}/${slug}/${weight}.woff2`;
261
+ }
262
+ function findFontFamily(family) {
263
+ const needle = family.trim().toLowerCase();
264
+ return FONT_CATALOG.find((f) => f.family.toLowerCase() === needle);
265
+ }
266
+ function nearestFontWeight(entry, weight) {
267
+ let best = entry.weights[0];
268
+ for (const w of entry.weights) {
269
+ if (Math.abs(w - weight) < Math.abs(best - weight)) best = w;
270
+ }
271
+ return best;
272
+ }
273
+ function fontStack(entry) {
274
+ const quoted = entry.family.includes(" ") ? `'${entry.family}'` : entry.family;
275
+ const fallback = entry.category === "serif" ? "Georgia, serif" : entry.category === "mono" ? "ui-monospace, monospace" : entry.category === "handwriting" ? "cursive" : "-apple-system, system-ui, sans-serif";
276
+ return `${quoted}, ${fallback}`;
277
+ }
278
+ function fontManifest() {
279
+ return {
280
+ version: 1,
281
+ base: FONT_CDN_BASE,
282
+ families: FONT_CATALOG.map((e) => ({
283
+ ...e,
284
+ files: Object.fromEntries(
285
+ e.weights.map((w) => [String(w), fontFaceUrl(e.slug, w)])
286
+ )
287
+ }))
288
+ };
289
+ }
290
+
291
+ export {
292
+ FONT_CATALOG,
293
+ FONT_CDN_BASE,
294
+ fontFaceUrl,
295
+ findFontFamily,
296
+ nearestFontWeight,
297
+ fontStack,
298
+ fontManifest
299
+ };
300
+ //# sourceMappingURL=chunk-YBEIOW7L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fontCatalog.ts","../src/fonts.ts"],"sourcesContent":["/**\n * Generated by the vosso build; do not edit by hand.\n * Curation lives in the build script; re-run it (then `pnpm assets:push`) to\n * change the catalog. Files: https://assets.vos.so/fonts/{slug}/{weight}.woff2 (latin subset).\n */\nexport interface FontCatalogEntry {\n family: string\n slug: string\n category: 'sans' | 'serif' | 'display' | 'handwriting' | 'mono'\n /** Hosted weight steps — canvas cannot synthesize weights. */\n weights: number[]\n}\n\nexport const FONT_CATALOG: FontCatalogEntry[] = [\n {\n family: 'Inter',\n slug: 'inter',\n category: 'sans',\n weights: [400, 500, 600, 700],\n },\n {\n family: 'Lexend',\n slug: 'lexend',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Roboto',\n slug: 'roboto',\n category: 'sans',\n weights: [400, 500, 700],\n },\n {\n family: 'Open Sans',\n slug: 'open-sans',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Montserrat',\n slug: 'montserrat',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Poppins',\n slug: 'poppins',\n category: 'sans',\n weights: [400, 500, 600, 700],\n },\n {\n family: 'Raleway',\n slug: 'raleway',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Work Sans',\n slug: 'work-sans',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Nunito',\n slug: 'nunito',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Rubik',\n slug: 'rubik',\n category: 'sans',\n weights: [400, 500, 700],\n },\n {\n family: 'DM Sans',\n slug: 'dm-sans',\n category: 'sans',\n weights: [400, 500, 700],\n },\n {\n family: 'Manrope',\n slug: 'manrope',\n category: 'sans',\n weights: [400, 600, 800],\n },\n {\n family: 'Outfit',\n slug: 'outfit',\n category: 'sans',\n weights: [400, 500, 700],\n },\n {\n family: 'Space Grotesk',\n slug: 'space-grotesk',\n category: 'sans',\n weights: [400, 500, 700],\n },\n {\n family: 'Plus Jakarta Sans',\n slug: 'plus-jakarta-sans',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Sora',\n slug: 'sora',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Figtree',\n slug: 'figtree',\n category: 'sans',\n weights: [400, 600, 700],\n },\n {\n family: 'Playfair Display',\n slug: 'playfair-display',\n category: 'serif',\n weights: [400, 600, 700],\n },\n {\n family: 'Merriweather',\n slug: 'merriweather',\n category: 'serif',\n weights: [400, 700, 900],\n },\n {\n family: 'Lora',\n slug: 'lora',\n category: 'serif',\n weights: [400, 500, 700],\n },\n {\n family: 'Libre Baskerville',\n slug: 'libre-baskerville',\n category: 'serif',\n weights: [400, 700],\n },\n {\n family: 'Cormorant Garamond',\n slug: 'cormorant-garamond',\n category: 'serif',\n weights: [400, 600, 700],\n },\n {\n family: 'EB Garamond',\n slug: 'eb-garamond',\n category: 'serif',\n weights: [400, 600, 700],\n },\n {\n family: 'DM Serif Display',\n slug: 'dm-serif-display',\n category: 'serif',\n weights: [400],\n },\n {\n family: 'Fraunces',\n slug: 'fraunces',\n category: 'serif',\n weights: [400, 600, 700],\n },\n {\n family: 'Source Serif 4',\n slug: 'source-serif-4',\n category: 'serif',\n weights: [400, 600, 700],\n },\n {\n family: 'Bebas Neue',\n slug: 'bebas-neue',\n category: 'display',\n weights: [400],\n },\n {\n family: 'Oswald',\n slug: 'oswald',\n category: 'display',\n weights: [400, 500, 700],\n },\n {\n family: 'Anton',\n slug: 'anton',\n category: 'display',\n weights: [400],\n },\n {\n family: 'Archivo Black',\n slug: 'archivo-black',\n category: 'display',\n weights: [400],\n },\n {\n family: 'Abril Fatface',\n slug: 'abril-fatface',\n category: 'display',\n weights: [400],\n },\n {\n family: 'Righteous',\n slug: 'righteous',\n category: 'display',\n weights: [400],\n },\n {\n family: 'Alfa Slab One',\n slug: 'alfa-slab-one',\n category: 'display',\n weights: [400],\n },\n {\n family: 'Caveat',\n slug: 'caveat',\n category: 'handwriting',\n weights: [400, 700],\n },\n {\n family: 'Pacifico',\n slug: 'pacifico',\n category: 'handwriting',\n weights: [400],\n },\n {\n family: 'Dancing Script',\n slug: 'dancing-script',\n category: 'handwriting',\n weights: [400, 700],\n },\n {\n family: 'Satisfy',\n slug: 'satisfy',\n category: 'handwriting',\n weights: [400],\n },\n {\n family: 'Shadows Into Light',\n slug: 'shadows-into-light',\n category: 'handwriting',\n weights: [400],\n },\n {\n family: 'JetBrains Mono',\n slug: 'jetbrains-mono',\n category: 'mono',\n weights: [400, 500, 700],\n },\n {\n family: 'Fira Code',\n slug: 'fira-code',\n category: 'mono',\n weights: [400, 500, 700],\n },\n {\n family: 'Space Mono',\n slug: 'space-mono',\n category: 'mono',\n weights: [400, 700],\n },\n {\n family: 'IBM Plex Mono',\n slug: 'ibm-plex-mono',\n category: 'mono',\n weights: [400, 500, 700],\n },\n]\n","/**\n * The ONE font-catalog seam (mirrors the params pattern): the curated,\n * self-hosted font families every surface reads — the FontField picker, the\n * take-editor lowering (SETUP FontFace loads), the API's GET /fonts, and\n * agent contracts. Generated by the vosso build; do not edit by hand. Files\n * live in the vosso-public bucket under fonts/ (assets.vos.so).\n */\nimport { FONT_CATALOG } from './fontCatalog'\nimport type { FontCatalogEntry } from './fontCatalog'\n\nexport { FONT_CATALOG } from './fontCatalog'\nexport type { FontCatalogEntry } from './fontCatalog'\n\nexport const FONT_CDN_BASE = 'https://assets.vos.so/fonts'\n\nexport type FontCategory = FontCatalogEntry['category']\n\n/** Public URL of a hosted face file (latin subset, normal style). */\nexport function fontFaceUrl(slug: string, weight: number): string {\n return `${FONT_CDN_BASE}/${slug}/${weight}.woff2`\n}\n\n/** Case-insensitive family lookup. */\nexport function findFontFamily(family: string): FontCatalogEntry | undefined {\n const needle = family.trim().toLowerCase()\n return FONT_CATALOG.find((f) => f.family.toLowerCase() === needle)\n}\n\n/** Nearest hosted weight step (canvas cannot synthesize weights). */\nexport function nearestFontWeight(\n entry: FontCatalogEntry,\n weight: number,\n): number {\n let best = entry.weights[0]\n for (const w of entry.weights) {\n if (Math.abs(w - weight) < Math.abs(best - weight)) best = w\n }\n return best\n}\n\n/**\n * CSS stack for a catalog family: the webfont first, then a category-true\n * system fallback (what renders during load and on fail-open).\n */\nexport function fontStack(entry: FontCatalogEntry): string {\n const quoted = entry.family.includes(' ') ? `'${entry.family}'` : entry.family\n const fallback =\n entry.category === 'serif'\n ? 'Georgia, serif'\n : entry.category === 'mono'\n ? 'ui-monospace, monospace'\n : entry.category === 'handwriting'\n ? 'cursive'\n : '-apple-system, system-ui, sans-serif'\n return `${quoted}, ${fallback}`\n}\n\n/** The manifest shape served by GET /api/fonts (mirrors fonts.json). */\nexport function fontManifest() {\n return {\n version: 1,\n base: FONT_CDN_BASE,\n families: FONT_CATALOG.map((e) => ({\n ...e,\n files: Object.fromEntries(\n e.weights.map((w) => [String(w), fontFaceUrl(e.slug, w)]),\n ),\n })),\n }\n}\n"],"mappings":"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","names":[]}