strapi-plugin-radiocult-uploader 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/dist/admin/App-Cd-TZrqC.mjs +1179 -0
  4. package/dist/admin/App-DTOuR7aY.js +1199 -0
  5. package/dist/admin/Settings-B3cZR7VF.mjs +516 -0
  6. package/dist/admin/Settings-DHv9SmRU.js +534 -0
  7. package/dist/admin/en-CTCtUg23.mjs +8 -0
  8. package/dist/admin/en-D_61ojL7.js +8 -0
  9. package/dist/admin/index-BLAtUo_j.js +334 -0
  10. package/dist/admin/index-Dj3e3A79.mjs +318 -0
  11. package/dist/admin/index.js +4 -0
  12. package/dist/admin/index.mjs +4 -0
  13. package/dist/admin/src/api.d.ts +107 -0
  14. package/dist/admin/src/components/Initializer.d.ts +5 -0
  15. package/dist/admin/src/components/Panel.d.ts +18 -0
  16. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  17. package/dist/admin/src/index.d.ts +3 -0
  18. package/dist/admin/src/pages/App.d.ts +2 -0
  19. package/dist/admin/src/pages/HomePage.d.ts +6 -0
  20. package/dist/admin/src/pages/Settings.d.ts +8 -0
  21. package/dist/admin/src/pages/Uploader.d.ts +10 -0
  22. package/dist/admin/src/pluginId.d.ts +1 -0
  23. package/dist/admin/src/theme/tokens.d.ts +96 -0
  24. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  25. package/dist/admin/tokens-ACCekCH6.mjs +161 -0
  26. package/dist/admin/tokens-CDeNcvZq.js +178 -0
  27. package/dist/server/index.js +1193 -0
  28. package/dist/server/index.mjs +1165 -0
  29. package/dist/server/src/bootstrap.d.ts +5 -0
  30. package/dist/server/src/config/index.d.ts +17 -0
  31. package/dist/server/src/content-types/index.d.ts +51 -0
  32. package/dist/server/src/content-types/track-record/schema.json.d.ts +29 -0
  33. package/dist/server/src/controllers/index.d.ts +21 -0
  34. package/dist/server/src/controllers/settings.d.ts +16 -0
  35. package/dist/server/src/controllers/uploader.d.ts +10 -0
  36. package/dist/server/src/controllers/utils.d.ts +16 -0
  37. package/dist/server/src/destroy.d.ts +5 -0
  38. package/dist/server/src/index.d.ts +267 -0
  39. package/dist/server/src/middlewares/index.d.ts +2 -0
  40. package/dist/server/src/policies/index.d.ts +2 -0
  41. package/dist/server/src/register.d.ts +5 -0
  42. package/dist/server/src/routes/admin/index.d.ts +22 -0
  43. package/dist/server/src/routes/index.d.ts +19 -0
  44. package/dist/server/src/services/id3.d.ts +18 -0
  45. package/dist/server/src/services/index.d.ts +156 -0
  46. package/dist/server/src/services/mapping.d.ts +47 -0
  47. package/dist/server/src/services/radiocult.d.ts +115 -0
  48. package/dist/server/src/services/records.d.ts +25 -0
  49. package/dist/server/src/services/settings.d.ts +103 -0
  50. package/dist/server/src/utils/errors.d.ts +5 -0
  51. package/dist/server/src/utils/plugin.d.ts +4 -0
  52. package/logo.png +0 -0
  53. package/package.json +98 -0
  54. package/scripts/migrate-from-fields.mjs +78 -0
