@qodin-co/sol 0.1.1

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,372 @@
1
+ import { a as getSessionIsLive, c as setSessionTimeMinutes, i as clearSessionTimeMinutes, n as useSolarTheme, o as getSessionTimeMinutes, s as setSessionLive } from "../solar-theme-provider-WbsezzLH.js";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { createPortal } from "react-dom";
5
+
6
+ //#region src/devtools/timeline.ts
7
+ function formatTime(minutes) {
8
+ const h = Math.floor(minutes / 60) % 24;
9
+ const m = Math.round(minutes % 60);
10
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
11
+ }
12
+ function phaseForMinutes(minutes) {
13
+ if (minutes < 180) return "midnight";
14
+ if (minutes < 310) return "night";
15
+ if (minutes < 350) return "dawn";
16
+ if (minutes < 380) return "sunrise";
17
+ if (minutes < 710) return "morning";
18
+ if (minutes < 730) return "solar-noon";
19
+ if (minutes < 1020) return "afternoon";
20
+ if (minutes < 1080) return "sunset";
21
+ if (minutes < 1200) return "dusk";
22
+ return "night";
23
+ }
24
+ function buildSliderGradient(palettes) {
25
+ return `linear-gradient(90deg, ${[
26
+ palettes.midnight.bg[1],
27
+ palettes.night.bg[1],
28
+ palettes.dawn.bg[1],
29
+ palettes.sunrise.bg[1],
30
+ palettes.morning.bg[1],
31
+ palettes["solar-noon"].bg[1],
32
+ palettes.afternoon.bg[1],
33
+ palettes.sunset.bg[1],
34
+ palettes.dusk.bg[1],
35
+ palettes.night.bg[1],
36
+ palettes.midnight.bg[1]
37
+ ].join(", ")})`;
38
+ }
39
+
40
+ //#endregion
41
+ //#region src/devtools/solar-devtools.tsx
42
+ const OPEN_KEY = "sol-devtools-open";
43
+ function getStoredOpen(fallback) {
44
+ try {
45
+ const v = localStorage.getItem(OPEN_KEY);
46
+ if (v === "1") return true;
47
+ if (v === "0") return false;
48
+ return fallback;
49
+ } catch {
50
+ return fallback;
51
+ }
52
+ }
53
+ function setStoredOpen(open) {
54
+ try {
55
+ localStorage.setItem(OPEN_KEY, open ? "1" : "0");
56
+ } catch {}
57
+ }
58
+ const POSITION_STYLES = {
59
+ "bottom-left": {
60
+ left: 16,
61
+ right: "auto"
62
+ },
63
+ "bottom-center": {
64
+ left: "50%",
65
+ transform: "translateX(-50%)"
66
+ },
67
+ "bottom-right": {
68
+ right: 16,
69
+ left: "auto"
70
+ }
71
+ };
72
+ function SolarDevTools({ defaultOpen = false, position = "bottom-center", enabled = true }) {
73
+ const [mounted, setMounted] = useState(false);
74
+ useEffect(() => setMounted(true), []);
75
+ if (!enabled || !mounted) return null;
76
+ return createPortal(/* @__PURE__ */ jsx(DevToolsInner, {
77
+ defaultOpen,
78
+ position
79
+ }), document.body);
80
+ }
81
+ function DevToolsInner({ defaultOpen, position }) {
82
+ const { setOverridePhase, setSimulatedDate, activeSkin, solarPosition } = useSolarTheme();
83
+ const [open, setOpen] = useState(() => getStoredOpen(defaultOpen));
84
+ const toggleOpen = useCallback(() => {
85
+ setOpen((prev) => {
86
+ const next = !prev;
87
+ setStoredOpen(next);
88
+ return next;
89
+ });
90
+ }, []);
91
+ const [timeMinutes, setTimeMinutes] = useState(720);
92
+ const [useLive, setUseLive] = useState(true);
93
+ useEffect(() => {
94
+ if (getSessionIsLive()) {
95
+ setUseLive(true);
96
+ const now$1 = /* @__PURE__ */ new Date();
97
+ setTimeMinutes(now$1.getHours() * 60 + now$1.getMinutes());
98
+ return;
99
+ }
100
+ const saved = getSessionTimeMinutes();
101
+ if (saved !== null) {
102
+ setTimeMinutes(saved);
103
+ setUseLive(false);
104
+ const d = /* @__PURE__ */ new Date();
105
+ d.setHours(0, 0, 0, 0);
106
+ d.setMinutes(saved);
107
+ setSimulatedDate(d);
108
+ return;
109
+ }
110
+ const now = /* @__PURE__ */ new Date();
111
+ setTimeMinutes(now.getHours() * 60 + now.getMinutes());
112
+ setUseLive(true);
113
+ setSessionLive(true);
114
+ }, []);
115
+ const providerSolarPhase = solarPosition?.phase;
116
+ const sliderPhase = useLive ? providerSolarPhase ?? phaseForMinutes(timeMinutes) : phaseForMinutes(timeMinutes);
117
+ useEffect(() => {
118
+ setOverridePhase(sliderPhase);
119
+ }, [sliderPhase, setOverridePhase]);
120
+ const goLive = useCallback(() => {
121
+ const now = /* @__PURE__ */ new Date();
122
+ setTimeMinutes(now.getHours() * 60 + now.getMinutes());
123
+ setUseLive(true);
124
+ setOverridePhase(null);
125
+ setSimulatedDate(void 0);
126
+ clearSessionTimeMinutes();
127
+ setSessionLive(true);
128
+ }, [setOverridePhase, setSimulatedDate]);
129
+ const handleSliderChange = useCallback((val) => {
130
+ setUseLive(false);
131
+ setTimeMinutes(val);
132
+ setSessionTimeMinutes(val);
133
+ setSessionLive(false);
134
+ const d = /* @__PURE__ */ new Date();
135
+ d.setHours(0, 0, 0, 0);
136
+ d.setMinutes(val);
137
+ setSimulatedDate(d);
138
+ }, [setSimulatedDate]);
139
+ const activePalettes = activeSkin.widgetPalettes;
140
+ const currentPalette = activePalettes[sliderPhase];
141
+ const gradient = useMemo(() => buildSliderGradient(activePalettes), [activePalettes]);
142
+ const accent = currentPalette.accentColor;
143
+ const textPrimary = currentPalette.textColor;
144
+ const bgColor = currentPalette.bg[1];
145
+ const isLight = currentPalette.mode === "light";
146
+ const posStyles = POSITION_STYLES[position] ?? POSITION_STYLES["bottom-center"];
147
+ if (!open) return /* @__PURE__ */ jsxs("button", {
148
+ type: "button",
149
+ onClick: toggleOpen,
150
+ style: {
151
+ position: "fixed",
152
+ bottom: 16,
153
+ ...posStyles,
154
+ zIndex: 99999,
155
+ display: "flex",
156
+ alignItems: "center",
157
+ gap: 8,
158
+ padding: "8px 16px",
159
+ borderRadius: 9999,
160
+ border: `1px solid ${isLight ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.12)"}`,
161
+ background: bgColor,
162
+ color: textPrimary,
163
+ fontSize: 13,
164
+ fontWeight: 500,
165
+ fontFamily: "inherit",
166
+ cursor: "pointer",
167
+ boxShadow: "0 4px 24px rgba(0,0,0,0.25)",
168
+ transition: "background 0.3s, color 0.3s, border-color 0.3s"
169
+ },
170
+ children: [/* @__PURE__ */ jsx("span", { style: {
171
+ width: 8,
172
+ height: 8,
173
+ borderRadius: "50%",
174
+ background: useLive ? "#22c55e" : "#f59e0b",
175
+ flexShrink: 0
176
+ } }), /* @__PURE__ */ jsx("span", {
177
+ style: { textTransform: "capitalize" },
178
+ children: sliderPhase.replace("-", " ")
179
+ })]
180
+ });
181
+ return /* @__PURE__ */ jsxs("div", {
182
+ style: {
183
+ position: "fixed",
184
+ bottom: 16,
185
+ ...posStyles,
186
+ zIndex: 99999,
187
+ width: 380,
188
+ maxWidth: "calc(100vw - 32px)",
189
+ borderRadius: 16,
190
+ border: `1px solid ${isLight ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.12)"}`,
191
+ background: bgColor,
192
+ color: textPrimary,
193
+ boxShadow: "0 8px 40px rgba(0,0,0,0.35)",
194
+ fontFamily: "inherit",
195
+ transition: "background 0.3s, color 0.3s, border-color 0.3s",
196
+ overflow: "hidden"
197
+ },
198
+ children: [/* @__PURE__ */ jsxs("button", {
199
+ type: "button",
200
+ onClick: toggleOpen,
201
+ style: {
202
+ display: "flex",
203
+ alignItems: "center",
204
+ gap: 8,
205
+ width: "100%",
206
+ padding: "12px 16px",
207
+ background: "none",
208
+ border: "none",
209
+ color: "inherit",
210
+ fontSize: 13,
211
+ fontWeight: 500,
212
+ fontFamily: "inherit",
213
+ cursor: "pointer",
214
+ borderBottom: `1px solid ${isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.08)"}`
215
+ },
216
+ children: [
217
+ /* @__PURE__ */ jsx("span", { style: {
218
+ width: 8,
219
+ height: 8,
220
+ borderRadius: "50%",
221
+ background: useLive ? "#22c55e" : "#f59e0b",
222
+ flexShrink: 0
223
+ } }),
224
+ /* @__PURE__ */ jsx("span", {
225
+ style: {
226
+ flex: 1,
227
+ textAlign: "left",
228
+ textTransform: "capitalize"
229
+ },
230
+ children: sliderPhase.replace("-", " ")
231
+ }),
232
+ /* @__PURE__ */ jsx("span", {
233
+ style: {
234
+ fontSize: 11,
235
+ opacity: .5
236
+ },
237
+ children: "▾ collapse"
238
+ })
239
+ ]
240
+ }), /* @__PURE__ */ jsxs("div", {
241
+ style: { padding: 16 },
242
+ children: [
243
+ /* @__PURE__ */ jsxs("div", {
244
+ style: {
245
+ display: "flex",
246
+ alignItems: "center",
247
+ justifyContent: "space-between",
248
+ marginBottom: 12
249
+ },
250
+ children: [/* @__PURE__ */ jsx("span", {
251
+ style: {
252
+ fontSize: 10,
253
+ textTransform: "uppercase",
254
+ letterSpacing: "0.05em",
255
+ opacity: .5
256
+ },
257
+ children: "Time of Day"
258
+ }), /* @__PURE__ */ jsxs("div", {
259
+ style: {
260
+ display: "flex",
261
+ alignItems: "center",
262
+ gap: 8
263
+ },
264
+ children: [!useLive && /* @__PURE__ */ jsxs("span", {
265
+ style: {
266
+ fontSize: 13,
267
+ fontWeight: 500,
268
+ fontVariantNumeric: "tabular-nums"
269
+ },
270
+ children: [formatTime(timeMinutes), /* @__PURE__ */ jsx("span", {
271
+ style: {
272
+ marginLeft: 8,
273
+ fontSize: 11,
274
+ opacity: .6,
275
+ textTransform: "capitalize"
276
+ },
277
+ children: sliderPhase.replace("-", " ")
278
+ })]
279
+ }), /* @__PURE__ */ jsx("button", {
280
+ type: "button",
281
+ onClick: goLive,
282
+ disabled: useLive,
283
+ style: {
284
+ padding: "4px 10px",
285
+ borderRadius: 8,
286
+ fontSize: 10,
287
+ fontWeight: 500,
288
+ textTransform: "uppercase",
289
+ letterSpacing: "0.05em",
290
+ fontFamily: "inherit",
291
+ border: `1px solid ${useLive ? "transparent" : isLight ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)"}`,
292
+ background: useLive ? accent : isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)",
293
+ color: useLive ? isLight ? "#fff" : "#000" : isLight ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.5)",
294
+ cursor: useLive ? "default" : "pointer",
295
+ transition: "all 0.2s"
296
+ },
297
+ children: useLive ? "● Live" : "Go Live"
298
+ })]
299
+ })]
300
+ }),
301
+ /* @__PURE__ */ jsxs("div", {
302
+ style: { position: "relative" },
303
+ children: [
304
+ /* @__PURE__ */ jsx("div", { style: {
305
+ height: 8,
306
+ borderRadius: 9999,
307
+ width: "100%",
308
+ background: gradient,
309
+ opacity: .6
310
+ } }),
311
+ /* @__PURE__ */ jsx("input", {
312
+ type: "range",
313
+ min: 0,
314
+ max: 1439,
315
+ step: 5,
316
+ value: timeMinutes,
317
+ onChange: (e) => handleSliderChange(Number(e.target.value)),
318
+ style: {
319
+ position: "absolute",
320
+ inset: 0,
321
+ width: "100%",
322
+ height: "100%",
323
+ opacity: 0,
324
+ cursor: "pointer",
325
+ margin: 0
326
+ }
327
+ }),
328
+ /* @__PURE__ */ jsx("div", { style: {
329
+ position: "absolute",
330
+ top: "50%",
331
+ width: 16,
332
+ height: 16,
333
+ borderRadius: "50%",
334
+ pointerEvents: "none",
335
+ left: `${timeMinutes / 1439 * 100}%`,
336
+ transform: "translate(-50%, -50%)",
337
+ background: accent,
338
+ boxShadow: `0 0 8px ${accent}, 0 2px 8px rgba(0,0,0,0.3)`,
339
+ border: "2px solid rgba(255,255,255,0.6)",
340
+ transition: "background 0.3s, box-shadow 0.3s"
341
+ } })
342
+ ]
343
+ }),
344
+ /* @__PURE__ */ jsx("div", {
345
+ style: {
346
+ display: "flex",
347
+ justifyContent: "space-between",
348
+ marginTop: 8
349
+ },
350
+ children: [
351
+ 0,
352
+ 6,
353
+ 12,
354
+ 18,
355
+ 24
356
+ ].map((h) => /* @__PURE__ */ jsxs("span", {
357
+ style: {
358
+ fontSize: 10,
359
+ fontVariantNumeric: "tabular-nums",
360
+ opacity: .4
361
+ },
362
+ children: [String(h % 24).padStart(2, "0"), ":00"]
363
+ }, h))
364
+ })
365
+ ]
366
+ })]
367
+ });
368
+ }
369
+
370
+ //#endregion
371
+ export { SolarDevTools };
372
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["POSITION_STYLES: Record<string, React.CSSProperties>","now","sliderPhase: SolarPhase"],"sources":["../../src/devtools/timeline.ts","../../src/devtools/solar-devtools.tsx"],"sourcesContent":["// src/devtools/timeline.ts\n//\n// Extracted helpers from showcase-content.client.tsx for the devtools timeline.\n\nimport type { SolarPhase } from '../hooks/useSolarPosition';\nimport type { WidgetPalette } from '../skins/index';\n\nexport const PHASES: SolarPhase[] = [\n 'midnight',\n 'night',\n 'dawn',\n 'sunrise',\n 'morning',\n 'solar-noon',\n 'afternoon',\n 'sunset',\n 'dusk',\n];\n\nexport function formatTime(minutes: number) {\n const h = Math.floor(minutes / 60) % 24;\n const m = Math.round(minutes % 60);\n return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;\n}\n\nexport function phaseForMinutes(minutes: number): SolarPhase {\n if (minutes < 180) return 'midnight';\n if (minutes < 310) return 'night';\n if (minutes < 350) return 'dawn';\n if (minutes < 380) return 'sunrise';\n if (minutes < 710) return 'morning';\n if (minutes < 730) return 'solar-noon';\n if (minutes < 1020) return 'afternoon';\n if (minutes < 1080) return 'sunset';\n if (minutes < 1200) return 'dusk';\n return 'night';\n}\n\nexport function buildSliderGradient(palettes: Record<SolarPhase, WidgetPalette>) {\n return `linear-gradient(90deg, ${[\n palettes.midnight.bg[1],\n palettes.night.bg[1],\n palettes.dawn.bg[1],\n palettes.sunrise.bg[1],\n palettes.morning.bg[1],\n palettes['solar-noon'].bg[1],\n palettes.afternoon.bg[1],\n palettes.sunset.bg[1],\n palettes.dusk.bg[1],\n palettes.night.bg[1],\n palettes.midnight.bg[1],\n ].join(', ')})`;\n}\n","'use client';\n\n// src/devtools/solar-devtools.tsx\n//\n// Bottom-fixed dev pill for scrubbing solar phases.\n// Reads the active skin's widgetPalettes so the timeline gradient\n// changes colour automatically when the user switches skins.\n//\n// Calls ctx.setSimulatedDate when scrubbing so every SolarWidget and\n// CompactWidget in the tree picks up the simulated time and moves its\n// orb to the correct arc position — without requiring any prop wiring\n// at the call site.\n\nimport type React from 'react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { SolarPhase } from '../hooks/useSolarPosition';\nimport {\n clearSessionTimeMinutes,\n getSessionIsLive,\n getSessionTimeMinutes,\n setSessionLive,\n setSessionTimeMinutes,\n} from '../lib/solar-phase-session';\nimport { useSolarTheme } from '../provider/solar-theme-provider';\nimport { buildSliderGradient, formatTime, phaseForMinutes } from './timeline';\n\n// ─── Props ────────────────────────────────────────────────────────────────────\n\nexport interface SolarDevToolsProps {\n defaultOpen?: boolean;\n position?: 'bottom-left' | 'bottom-center' | 'bottom-right';\n enabled?: boolean;\n}\n\n// ─── Storage helpers for open/collapsed state ─────────────────────────────────\n\nconst OPEN_KEY = 'sol-devtools-open';\n\nfunction getStoredOpen(fallback: boolean): boolean {\n try {\n const v = localStorage.getItem(OPEN_KEY);\n if (v === '1') return true;\n if (v === '0') return false;\n return fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction setStoredOpen(open: boolean): void {\n try {\n localStorage.setItem(OPEN_KEY, open ? '1' : '0');\n } catch {}\n}\n\n// ─── Positioning map ──────────────────────────────────────────────────────────\n\nconst POSITION_STYLES: Record<string, React.CSSProperties> = {\n 'bottom-left': { left: 16, right: 'auto' },\n 'bottom-center': { left: '50%', transform: 'translateX(-50%)' },\n 'bottom-right': { right: 16, left: 'auto' },\n};\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function SolarDevTools({\n defaultOpen = false,\n position = 'bottom-center',\n enabled = true,\n}: SolarDevToolsProps) {\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n\n if (!enabled || !mounted) return null;\n return createPortal(\n <DevToolsInner defaultOpen={defaultOpen} position={position} />,\n document.body,\n );\n}\n\n// ─── Inner (rendered inside portal) ───────────────────────────────────────────\n\nfunction DevToolsInner({\n defaultOpen,\n position,\n}: {\n defaultOpen: boolean;\n position: 'bottom-left' | 'bottom-center' | 'bottom-right';\n}) {\n const { setOverridePhase, setSimulatedDate, activeSkin, solarPosition } = useSolarTheme();\n\n // ── Open / collapsed ────────────────────────────────────────────────────────\n const [open, setOpen] = useState(() => getStoredOpen(defaultOpen));\n const toggleOpen = useCallback(() => {\n setOpen((prev) => {\n const next = !prev;\n setStoredOpen(next);\n return next;\n });\n }, []);\n\n // ── Time state ───────────────────────────────────────────────────────────────\n const [timeMinutes, setTimeMinutes] = useState(720);\n const [useLive, setUseLive] = useState(true);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: setSimulatedDate is stable\n useEffect(() => {\n if (getSessionIsLive()) {\n setUseLive(true);\n const now = new Date();\n setTimeMinutes(now.getHours() * 60 + now.getMinutes());\n return;\n }\n const saved = getSessionTimeMinutes();\n if (saved !== null) {\n setTimeMinutes(saved);\n setUseLive(false);\n // Restore simulatedDate on mount so widgets are immediately correct\n const d = new Date();\n d.setHours(0, 0, 0, 0);\n d.setMinutes(saved);\n setSimulatedDate(d);\n return;\n }\n const now = new Date();\n setTimeMinutes(now.getHours() * 60 + now.getMinutes());\n setUseLive(true);\n setSessionLive(true);\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n // ^ setSimulatedDate is stable (useCallback in provider), safe to omit\n\n // ── Phase resolution ────────────────────────────────────────────────────────\n const providerSolarPhase = solarPosition?.phase;\n const sliderPhase: SolarPhase = useLive\n ? (providerSolarPhase ?? phaseForMinutes(timeMinutes))\n : phaseForMinutes(timeMinutes);\n\n // ── Sync phase to provider ───────────────────────────────────────────────────\n useEffect(() => {\n setOverridePhase(sliderPhase);\n }, [sliderPhase, setOverridePhase]);\n\n // ── Go Live ─────────────────────────────────────────────────────────────────\n const goLive = useCallback(() => {\n const now = new Date();\n const mins = now.getHours() * 60 + now.getMinutes();\n setTimeMinutes(mins);\n setUseLive(true);\n setOverridePhase(null);\n setSimulatedDate(undefined); // clear — widgets return to real current time\n clearSessionTimeMinutes();\n setSessionLive(true);\n }, [setOverridePhase, setSimulatedDate]);\n\n // ── Slider onChange ──────────────────────────────────────────────────────────\n const handleSliderChange = useCallback(\n (val: number) => {\n setUseLive(false);\n setTimeMinutes(val);\n setSessionTimeMinutes(val);\n setSessionLive(false);\n // Push simulated date into context so every widget's orb moves\n const d = new Date();\n d.setHours(0, 0, 0, 0);\n d.setMinutes(val);\n setSimulatedDate(d);\n },\n [setSimulatedDate],\n );\n\n // ── Skin-aware palettes ─────────────────────────────────────────────────────\n const activePalettes = activeSkin.widgetPalettes;\n const currentPalette = activePalettes[sliderPhase];\n const gradient = useMemo(() => buildSliderGradient(activePalettes), [activePalettes]);\n\n // ── Accent from current skin + phase ────────────────────────────────────────\n const accent = currentPalette.accentColor;\n const textPrimary = currentPalette.textColor;\n const bgColor = currentPalette.bg[1];\n const isLight = currentPalette.mode === 'light';\n\n // ── Position styles ─────────────────────────────────────────────────────────\n const posStyles = POSITION_STYLES[position] ?? POSITION_STYLES['bottom-center'];\n\n // ── Collapsed pill ──────────────────────────────────────────────────────────\n if (!open) {\n return (\n <button\n type=\"button\"\n onClick={toggleOpen}\n style={{\n position: 'fixed',\n bottom: 16,\n ...posStyles,\n zIndex: 99999,\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '8px 16px',\n borderRadius: 9999,\n border: `1px solid ${isLight ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.12)'}`,\n background: bgColor,\n color: textPrimary,\n fontSize: 13,\n fontWeight: 500,\n fontFamily: 'inherit',\n cursor: 'pointer',\n boxShadow: '0 4px 24px rgba(0,0,0,0.25)',\n transition: 'background 0.3s, color 0.3s, border-color 0.3s',\n }}\n >\n <span\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: useLive ? '#22c55e' : '#f59e0b',\n flexShrink: 0,\n }}\n />\n <span style={{ textTransform: 'capitalize' }}>{sliderPhase.replace('-', ' ')}</span>\n </button>\n );\n }\n\n // ── Expanded panel ──────────────────────────────────────────────────────────\n return (\n <div\n style={{\n position: 'fixed',\n bottom: 16,\n ...posStyles,\n zIndex: 99999,\n width: 380,\n maxWidth: 'calc(100vw - 32px)',\n borderRadius: 16,\n border: `1px solid ${isLight ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.12)'}`,\n background: bgColor,\n color: textPrimary,\n boxShadow: '0 8px 40px rgba(0,0,0,0.35)',\n fontFamily: 'inherit',\n transition: 'background 0.3s, color 0.3s, border-color 0.3s',\n overflow: 'hidden',\n }}\n >\n {/* Header — click to collapse */}\n <button\n type=\"button\"\n onClick={toggleOpen}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n width: '100%',\n padding: '12px 16px',\n background: 'none',\n border: 'none',\n color: 'inherit',\n fontSize: 13,\n fontWeight: 500,\n fontFamily: 'inherit',\n cursor: 'pointer',\n borderBottom: `1px solid ${isLight ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.08)'}`,\n }}\n >\n <span\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: useLive ? '#22c55e' : '#f59e0b',\n flexShrink: 0,\n }}\n />\n <span style={{ flex: 1, textAlign: 'left', textTransform: 'capitalize' }}>\n {sliderPhase.replace('-', ' ')}\n </span>\n <span style={{ fontSize: 11, opacity: 0.5 }}>▾ collapse</span>\n </button>\n\n {/* Panel body */}\n <div style={{ padding: 16 }}>\n {/* Live button + time display */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginBottom: 12,\n }}\n >\n <span\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n opacity: 0.5,\n }}\n >\n Time of Day\n </span>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n {!useLive && (\n <span style={{ fontSize: 13, fontWeight: 500, fontVariantNumeric: 'tabular-nums' }}>\n {formatTime(timeMinutes)}\n <span\n style={{ marginLeft: 8, fontSize: 11, opacity: 0.6, textTransform: 'capitalize' }}\n >\n {sliderPhase.replace('-', ' ')}\n </span>\n </span>\n )}\n <button\n type=\"button\"\n onClick={goLive}\n disabled={useLive}\n style={{\n padding: '4px 10px',\n borderRadius: 8,\n fontSize: 10,\n fontWeight: 500,\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n fontFamily: 'inherit',\n border: `1px solid ${useLive ? 'transparent' : isLight ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.1)'}`,\n background: useLive\n ? accent\n : isLight\n ? 'rgba(0,0,0,0.04)'\n : 'rgba(255,255,255,0.06)',\n color: useLive\n ? isLight\n ? '#fff'\n : '#000'\n : isLight\n ? 'rgba(0,0,0,0.5)'\n : 'rgba(255,255,255,0.5)',\n cursor: useLive ? 'default' : 'pointer',\n transition: 'all 0.2s',\n }}\n >\n {useLive ? '● Live' : 'Go Live'}\n </button>\n </div>\n </div>\n\n {/* Gradient slider */}\n <div style={{ position: 'relative' }}>\n <div\n style={{\n height: 8,\n borderRadius: 9999,\n width: '100%',\n background: gradient,\n opacity: 0.6,\n }}\n />\n <input\n type=\"range\"\n min={0}\n max={1439}\n step={5}\n value={timeMinutes}\n onChange={(e) => handleSliderChange(Number(e.target.value))}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n opacity: 0,\n cursor: 'pointer',\n margin: 0,\n }}\n />\n <div\n style={{\n position: 'absolute',\n top: '50%',\n width: 16,\n height: 16,\n borderRadius: '50%',\n pointerEvents: 'none',\n left: `${(timeMinutes / 1439) * 100}%`,\n transform: 'translate(-50%, -50%)',\n background: accent,\n boxShadow: `0 0 8px ${accent}, 0 2px 8px rgba(0,0,0,0.3)`,\n border: '2px solid rgba(255,255,255,0.6)',\n transition: 'background 0.3s, box-shadow 0.3s',\n }}\n />\n </div>\n\n {/* Hour markers */}\n <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 8 }}>\n {[0, 6, 12, 18, 24].map((h) => (\n <span\n key={h}\n style={{\n fontSize: 10,\n fontVariantNumeric: 'tabular-nums',\n opacity: 0.4,\n }}\n >\n {String(h % 24).padStart(2, '0')}:00\n </span>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}