@@ -0,0 +1,107 @@
1
+ import { ThemeSettings } from './theme/tokens';
2
+ export type PlatformKey = 'mixcloud' | 'soundcloud';
3
+ export declare const PLATFORMS: PlatformKey[];
4
+ export declare const PLATFORM_LABEL: Record<PlatformKey, string>;
5
+ export interface PlatformStatus {
6
+ state: 'not_published' | 'publishing' | 'linked' | 'stalled';
7
+ requestedAt: string | null;
8
+ link: string | null;
9
+ }
10
+ export interface RcStatus {
11
+ enabled: boolean;
12
+ trackId: string | null;
13
+ uploadedAt: string | null;
14
+ platforms: Record<PlatformKey, PlatformStatus>;
15
+ rateLimitMessage?: string | null;
16
+ details: {
17
+ title: string;
18
+ showName: string;
19
+ artist: string;
20
+ album: string;
21
+ description: string;
22
+ genres: string[];
23
+ image: {
24
+ name: string;
25
+ previewUrl: string;
26
+ usable: boolean;
27
+ } | null;
28
+ };
29
+ }
30
+ export interface ContentTypeInfo {
31
+ uid: string;
32
+ displayName: string;
33
+ attributes: {
34
+ name: string;
35
+ type: string;
36
+ target: string | null;
37
+ targetAttributes: string[] | null;
38
+ }[];
39
+ }
40
+ export interface SettingsPayload {
41
+ credentials: {
42
+ stationId: string;
43
+ enabled: boolean;
44
+ apiKeySet: boolean;
45
+ };
46
+ mapping: Record<string, string | null>;
47
+ defaults: {
48
+ artist: string;
49
+ description: string;
50
+ };
51
+ theme: ThemeSettings;
52
+ envLocks: {
53
+ apiKey: boolean;
54
+ stationId: boolean;
55
+ enabled: boolean;
56
+ };
57
+ }
58
+ export declare const apiError: (error: any) => string;
59
+ export declare const fetchStatus: (documentId: string, refresh?: boolean) => Promise<RcStatus>;
60
+ export declare const publishTrack: (documentId: string, platform: PlatformKey, body: {
61
+ title: string;
62
+ tags: string[];
63
+ description: string;
64
+ }, force?: boolean) => Promise<unknown>;
65
+ export declare const saveTrackDetails: (documentId: string, meta: {
66
+ title: string;
67
+ artist: string;
68
+ album: string;
69
+ }) => Promise<unknown>;
70
+ export declare const fetchUiConfig: () => Promise<{
71
+ theme: ThemeSettings;
72
+ contentType: string | null;
73
+ }>;
74
+ export declare const fetchSettings: () => Promise<SettingsPayload>;
75
+ export declare const saveSettings: (patch: Record<string, unknown>) => Promise<SettingsPayload>;
76
+ export declare const testConnection: () => Promise<{
77
+ ok: boolean;
78
+ message: string;
79
+ }>;
80
+ export declare const fetchContentTypes: () => Promise<ContentTypeInfo[]>;
81
+ export declare const fetchDiagnostics: () => Promise<{
82
+ nodeVersion: string;
83
+ openAsBlob: boolean;
84
+ bodyMaxFileSize: number | null;
85
+ bodyLimitOk: boolean | null;
86
+ cronEnabled: boolean;
87
+ }>;
88
+ export interface UploadCallbacks {
89
+ onProgress: (pct: number | null) => void;
90
+ onDone: (body: {
91
+ trackId?: string;
92
+ warnings?: string[];
93
+ }) => void;
94
+ onConflict: (message: string) => void;
95
+ onError: (message: string, status?: number) => void;
96
+ onAbort: () => void;
97
+ }
98
+ /**
99
+ * Uploads the audio file with progress reporting. Returns the XHR so the
100
+ * caller can abort. `token` comes from useAuth — fetch() has no upload
101
+ * progress events, so this one call is a raw XHR.
102
+ */
103
+ export declare const uploadTrack: (documentId: string, file: File, meta: {
104
+ title: string;
105
+ artist: string;
106
+ album: string;
107
+ }, force: boolean, token: string, callbacks: UploadCallbacks) => XMLHttpRequest;
@@ -0,0 +1,5 @@
1
+ type InitializerProps = {
2
+ setPlugin: (id: string) => void;
3
+ };
4
+ declare const Initializer: ({ setPlugin }: InitializerProps) => null;
5
+ export { Initializer };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Edit-view side panel: shows where the entry has got to (uploaded /
3
+ * publishing / linked) and opens the uploader page. Only rendered on the
4
+ * content type configured in the plugin settings.
5
+ */
6
+ import * as React from 'react';
7
+ /**
8
+ * PanelComponent for content-manager's addEditViewSidePanel. Called as a
9
+ * plain function during render — it must not use hooks itself; everything
10
+ * stateful lives in the components above.
11
+ */
12
+ export declare const RadioCultPanel: (props: {
13
+ model: string;
14
+ documentId?: string;
15
+ }) => {
16
+ title: string;
17
+ content: React.JSX.Element;
18
+ } | null;
@@ -0,0 +1,2 @@
1
+ declare const PluginIcon: () => import("react").JSX.Element;
2
+ export { PluginIcon };
@@ -0,0 +1,3 @@
1
+ import { StrapiApp } from '@strapi/strapi/admin';
2
+ declare const plugin: StrapiApp['appPlugins'][string];
3
+ export default plugin;
@@ -0,0 +1,2 @@
1
+ declare const App: () => import("react").JSX.Element;
2
+ export default App;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Landing page for the plugin's menu entry: explains how the uploader is
3
+ * opened (from an entry's edit view) and links to the settings.
4
+ */
5
+ declare const HomePage: () => import("react").JSX.Element;
6
+ export { HomePage };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Plugin settings page: Radio Cult credentials, content-type field mapping,
3
+ * publish defaults, uploader theme (preset + font + colour overrides + logo) and
4
+ * environment diagnostics.
5
+ */
6
+ import * as React from 'react';
7
+ declare const Settings: () => React.JSX.Element;
8
+ export default Settings;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The Episode Uploader page: step 1 edit track details, step 2 upload the
3
+ * audio to Radio Cult (with real progress), step 3 publish to Mixcloud /
4
+ * SoundCloud, plus a terminal-style activity log. Rendered inside the admin
5
+ * at /admin/plugins/radiocult-uploader/upload/:documentId, themed via the
6
+ * plugin settings (CSS custom properties on the wrapper).
7
+ */
8
+ import * as React from 'react';
9
+ declare const Uploader: () => React.JSX.Element;
10
+ export default Uploader;
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_ID = "radiocult-uploader";
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Uploader page theme system. A theme is a flat map of colour tokens; presets
3
+ * provide complete maps and the settings page lets users override a small,
4
+ * curated subset of them (see EDITABLE_TOKENS) plus the font, logo and station
5
+ * name. Tokens are applied as CSS custom properties (--rcu-*) on the uploader
6
+ * page wrapper.
7
+ */
8
+ export interface ThemeTokens {
9
+ /** Page background gradient, light end. */
10
+ pageBg: string;
11
+ /** Page background gradient, dark end. */
12
+ pageBgDeep: string;
13
+ /** Main text / primary UI colour. */
14
+ text: string;
15
+ /** Secondary text. */
16
+ textDim: string;
17
+ /** Gradient partner for text on headings and buttons. */
18
+ contrast: string;
19
+ /** Card background. */
20
+ card: string;
21
+ /** Card / input border. */
22
+ cardBorder: string;
23
+ /** Input background. */
24
+ inputBg: string;
25
+ /** Sunken areas (log background, progress track). */
26
+ sunk: string;
27
+ /** Success. */
28
+ green: string;
29
+ /** Warning / in-progress. */
30
+ amber: string;
31
+ /** Error / destructive. */
32
+ red: string;
33
+ /** Mixcloud accent. */
34
+ mixcloud: string;
35
+ /** SoundCloud accent. */
36
+ soundcloud: string;
37
+ /** Text on filled buttons and step badges. */
38
+ buttonText: string;
39
+ /** Strong shadow colour (with alpha). */
40
+ shadow: string;
41
+ /** Soft shadow / focus ring colour (with alpha). */
42
+ shadowSoft: string;
43
+ /** Vinyl record groove colours. */
44
+ vinylDark: string;
45
+ vinylMid: string;
46
+ }
47
+ /**
48
+ * The only tokens the settings page exposes. Deliberately short — everything
49
+ * else follows the preset — and deliberately all plain hex, so every one of
50
+ * them gets a real colour picker rather than a read-only swatch.
51
+ */
52
+ export declare const EDITABLE_TOKENS: {
53
+ key: keyof ThemeTokens;
54
+ label: string;
55
+ hint: string;
56
+ }[];
57
+ export declare const EDITABLE_TOKEN_KEYS: (keyof ThemeTokens)[];
58
+ /** Drops any stored override that is no longer user-editable. */
59
+ export declare const pickEditableOverrides: (overrides: Partial<ThemeTokens> | null | undefined) => Partial<ThemeTokens>;
60
+ export interface FontOption {
61
+ label: string;
62
+ /** CSS font-family stack. */
63
+ stack: string;
64
+ /** Google Fonts css2 `family=` value, or null for the system stack. */
65
+ google: string | null;
66
+ }
67
+ /** System default plus a handful of Google Fonts, loaded on demand. */
68
+ export declare const FONTS: Record<string, FontOption>;
69
+ export declare const DEFAULT_FONT = "system";
70
+ export declare const resolveFont: (theme: Partial<ThemeSettings> | null | undefined) => FontOption;
71
+ /** Google Fonts stylesheet URL for a font, or null when none is needed. */
72
+ export declare const googleFontHref: (font: FontOption) => string | null;
73
+ /**
74
+ * Loads a Google Fonts stylesheet on demand. Links are keyed by href and left
75
+ * in <head> once added, so switching back and forth in settings is instant and
76
+ * nothing is fetched twice.
77
+ */
78
+ export declare const useGoogleFont: (href: string | null) => void;
79
+ export declare const PRESETS: Record<string, {
80
+ label: string;
81
+ tokens: ThemeTokens;
82
+ }>;
83
+ export interface ThemeSettings {
84
+ preset: string;
85
+ overrides: Partial<ThemeTokens>;
86
+ /** Key into FONTS. */
87
+ fontFamily: string;
88
+ logoUrl: string | null;
89
+ stationName: string;
90
+ }
91
+ /** Effective tokens: preset (falling back to paper) + user overrides. */
92
+ export declare const resolveTokens: (theme: Partial<ThemeSettings> | null | undefined) => ThemeTokens;
93
+ /** CSS custom properties for the uploader wrapper. */
94
+ export declare const tokensToCssVars: (tokens: ThemeTokens) => Record<string, string>;
95
+ /** Colour vars plus the font var, ready to spread onto a wrapper's style. */
96
+ export declare const themeToCssVars: (theme: Partial<ThemeSettings> | null | undefined) => Record<string, string>;
@@ -0,0 +1,2 @@
1
+ declare const getTranslation: (id: string) => string;
2
+ export { getTranslation };
@@ -0,0 +1,161 @@
1
+ import * as React from "react";
2
+ const EDITABLE_TOKENS = [
3
+ { key: "pageBg", label: "Background", hint: "Top of the page gradient" },
4
+ { key: "pageBgDeep", label: "Background (gradient end)", hint: "Bottom of the page gradient" },
5
+ { key: "text", label: "Text", hint: "Headings, body text and filled buttons" },
6
+ { key: "textDim", label: "Secondary text", hint: "Hints, labels and captions" },
7
+ { key: "contrast", label: "Accent", hint: "Second colour in heading and button gradients" },
8
+ { key: "buttonText", label: "Button text", hint: "Text on filled buttons and step badges" }
9
+ ];
10
+ const EDITABLE_TOKEN_KEYS = EDITABLE_TOKENS.map((t) => t.key);
11
+ const pickEditableOverrides = (overrides) => Object.fromEntries(
12
+ Object.entries(overrides ?? {}).filter(
13
+ ([key]) => EDITABLE_TOKEN_KEYS.includes(key)
14
+ )
15
+ );
16
+ const SYSTEM_STACK = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`;
17
+ const FONTS = {
18
+ system: { label: "System default", stack: SYSTEM_STACK, google: null },
19
+ inter: {
20
+ label: "Inter",
21
+ stack: `'Inter', ${SYSTEM_STACK}`,
22
+ google: "Inter:wght@400;600;700;800"
23
+ },
24
+ "dm-sans": {
25
+ label: "DM Sans",
26
+ stack: `'DM Sans', ${SYSTEM_STACK}`,
27
+ google: "DM+Sans:wght@400;500;700"
28
+ },
29
+ poppins: {
30
+ label: "Poppins",
31
+ stack: `'Poppins', ${SYSTEM_STACK}`,
32
+ google: "Poppins:wght@400;600;700;800"
33
+ },
34
+ "space-grotesk": {
35
+ label: "Space Grotesk",
36
+ stack: `'Space Grotesk', ${SYSTEM_STACK}`,
37
+ google: "Space+Grotesk:wght@400;500;700"
38
+ },
39
+ "work-sans": {
40
+ label: "Work Sans",
41
+ stack: `'Work Sans', ${SYSTEM_STACK}`,
42
+ google: "Work+Sans:wght@400;600;700;800"
43
+ },
44
+ "libre-franklin": {
45
+ label: "Libre Franklin",
46
+ stack: `'Libre Franklin', ${SYSTEM_STACK}`,
47
+ google: "Libre+Franklin:wght@400;600;700;800"
48
+ },
49
+ "playfair-display": {
50
+ label: "Playfair Display (serif)",
51
+ stack: `'Playfair Display', Georgia, serif`,
52
+ google: "Playfair+Display:wght@400;600;700;800"
53
+ },
54
+ "ibm-plex-mono": {
55
+ label: "IBM Plex Mono",
56
+ stack: `'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace`,
57
+ google: "IBM+Plex+Mono:wght@400;500;700"
58
+ }
59
+ };
60
+ const DEFAULT_FONT = "system";
61
+ const resolveFont = (theme) => FONTS[theme?.fontFamily ?? DEFAULT_FONT] ?? FONTS[DEFAULT_FONT];
62
+ const googleFontHref = (font) => font.google ? `https://fonts.googleapis.com/css2?family=${font.google}&display=swap` : null;
63
+ const useGoogleFont = (href) => {
64
+ React.useEffect(() => {
65
+ if (!href || document.querySelector(`link[data-rcu-font][href="${href}"]`)) return;
66
+ const link = document.createElement("link");
67
+ link.rel = "stylesheet";
68
+ link.href = href;
69
+ link.setAttribute("data-rcu-font", "");
70
+ document.head.appendChild(link);
71
+ }, [href]);
72
+ };
73
+ const paper = {
74
+ pageBg: "#C0B7AF",
75
+ pageBgDeep: "#ADA39B",
76
+ text: "#322824",
77
+ textDim: "#5c524d",
78
+ contrast: "#000000",
79
+ card: "rgba(255, 255, 255, 0.26)",
80
+ cardBorder: "rgba(50, 40, 36, 0.22)",
81
+ inputBg: "rgba(255, 255, 255, 0.42)",
82
+ sunk: "rgba(50, 40, 36, 0.09)",
83
+ green: "#3f6b52",
84
+ amber: "#8a6a24",
85
+ red: "#8f3a2f",
86
+ mixcloud: "#3f6266",
87
+ soundcloud: "#8f5a2f",
88
+ buttonText: "#C0B7AF",
89
+ shadow: "rgba(50, 40, 36, 0.3)",
90
+ shadowSoft: "rgba(50, 40, 36, 0.16)",
91
+ vinylDark: "#1b1512",
92
+ vinylMid: "#2f2521"
93
+ };
94
+ const midnight = {
95
+ pageBg: "#150b2a",
96
+ pageBgDeep: "#0d0620",
97
+ text: "#f0e9fb",
98
+ textDim: "#9d92b8",
99
+ contrast: "#ff2e88",
100
+ card: "rgba(255, 255, 255, 0.05)",
101
+ cardBorder: "rgba(240, 233, 251, 0.16)",
102
+ inputBg: "rgba(255, 255, 255, 0.07)",
103
+ sunk: "rgba(0, 0, 0, 0.3)",
104
+ green: "#4fd08a",
105
+ amber: "#e8bb54",
106
+ red: "#ff5c4d",
107
+ mixcloud: "#52aab4",
108
+ soundcloud: "#ff7733",
109
+ buttonText: "#150b2a",
110
+ shadow: "rgba(0, 0, 0, 0.5)",
111
+ shadowSoft: "rgba(255, 46, 136, 0.25)",
112
+ vinylDark: "#07030f",
113
+ vinylMid: "#1e1433"
114
+ };
115
+ const slate = {
116
+ pageBg: "#dde3e8",
117
+ pageBgDeep: "#c6cfd6",
118
+ text: "#26313a",
119
+ textDim: "#5b6873",
120
+ contrast: "#0a5c8a",
121
+ card: "rgba(255, 255, 255, 0.45)",
122
+ cardBorder: "rgba(38, 49, 58, 0.2)",
123
+ inputBg: "rgba(255, 255, 255, 0.6)",
124
+ sunk: "rgba(38, 49, 58, 0.08)",
125
+ green: "#2e7d4f",
126
+ amber: "#9a6d1c",
127
+ red: "#a63a2e",
128
+ mixcloud: "#28707a",
129
+ soundcloud: "#b35f2a",
130
+ buttonText: "#eef2f5",
131
+ shadow: "rgba(38, 49, 58, 0.28)",
132
+ shadowSoft: "rgba(38, 49, 58, 0.14)",
133
+ vinylDark: "#161b1f",
134
+ vinylMid: "#2b343b"
135
+ };
136
+ const PRESETS = {
137
+ paper: { label: "Paper (light)", tokens: paper },
138
+ midnight: { label: "Midnight (dark)", tokens: midnight },
139
+ slate: { label: "Slate (light)", tokens: slate }
140
+ };
141
+ const resolveTokens = (theme) => ({
142
+ ...(PRESETS[theme?.preset ?? "paper"] ?? PRESETS.paper).tokens,
143
+ ...pickEditableOverrides(theme?.overrides)
144
+ });
145
+ const tokensToCssVars = (tokens) => Object.fromEntries(Object.entries(tokens).map(([key, value]) => [`--rcu-${key}`, value]));
146
+ const themeToCssVars = (theme) => ({
147
+ ...tokensToCssVars(resolveTokens(theme)),
148
+ "--rcu-fontFamily": resolveFont(theme).stack
149
+ });
150
+ export {
151
+ EDITABLE_TOKENS as E,
152
+ FONTS as F,
153
+ PRESETS as P,
154
+ resolveTokens as a,
155
+ tokensToCssVars as b,
156
+ googleFontHref as g,
157
+ pickEditableOverrides as p,
158
+ resolveFont as r,
159
+ themeToCssVars as t,
160
+ useGoogleFont as u
161
+ };
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ const React = require("react");
3
+ function _interopNamespace(e) {
4
+ if (e && e.__esModule) return e;
5
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
6
+ if (e) {
7
+ for (const k in e) {
8
+ if (k !== "default") {
9
+ const d = Object.getOwnPropertyDescriptor(e, k);
10
+ Object.defineProperty(n, k, d.get ? d : {
11
+ enumerable: true,
12
+ get: () => e[k]
13
+ });
14
+ }
15
+ }
16
+ }
17
+ n.default = e;
18
+ return Object.freeze(n);
19
+ }
20
+ const React__namespace = /* @__PURE__ */ _interopNamespace(React);
21
+ const EDITABLE_TOKENS = [
22
+ { key: "pageBg", label: "Background", hint: "Top of the page gradient" },
23
+ { key: "pageBgDeep", label: "Background (gradient end)", hint: "Bottom of the page gradient" },
24
+ { key: "text", label: "Text", hint: "Headings, body text and filled buttons" },
25
+ { key: "textDim", label: "Secondary text", hint: "Hints, labels and captions" },
26
+ { key: "contrast", label: "Accent", hint: "Second colour in heading and button gradients" },
27
+ { key: "buttonText", label: "Button text", hint: "Text on filled buttons and step badges" }
28
+ ];
29
+ const EDITABLE_TOKEN_KEYS = EDITABLE_TOKENS.map((t) => t.key);
30
+ const pickEditableOverrides = (overrides) => Object.fromEntries(
31
+ Object.entries(overrides ?? {}).filter(
32
+ ([key]) => EDITABLE_TOKEN_KEYS.includes(key)
33
+ )
34
+ );
35
+ const SYSTEM_STACK = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`;
36
+ const FONTS = {
37
+ system: { label: "System default", stack: SYSTEM_STACK, google: null },
38
+ inter: {
39
+ label: "Inter",
40
+ stack: `'Inter', ${SYSTEM_STACK}`,
41
+ google: "Inter:wght@400;600;700;800"
42
+ },
43
+ "dm-sans": {
44
+ label: "DM Sans",
45
+ stack: `'DM Sans', ${SYSTEM_STACK}`,
46
+ google: "DM+Sans:wght@400;500;700"
47
+ },
48
+ poppins: {
49
+ label: "Poppins",
50
+ stack: `'Poppins', ${SYSTEM_STACK}`,
51
+ google: "Poppins:wght@400;600;700;800"
52
+ },
53
+ "space-grotesk": {
54
+ label: "Space Grotesk",
55
+ stack: `'Space Grotesk', ${SYSTEM_STACK}`,
56
+ google: "Space+Grotesk:wght@400;500;700"
57
+ },
58
+ "work-sans": {
59
+ label: "Work Sans",
60
+ stack: `'Work Sans', ${SYSTEM_STACK}`,
61
+ google: "Work+Sans:wght@400;600;700;800"
62
+ },
63
+ "libre-franklin": {
64
+ label: "Libre Franklin",
65
+ stack: `'Libre Franklin', ${SYSTEM_STACK}`,
66
+ google: "Libre+Franklin:wght@400;600;700;800"
67
+ },
68
+ "playfair-display": {
69
+ label: "Playfair Display (serif)",
70
+ stack: `'Playfair Display', Georgia, serif`,
71
+ google: "Playfair+Display:wght@400;600;700;800"
72
+ },
73
+ "ibm-plex-mono": {
74
+ label: "IBM Plex Mono",
75
+ stack: `'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace`,
76
+ google: "IBM+Plex+Mono:wght@400;500;700"
77
+ }
78
+ };
79
+ const DEFAULT_FONT = "system";
80
+ const resolveFont = (theme) => FONTS[theme?.fontFamily ?? DEFAULT_FONT] ?? FONTS[DEFAULT_FONT];
81
+ const googleFontHref = (font) => font.google ? `https://fonts.googleapis.com/css2?family=${font.google}&display=swap` : null;
82
+ const useGoogleFont = (href) => {
83
+ React__namespace.useEffect(() => {
84
+ if (!href || document.querySelector(`link[data-rcu-font][href="${href}"]`)) return;
85
+ const link = document.createElement("link");
86
+ link.rel = "stylesheet";
87
+ link.href = href;
88
+ link.setAttribute("data-rcu-font", "");
89
+ document.head.appendChild(link);
90
+ }, [href]);
91
+ };
92
+ const paper = {
93
+ pageBg: "#C0B7AF",
94
+ pageBgDeep: "#ADA39B",
95
+ text: "#322824",
96
+ textDim: "#5c524d",
97
+ contrast: "#000000",
98
+ card: "rgba(255, 255, 255, 0.26)",
99
+ cardBorder: "rgba(50, 40, 36, 0.22)",
100
+ inputBg: "rgba(255, 255, 255, 0.42)",
101
+ sunk: "rgba(50, 40, 36, 0.09)",
102
+ green: "#3f6b52",
103
+ amber: "#8a6a24",
104
+ red: "#8f3a2f",
105
+ mixcloud: "#3f6266",
106
+ soundcloud: "#8f5a2f",
107
+ buttonText: "#C0B7AF",
108
+ shadow: "rgba(50, 40, 36, 0.3)",
109
+ shadowSoft: "rgba(50, 40, 36, 0.16)",
110
+ vinylDark: "#1b1512",
111
+ vinylMid: "#2f2521"
112
+ };
113
+ const midnight = {
114
+ pageBg: "#150b2a",
115
+ pageBgDeep: "#0d0620",
116
+ text: "#f0e9fb",
117
+ textDim: "#9d92b8",
118
+ contrast: "#ff2e88",
119
+ card: "rgba(255, 255, 255, 0.05)",
120
+ cardBorder: "rgba(240, 233, 251, 0.16)",
121
+ inputBg: "rgba(255, 255, 255, 0.07)",
122
+ sunk: "rgba(0, 0, 0, 0.3)",
123
+ green: "#4fd08a",
124
+ amber: "#e8bb54",
125
+ red: "#ff5c4d",
126
+ mixcloud: "#52aab4",
127
+ soundcloud: "#ff7733",
128
+ buttonText: "#150b2a",
129
+ shadow: "rgba(0, 0, 0, 0.5)",
130
+ shadowSoft: "rgba(255, 46, 136, 0.25)",
131
+ vinylDark: "#07030f",
132
+ vinylMid: "#1e1433"
133
+ };
134
+ const slate = {
135
+ pageBg: "#dde3e8",
136
+ pageBgDeep: "#c6cfd6",
137
+ text: "#26313a",
138
+ textDim: "#5b6873",
139
+ contrast: "#0a5c8a",
140
+ card: "rgba(255, 255, 255, 0.45)",
141
+ cardBorder: "rgba(38, 49, 58, 0.2)",
142
+ inputBg: "rgba(255, 255, 255, 0.6)",
143
+ sunk: "rgba(38, 49, 58, 0.08)",
144
+ green: "#2e7d4f",
145
+ amber: "#9a6d1c",
146
+ red: "#a63a2e",
147
+ mixcloud: "#28707a",
148
+ soundcloud: "#b35f2a",
149
+ buttonText: "#eef2f5",
150
+ shadow: "rgba(38, 49, 58, 0.28)",
151
+ shadowSoft: "rgba(38, 49, 58, 0.14)",
152
+ vinylDark: "#161b1f",
153
+ vinylMid: "#2b343b"
154
+ };
155
+ const PRESETS = {
156
+ paper: { label: "Paper (light)", tokens: paper },
157
+ midnight: { label: "Midnight (dark)", tokens: midnight },
158
+ slate: { label: "Slate (light)", tokens: slate }
159
+ };
160
+ const resolveTokens = (theme) => ({
161
+ ...(PRESETS[theme?.preset ?? "paper"] ?? PRESETS.paper).tokens,
162
+ ...pickEditableOverrides(theme?.overrides)
163
+ });
164
+ const tokensToCssVars = (tokens) => Object.fromEntries(Object.entries(tokens).map(([key, value]) => [`--rcu-${key}`, value]));
165
+ const themeToCssVars = (theme) => ({
166
+ ...tokensToCssVars(resolveTokens(theme)),
167
+ "--rcu-fontFamily": resolveFont(theme).stack
168
+ });
169
+ exports.EDITABLE_TOKENS = EDITABLE_TOKENS;
170
+ exports.FONTS = FONTS;
171
+ exports.PRESETS = PRESETS;
172
+ exports.googleFontHref = googleFontHref;
173
+ exports.pickEditableOverrides = pickEditableOverrides;
174
+ exports.resolveFont = resolveFont;
175
+ exports.resolveTokens = resolveTokens;
176
+ exports.themeToCssVars = themeToCssVars;
177
+ exports.tokensToCssVars = tokensToCssVars;
178
+ exports.useGoogleFont = useGoogleFont;