@circadian/sol 0.2.8 → 0.2.10

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["WMO_MAP: Record<number, WeatherCategory>","fetchWeather","WMO_MAP","useWeatherData","finalPalette: WidgetPalette","opts: Intl.DateTimeFormatOptions","liveWeatherCategory: WeatherCategory | null","liveTemperatureC: number | null","WMO_MAP: Record<number, WeatherCategory>","palette: WidgetPalette","liveWeatherCategory: WeatherCategory | null","liveTemperatureC: number | null","props: CompactSkinProps","opts: Intl.DateTimeFormatOptions"],"sources":["../src/widgets/solar-widget.shell.tsx","../src/widgets/compact-widget.shell.tsx"],"sourcesContent":["'use client';\n\n/**\n * widgets/solar-widget.shell.tsx (skin-aware shell)\n *\n * Weather fetch is now centralised here alongside the compact shell:\n * - Fetches temperature + weather_code from open-meteo once per mount\n * - Resolves liveWeatherCategory: weatherCategoryOverride ?? live category\n * - Passes liveWeatherCategory + liveTemperatureC down to skin components\n * - Skins should prefer these props over running their own fetch\n *\n * simulatedDate prop wins over ctx.simulatedDate (set by SolarDevTools),\n * which wins over undefined (live time). This means the devtools timeline\n * scrubber moves the orb in every widget without requiring any prop wiring.\n */\n\nimport { useEffect, useLayoutEffect, useMemo, useState } from 'react';\nimport type { SolarPhase } from '../hooks/useSolarPosition';\nimport { lerpHex } from '../lib/solar-lerp';\nimport { useSolarTheme } from '../provider/solar-theme-provider';\nimport type { DesignMode, WidgetPalette } from '../skins/types/widget-skin.types';\n\n// ─── Re-exported types ────────────────────────────────────────────────────────\n\nexport type ExpandDirection =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport type WidgetSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\nexport type WeatherCategory =\n | 'clear'\n | 'partly-cloudy'\n | 'overcast'\n | 'fog'\n | 'drizzle'\n | 'rain'\n | 'heavy-rain'\n | 'snow'\n | 'heavy-snow'\n | 'thunder';\n\nexport type CustomPalettes = Partial<Record<SolarPhase, { bg: [string, string, string] }>>;\n\nimport { SKINS } from '../skins/index';\nexport const PALETTES = SKINS.foundry.widgetPalettes;\n\n// ─── WMO weather code map ─────────────────────────────────────────────────────\n\nconst WMO_MAP: Record<number, WeatherCategory> = {\n 0: 'clear',\n 1: 'clear',\n 2: 'partly-cloudy',\n 3: 'overcast',\n 45: 'fog',\n 48: 'fog',\n 51: 'drizzle',\n 53: 'drizzle',\n 55: 'drizzle',\n 61: 'rain',\n 63: 'rain',\n 65: 'heavy-rain',\n 71: 'snow',\n 73: 'snow',\n 75: 'heavy-snow',\n 80: 'rain',\n 82: 'heavy-rain',\n 85: 'snow',\n 86: 'heavy-snow',\n 95: 'thunder',\n 96: 'thunder',\n 99: 'thunder',\n};\n\ninterface LiveWeather {\n temperatureC: number;\n category: WeatherCategory;\n}\n\nasync function fetchWeather(lat: number, lon: number): Promise<LiveWeather> {\n const url = new URL('https://api.open-meteo.com/v1/forecast');\n url.searchParams.set('latitude', String(lat));\n url.searchParams.set('longitude', String(lon));\n url.searchParams.set('current', 'temperature_2m,weather_code');\n url.searchParams.set('forecast_days', '1');\n const data = (await fetch(url.toString()).then((r) => r.json())) as {\n current: { temperature_2m: number; weather_code: number };\n };\n return {\n temperatureC: Math.round(data.current.temperature_2m),\n category: WMO_MAP[data.current.weather_code] ?? 'clear',\n };\n}\n\nfunction useWeatherData(lat: number | null, lon: number | null) {\n const [weather, setWeather] = useState<LiveWeather | null>(null);\n useEffect(() => {\n if (!lat || !lon) return;\n let dead = false;\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {});\n const id = setInterval(\n () =>\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {}),\n 30 * 60 * 1000,\n );\n return () => {\n dead = true;\n clearInterval(id);\n };\n }, [lat, lon]);\n return weather;\n}\n\n// ─── Widget props ─────────────────────────────────────────────────────────────\n\nexport interface SolarWidgetProps {\n expandDirection?: ExpandDirection;\n size?: WidgetSize;\n showFlag?: boolean;\n showWeather?: boolean;\n hoverEffect?: boolean;\n customPalettes?: CustomPalettes;\n phaseOverride?: SolarPhase;\n weatherCategoryOverride?: WeatherCategory | null;\n /** Explicit simulated date. Falls back to ctx.simulatedDate (from SolarDevTools)\n * then to real current time. */\n simulatedDate?: Date;\n /** Lock the widget state: `true` = always expanded, `false` = always collapsed.\n * Omit or pass `undefined` for default localStorage-driven behaviour. */\n forceExpanded?: boolean;\n}\n\n// ─── Palette blending helper ──────────────────────────────────────────────────\n\nfunction blendWidgetPalette(from: WidgetPalette, to: WidgetPalette, t: number): WidgetPalette {\n return {\n bg: [\n lerpHex(from.bg[0], to.bg[0], t),\n lerpHex(from.bg[1], to.bg[1], t),\n lerpHex(from.bg[2], to.bg[2], t),\n ],\n textColor: lerpHex(from.textColor, to.textColor, t),\n accentColor: lerpHex(from.accentColor, to.accentColor, t),\n orb: lerpHex(from.orb, to.orb, t),\n outerGlow: from.outerGlow,\n mode: t < 0.5 ? from.mode : to.mode,\n };\n}\n\n// ─── Shell component ──────────────────────────────────────────────────────────\n\nexport function SolarWidget({\n expandDirection = 'bottom-right',\n size = 'lg',\n showFlag = false,\n showWeather = false,\n hoverEffect = false,\n customPalettes,\n phaseOverride,\n weatherCategoryOverride,\n simulatedDate: simulatedDateProp,\n forceExpanded,\n}: SolarWidgetProps) {\n const [mounted, setMounted] = useState(false);\n useLayoutEffect(() => setMounted(true), []);\n\n const {\n phase,\n blend,\n activeSkin,\n timezone,\n latitude,\n longitude,\n simulatedDate: ctxSimulatedDate,\n setCustomPalettes,\n } = useSolarTheme();\n\n // Register customPalettes into context so DevTools can read them\n useEffect(() => {\n setCustomPalettes(customPalettes);\n return () => setCustomPalettes(undefined);\n }, [customPalettes, setCustomPalettes]);\n\n // prop wins → context (devtools) → undefined (live)\n const simulatedDate = simulatedDateProp ?? ctxSimulatedDate;\n\n const [expanded, setExpanded] = useState(false);\n\n const activePhase = phaseOverride ?? phase;\n const activeBlend = phaseOverride\n ? { phase: phaseOverride, nextPhase: phaseOverride, t: 0 }\n : blend;\n\n const fromPalette = activeSkin.widgetPalettes[activeBlend.phase];\n const toPalette = activeSkin.widgetPalettes[activeBlend.nextPhase];\n\n const blendedPalette = useMemo(\n () => blendWidgetPalette(fromPalette, toPalette, activeBlend.t),\n [fromPalette, toPalette, activeBlend.t],\n );\n\n const finalPalette: WidgetPalette = useMemo(() => {\n if (!customPalettes?.[activePhase]) return blendedPalette;\n return { ...blendedPalette, bg: customPalettes[activePhase]?.bg };\n }, [blendedPalette, customPalettes, activePhase]);\n\n const time = useMemo(() => {\n const d = simulatedDate ?? new Date();\n const opts: Intl.DateTimeFormatOptions = {\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n };\n if (timezone) opts.timeZone = timezone;\n const parts = new Intl.DateTimeFormat('en-GB', opts).formatToParts(d);\n const hh = parts.find((p) => p.type === 'hour')?.value ?? '00';\n const mm = parts.find((p) => p.type === 'minute')?.value ?? '00';\n return `${hh}:${mm}`;\n }, [simulatedDate, timezone]);\n\n // ── Centralised weather fetch ────────────────────────────────────────────\n const liveWeather = useWeatherData(latitude ?? null, longitude ?? null);\n\n const liveWeatherCategory: WeatherCategory | null = showWeather\n ? (weatherCategoryOverride ?? liveWeather?.category ?? null)\n : null;\n\n const liveTemperatureC: number | null = liveWeather?.temperatureC ?? null;\n\n const SkinComponent = activeSkin.Component;\n\n return (\n <div style={{ visibility: mounted ? 'visible' : 'hidden' }}>\n <SkinComponent\n phase={activePhase}\n blend={activeBlend}\n expanded={expanded}\n onToggle={() => setExpanded((v) => !v)}\n expandDirection={expandDirection}\n size={size}\n time={time}\n location=\"\"\n showFlag={showFlag}\n showWeather={showWeather}\n hoverEffect={hoverEffect}\n weather={weatherCategoryOverride}\n liveWeatherCategory={liveWeatherCategory}\n liveTemperatureC={liveTemperatureC}\n palette={finalPalette}\n latitude={latitude}\n longitude={longitude}\n timezone={timezone}\n simulatedDate={simulatedDate}\n forceExpanded={forceExpanded}\n />\n </div>\n );\n}\n","'use client';\n\n/**\n * widgets/compact-widget.shell.tsx\n *\n * Shell for the compact solar widget — a slim pill/bar format.\n *\n * simulatedDate prop wins over ctx.simulatedDate (set by SolarDevTools),\n * which wins over undefined (live time). This means the devtools timeline\n * scrubber moves the orb in compact widgets without requiring any prop wiring.\n */\n\nimport { useEffect, useLayoutEffect, useMemo, useState } from 'react';\nimport type { SolarBlend, SolarPhase } from '../hooks/useSolarPosition';\nimport { lerpHex } from '../lib/solar-lerp';\nimport { useSolarTheme } from '../provider/solar-theme-provider';\nimport type { DesignMode, SkinDefinition, WidgetPalette } from '../skins/types/widget-skin.types';\nimport type { CustomPalettes } from './solar-widget.shell';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type WeatherCategory =\n | 'clear'\n | 'partly-cloudy'\n | 'overcast'\n | 'fog'\n | 'drizzle'\n | 'rain'\n | 'heavy-rain'\n | 'snow'\n | 'heavy-snow'\n | 'thunder';\n\nexport type CompactSize = 'sm' | 'md' | 'lg';\n\n// ─── WMO weather code map ─────────────────────────────────────────────────────\n\nconst WMO_MAP: Record<number, WeatherCategory> = {\n 0: 'clear',\n 1: 'clear',\n 2: 'partly-cloudy',\n 3: 'overcast',\n 45: 'fog',\n 48: 'fog',\n 51: 'drizzle',\n 53: 'drizzle',\n 55: 'drizzle',\n 61: 'rain',\n 63: 'rain',\n 65: 'heavy-rain',\n 71: 'snow',\n 73: 'snow',\n 75: 'heavy-snow',\n 80: 'rain',\n 82: 'heavy-rain',\n 85: 'snow',\n 86: 'heavy-snow',\n 95: 'thunder',\n 96: 'thunder',\n 99: 'thunder',\n};\n\n// ─── Centralised weather fetch ────────────────────────────────────────────────\n\ninterface LiveWeather {\n temperatureC: number;\n category: WeatherCategory;\n}\n\nasync function fetchWeather(lat: number, lon: number): Promise<LiveWeather> {\n const url = new URL('https://api.open-meteo.com/v1/forecast');\n url.searchParams.set('latitude', String(lat));\n url.searchParams.set('longitude', String(lon));\n url.searchParams.set('current', 'temperature_2m,weather_code');\n url.searchParams.set('forecast_days', '1');\n const data = (await fetch(url.toString()).then((r) => r.json())) as {\n current: { temperature_2m: number; weather_code: number };\n };\n return {\n temperatureC: Math.round(data.current.temperature_2m),\n category: WMO_MAP[data.current.weather_code] ?? 'clear',\n };\n}\n\nfunction useWeatherData(lat: number | null, lon: number | null) {\n const [weather, setWeather] = useState<LiveWeather | null>(null);\n useEffect(() => {\n if (!lat || !lon) return;\n let dead = false;\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {});\n const id = setInterval(\n () =>\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {}),\n 30 * 60 * 1000,\n );\n return () => {\n dead = true;\n clearInterval(id);\n };\n }, [lat, lon]);\n return weather;\n}\n\n// ─── Props passed to every compact skin component ─────────────────────────────\n\nexport interface CompactSkinProps {\n phase: SolarPhase;\n blend: SolarBlend;\n time: string;\n location: string;\n flag?: string;\n temperature?: string;\n weather?: WeatherCategory | null;\n liveWeatherCategory: WeatherCategory | null;\n liveTemperatureC: number | null;\n latitude?: number | null;\n longitude?: number | null;\n timezone?: string | null;\n simulatedDate?: Date;\n showFlag: boolean;\n showWeather: boolean;\n showTemperature: boolean;\n size: CompactSize;\n palette: WidgetPalette;\n}\n\n// ─── Public props for the shell ───────────────────────────────────────────────\n\nexport interface CompactWidgetProps {\n design?: DesignMode;\n overridePhase?: SolarPhase | null;\n time?: string;\n location?: string;\n flag?: string;\n temperature?: string;\n weather?: WeatherCategory | null;\n weatherCategoryOverride?: WeatherCategory | null;\n showFlag?: boolean;\n showWeather?: boolean;\n showTemperature?: boolean;\n size?: CompactSize;\n latitude?: number | null;\n longitude?: number | null;\n timezone?: string | null;\n /** Override background colors per phase */\n customPalettes?: CustomPalettes;\n /** Explicit simulated date. Falls back to ctx.simulatedDate (from SolarDevTools)\n * then to real current time. */\n simulatedDate?: Date;\n className?: string;\n}\n\n// ─── Palette blending ─────────────────────────────────────────────────────────\n\nfunction blendPalette(skin: SkinDefinition, blend: SolarBlend): WidgetPalette {\n const from = skin.widgetPalettes[blend.phase];\n const to = skin.widgetPalettes[blend.nextPhase];\n const t = blend.t;\n if (t === 0) return from;\n const lerp = (a: string, b: string) => lerpHex(a, b, t);\n return {\n bg: [lerp(from.bg[0], to.bg[0]), lerp(from.bg[1], to.bg[1]), lerp(from.bg[2], to.bg[2])] as [\n string,\n string,\n string,\n ],\n textColor: lerp(from.textColor, to.textColor),\n accentColor: lerp(from.accentColor, to.accentColor),\n orb: lerp(from.orb, to.orb),\n outerGlow: lerp(from.outerGlow, to.outerGlow),\n mode: from.mode,\n };\n}\n\n// ─── Shell ────────────────────────────────────────────────────────────────────\n\nexport function CompactWidget({\n design: designOverride,\n overridePhase,\n time,\n location = '',\n flag,\n temperature,\n weather = null,\n weatherCategoryOverride,\n showFlag = false,\n showWeather = false,\n showTemperature = true,\n size = 'md',\n latitude,\n longitude,\n timezone,\n customPalettes,\n simulatedDate: simulatedDateProp,\n className = '',\n}: CompactWidgetProps) {\n const [mounted, setMounted] = useState(false);\n useLayoutEffect(() => setMounted(true), []);\n\n const ctx = useSolarTheme();\n\n // Register customPalettes into context so DevTools can read them\n useEffect(() => {\n ctx.setCustomPalettes(customPalettes);\n return () => ctx.setCustomPalettes(undefined);\n }, [customPalettes, ctx.setCustomPalettes]);\n\n // prop wins → context (devtools) → undefined (live)\n const simulatedDate = simulatedDateProp ?? ctx.simulatedDate;\n\n const skin = useMemo(() => {\n if (!designOverride || designOverride === ctx.design) return ctx.activeSkin;\n return ctx.activeSkin;\n }, [designOverride, ctx.design, ctx.activeSkin]);\n\n const phase = overridePhase ?? ctx.phase;\n const blend = overridePhase\n ? { phase: overridePhase, nextPhase: overridePhase, t: 0 }\n : ctx.blend;\n\n const blendedPalette = useMemo(() => blendPalette(skin, blend), [skin, blend]);\n\n const palette: WidgetPalette = useMemo(() => {\n if (!customPalettes?.[phase]) return blendedPalette;\n return { ...blendedPalette, bg: customPalettes[phase]?.bg };\n }, [blendedPalette, customPalettes, phase]);\n\n const resolvedLat = latitude ?? ctx.latitude;\n const resolvedLon = longitude ?? ctx.longitude;\n const resolvedTz = timezone ?? ctx.timezone;\n\n // ── Centralised weather fetch ────────────────────────────────────────────\n const liveWeather = useWeatherData(resolvedLat ?? null, resolvedLon ?? null);\n\n const liveWeatherCategory: WeatherCategory | null = showWeather\n ? (weatherCategoryOverride ?? weather ?? liveWeather?.category ?? null)\n : null;\n\n const liveTemperatureC: number | null = liveWeather?.temperatureC ?? null;\n\n // ── Resolve time string ──────────────────────────────────────────────────\n // If caller passes an explicit time string, use it. Otherwise derive from\n // simulatedDate (or real time) so the clock display matches the orb.\n const resolvedTime = useMemo(() => {\n if (time) return time;\n const d = simulatedDate ?? new Date();\n const opts: Intl.DateTimeFormatOptions = {\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n };\n if (resolvedTz) opts.timeZone = resolvedTz;\n const parts = new Intl.DateTimeFormat('en-GB', opts).formatToParts(d);\n const hh = parts.find((p) => p.type === 'hour')?.value ?? '00';\n const mm = parts.find((p) => p.type === 'minute')?.value ?? '00';\n return `${hh}:${mm}`;\n }, [time, simulatedDate, resolvedTz]);\n\n const props: CompactSkinProps = {\n phase,\n blend,\n time: resolvedTime,\n location,\n flag,\n temperature,\n weather,\n liveWeatherCategory,\n liveTemperatureC,\n latitude: resolvedLat,\n longitude: resolvedLon,\n timezone: resolvedTz,\n simulatedDate,\n showFlag,\n showWeather,\n showTemperature,\n size,\n palette,\n };\n\n const CompactComponent = (\n skin as SkinDefinition & {\n CompactComponent?: React.ComponentType<CompactSkinProps>;\n }\n ).CompactComponent;\n\n if (!CompactComponent) {\n return (\n <div className={className} style={{ opacity: 0.4, fontSize: 11, color: '#888' }}>\n Compact not implemented for {skin.id}\n </div>\n );\n }\n\n return (\n <div\n className={className}\n style={{ visibility: mounted ? 'visible' : 'hidden', isolation: 'isolate' }}\n >\n <CompactComponent {...props} />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["WMO_MAP: Record<number, WeatherCategory>","fetchWeather","WMO_MAP","useWeatherData","finalPalette: WidgetPalette","opts: Intl.DateTimeFormatOptions","liveWeatherCategory: WeatherCategory | null","liveTemperatureC: number | null","WMO_MAP: Record<number, WeatherCategory>","palette: WidgetPalette","liveWeatherCategory: WeatherCategory | null","liveTemperatureC: number | null","props: CompactSkinProps","opts: Intl.DateTimeFormatOptions","UNIVERSAL_PHASE_MOTION: MotionProfile","VOID_MOTION: MotionProfile","PARCHMENT_MOTION: MotionProfile","SIGNAL_MOTION: MotionProfile","MERIDIAN_MOTION: MotionProfile","SUNDIAL_MOTION: MotionProfile","PAPER_MOTION: MotionProfile","FOUNDRY_MOTION: MotionProfile","MINERAL_MOTION: MotionProfile","TIDE_MOTION: MotionProfile","AURORA_MOTION: MotionProfile","SKIN_MOTION_PROFILES: Record<DesignMode, MotionProfile>"],"sources":["../src/widgets/solar-widget.shell.tsx","../src/widgets/compact-widget.shell.tsx","../src/shared/shader-motion-profiles.tsx","../src/shared/solar-shader-bg.tsx"],"sourcesContent":["'use client';\n\n/**\n * widgets/solar-widget.shell.tsx (skin-aware shell)\n *\n * Weather fetch is now centralised here alongside the compact shell:\n * - Fetches temperature + weather_code from open-meteo once per mount\n * - Resolves liveWeatherCategory: weatherCategoryOverride ?? live category\n * - Passes liveWeatherCategory + liveTemperatureC down to skin components\n * - Skins should prefer these props over running their own fetch\n *\n * simulatedDate prop wins over ctx.simulatedDate (set by SolarDevTools),\n * which wins over undefined (live time). This means the devtools timeline\n * scrubber moves the orb in every widget without requiring any prop wiring.\n */\n\nimport { useEffect, useLayoutEffect, useMemo, useState } from 'react';\nimport type { SolarPhase } from '../hooks/useSolarPosition';\nimport { lerpHex } from '../lib/solar-lerp';\nimport { useSolarTheme } from '../provider/solar-theme-provider';\nimport type { DesignMode, WidgetPalette } from '../skins/types/widget-skin.types';\n\n// ─── Re-exported types ────────────────────────────────────────────────────────\n\nexport type ExpandDirection =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport type WidgetSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\nexport type WeatherCategory =\n | 'clear'\n | 'partly-cloudy'\n | 'overcast'\n | 'fog'\n | 'drizzle'\n | 'rain'\n | 'heavy-rain'\n | 'snow'\n | 'heavy-snow'\n | 'thunder';\n\nexport type CustomPalettes = Partial<Record<SolarPhase, { bg: [string, string, string] }>>;\n\nimport { SKINS } from '../skins/index';\nexport const PALETTES = SKINS.foundry.widgetPalettes;\n\n// ─── WMO weather code map ─────────────────────────────────────────────────────\n\nconst WMO_MAP: Record<number, WeatherCategory> = {\n 0: 'clear',\n 1: 'clear',\n 2: 'partly-cloudy',\n 3: 'overcast',\n 45: 'fog',\n 48: 'fog',\n 51: 'drizzle',\n 53: 'drizzle',\n 55: 'drizzle',\n 61: 'rain',\n 63: 'rain',\n 65: 'heavy-rain',\n 71: 'snow',\n 73: 'snow',\n 75: 'heavy-snow',\n 80: 'rain',\n 82: 'heavy-rain',\n 85: 'snow',\n 86: 'heavy-snow',\n 95: 'thunder',\n 96: 'thunder',\n 99: 'thunder',\n};\n\ninterface LiveWeather {\n temperatureC: number;\n category: WeatherCategory;\n}\n\nasync function fetchWeather(lat: number, lon: number): Promise<LiveWeather> {\n const url = new URL('https://api.open-meteo.com/v1/forecast');\n url.searchParams.set('latitude', String(lat));\n url.searchParams.set('longitude', String(lon));\n url.searchParams.set('current', 'temperature_2m,weather_code');\n url.searchParams.set('forecast_days', '1');\n const data = (await fetch(url.toString()).then((r) => r.json())) as {\n current: { temperature_2m: number; weather_code: number };\n };\n return {\n temperatureC: Math.round(data.current.temperature_2m),\n category: WMO_MAP[data.current.weather_code] ?? 'clear',\n };\n}\n\nfunction useWeatherData(lat: number | null, lon: number | null) {\n const [weather, setWeather] = useState<LiveWeather | null>(null);\n useEffect(() => {\n if (!lat || !lon) return;\n let dead = false;\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {});\n const id = setInterval(\n () =>\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {}),\n 30 * 60 * 1000,\n );\n return () => {\n dead = true;\n clearInterval(id);\n };\n }, [lat, lon]);\n return weather;\n}\n\n// ─── Widget props ─────────────────────────────────────────────────────────────\n\nexport interface SolarWidgetProps {\n expandDirection?: ExpandDirection;\n size?: WidgetSize;\n showFlag?: boolean;\n showWeather?: boolean;\n hoverEffect?: boolean;\n customPalettes?: CustomPalettes;\n phaseOverride?: SolarPhase;\n weatherCategoryOverride?: WeatherCategory | null;\n /** Explicit simulated date. Falls back to ctx.simulatedDate (from SolarDevTools)\n * then to real current time. */\n simulatedDate?: Date;\n /** Lock the widget state: `true` = always expanded, `false` = always collapsed.\n * Omit or pass `undefined` for default localStorage-driven behaviour. */\n forceExpanded?: boolean;\n}\n\n// ─── Palette blending helper ──────────────────────────────────────────────────\n\nfunction blendWidgetPalette(from: WidgetPalette, to: WidgetPalette, t: number): WidgetPalette {\n return {\n bg: [\n lerpHex(from.bg[0], to.bg[0], t),\n lerpHex(from.bg[1], to.bg[1], t),\n lerpHex(from.bg[2], to.bg[2], t),\n ],\n textColor: lerpHex(from.textColor, to.textColor, t),\n accentColor: lerpHex(from.accentColor, to.accentColor, t),\n orb: lerpHex(from.orb, to.orb, t),\n outerGlow: from.outerGlow,\n mode: t < 0.5 ? from.mode : to.mode,\n };\n}\n\n// ─── Shell component ──────────────────────────────────────────────────────────\n\nexport function SolarWidget({\n expandDirection = 'bottom-right',\n size = 'lg',\n showFlag = false,\n showWeather = false,\n hoverEffect = false,\n customPalettes,\n phaseOverride,\n weatherCategoryOverride,\n simulatedDate: simulatedDateProp,\n forceExpanded,\n}: SolarWidgetProps) {\n const [mounted, setMounted] = useState(false);\n useLayoutEffect(() => setMounted(true), []);\n\n const {\n phase,\n blend,\n activeSkin,\n timezone,\n latitude,\n longitude,\n simulatedDate: ctxSimulatedDate,\n setCustomPalettes,\n } = useSolarTheme();\n\n // Register customPalettes into context so DevTools can read them\n useEffect(() => {\n setCustomPalettes(customPalettes);\n return () => setCustomPalettes(undefined);\n }, [customPalettes, setCustomPalettes]);\n\n // prop wins → context (devtools) → undefined (live)\n const simulatedDate = simulatedDateProp ?? ctxSimulatedDate;\n\n const [expanded, setExpanded] = useState(false);\n\n const activePhase = phaseOverride ?? phase;\n const activeBlend = phaseOverride\n ? { phase: phaseOverride, nextPhase: phaseOverride, t: 0 }\n : blend;\n\n const fromPalette = activeSkin.widgetPalettes[activeBlend.phase];\n const toPalette = activeSkin.widgetPalettes[activeBlend.nextPhase];\n\n const blendedPalette = useMemo(\n () => blendWidgetPalette(fromPalette, toPalette, activeBlend.t),\n [fromPalette, toPalette, activeBlend.t],\n );\n\n const finalPalette: WidgetPalette = useMemo(() => {\n if (!customPalettes?.[activePhase]) return blendedPalette;\n return { ...blendedPalette, bg: customPalettes[activePhase]?.bg };\n }, [blendedPalette, customPalettes, activePhase]);\n\n const time = useMemo(() => {\n const d = simulatedDate ?? new Date();\n const opts: Intl.DateTimeFormatOptions = {\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n };\n if (timezone) opts.timeZone = timezone;\n const parts = new Intl.DateTimeFormat('en-GB', opts).formatToParts(d);\n const hh = parts.find((p) => p.type === 'hour')?.value ?? '00';\n const mm = parts.find((p) => p.type === 'minute')?.value ?? '00';\n return `${hh}:${mm}`;\n }, [simulatedDate, timezone]);\n\n // ── Centralised weather fetch ────────────────────────────────────────────\n const liveWeather = useWeatherData(latitude ?? null, longitude ?? null);\n\n const liveWeatherCategory: WeatherCategory | null = showWeather\n ? (weatherCategoryOverride ?? liveWeather?.category ?? null)\n : null;\n\n const liveTemperatureC: number | null = liveWeather?.temperatureC ?? null;\n\n const SkinComponent = activeSkin.Component;\n\n return (\n <div style={{ visibility: mounted ? 'visible' : 'hidden' }}>\n <SkinComponent\n phase={activePhase}\n blend={activeBlend}\n expanded={expanded}\n onToggle={() => setExpanded((v) => !v)}\n expandDirection={expandDirection}\n size={size}\n time={time}\n location=\"\"\n showFlag={showFlag}\n showWeather={showWeather}\n hoverEffect={hoverEffect}\n weather={weatherCategoryOverride}\n liveWeatherCategory={liveWeatherCategory}\n liveTemperatureC={liveTemperatureC}\n palette={finalPalette}\n latitude={latitude}\n longitude={longitude}\n timezone={timezone}\n simulatedDate={simulatedDate}\n forceExpanded={forceExpanded}\n />\n </div>\n );\n}\n","'use client';\n\n/**\n * widgets/compact-widget.shell.tsx\n *\n * Shell for the compact solar widget — a slim pill/bar format.\n *\n * simulatedDate prop wins over ctx.simulatedDate (set by SolarDevTools),\n * which wins over undefined (live time). This means the devtools timeline\n * scrubber moves the orb in compact widgets without requiring any prop wiring.\n */\n\nimport { useEffect, useLayoutEffect, useMemo, useState } from 'react';\nimport type { SolarBlend, SolarPhase } from '../hooks/useSolarPosition';\nimport { lerpHex } from '../lib/solar-lerp';\nimport { useSolarTheme } from '../provider/solar-theme-provider';\nimport type { DesignMode, SkinDefinition, WidgetPalette } from '../skins/types/widget-skin.types';\nimport type { CustomPalettes } from './solar-widget.shell';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type WeatherCategory =\n | 'clear'\n | 'partly-cloudy'\n | 'overcast'\n | 'fog'\n | 'drizzle'\n | 'rain'\n | 'heavy-rain'\n | 'snow'\n | 'heavy-snow'\n | 'thunder';\n\nexport type CompactSize = 'sm' | 'md' | 'lg';\n\n// ─── WMO weather code map ─────────────────────────────────────────────────────\n\nconst WMO_MAP: Record<number, WeatherCategory> = {\n 0: 'clear',\n 1: 'clear',\n 2: 'partly-cloudy',\n 3: 'overcast',\n 45: 'fog',\n 48: 'fog',\n 51: 'drizzle',\n 53: 'drizzle',\n 55: 'drizzle',\n 61: 'rain',\n 63: 'rain',\n 65: 'heavy-rain',\n 71: 'snow',\n 73: 'snow',\n 75: 'heavy-snow',\n 80: 'rain',\n 82: 'heavy-rain',\n 85: 'snow',\n 86: 'heavy-snow',\n 95: 'thunder',\n 96: 'thunder',\n 99: 'thunder',\n};\n\n// ─── Centralised weather fetch ────────────────────────────────────────────────\n\ninterface LiveWeather {\n temperatureC: number;\n category: WeatherCategory;\n}\n\nasync function fetchWeather(lat: number, lon: number): Promise<LiveWeather> {\n const url = new URL('https://api.open-meteo.com/v1/forecast');\n url.searchParams.set('latitude', String(lat));\n url.searchParams.set('longitude', String(lon));\n url.searchParams.set('current', 'temperature_2m,weather_code');\n url.searchParams.set('forecast_days', '1');\n const data = (await fetch(url.toString()).then((r) => r.json())) as {\n current: { temperature_2m: number; weather_code: number };\n };\n return {\n temperatureC: Math.round(data.current.temperature_2m),\n category: WMO_MAP[data.current.weather_code] ?? 'clear',\n };\n}\n\nfunction useWeatherData(lat: number | null, lon: number | null) {\n const [weather, setWeather] = useState<LiveWeather | null>(null);\n useEffect(() => {\n if (!lat || !lon) return;\n let dead = false;\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {});\n const id = setInterval(\n () =>\n fetchWeather(lat, lon)\n .then((w) => {\n if (!dead) setWeather(w);\n })\n .catch(() => {}),\n 30 * 60 * 1000,\n );\n return () => {\n dead = true;\n clearInterval(id);\n };\n }, [lat, lon]);\n return weather;\n}\n\n// ─── Props passed to every compact skin component ─────────────────────────────\n\nexport interface CompactSkinProps {\n phase: SolarPhase;\n blend: SolarBlend;\n time: string;\n location: string;\n flag?: string;\n temperature?: string;\n weather?: WeatherCategory | null;\n liveWeatherCategory: WeatherCategory | null;\n liveTemperatureC: number | null;\n latitude?: number | null;\n longitude?: number | null;\n timezone?: string | null;\n simulatedDate?: Date;\n showFlag: boolean;\n showWeather: boolean;\n showTemperature: boolean;\n size: CompactSize;\n palette: WidgetPalette;\n}\n\n// ─── Public props for the shell ───────────────────────────────────────────────\n\nexport interface CompactWidgetProps {\n design?: DesignMode;\n overridePhase?: SolarPhase | null;\n time?: string;\n location?: string;\n flag?: string;\n temperature?: string;\n weather?: WeatherCategory | null;\n weatherCategoryOverride?: WeatherCategory | null;\n showFlag?: boolean;\n showWeather?: boolean;\n showTemperature?: boolean;\n size?: CompactSize;\n latitude?: number | null;\n longitude?: number | null;\n timezone?: string | null;\n /** Override background colors per phase */\n customPalettes?: CustomPalettes;\n /** Explicit simulated date. Falls back to ctx.simulatedDate (from SolarDevTools)\n * then to real current time. */\n simulatedDate?: Date;\n className?: string;\n}\n\n// ─── Palette blending ─────────────────────────────────────────────────────────\n\nfunction blendPalette(skin: SkinDefinition, blend: SolarBlend): WidgetPalette {\n const from = skin.widgetPalettes[blend.phase];\n const to = skin.widgetPalettes[blend.nextPhase];\n const t = blend.t;\n if (t === 0) return from;\n const lerp = (a: string, b: string) => lerpHex(a, b, t);\n return {\n bg: [lerp(from.bg[0], to.bg[0]), lerp(from.bg[1], to.bg[1]), lerp(from.bg[2], to.bg[2])] as [\n string,\n string,\n string,\n ],\n textColor: lerp(from.textColor, to.textColor),\n accentColor: lerp(from.accentColor, to.accentColor),\n orb: lerp(from.orb, to.orb),\n outerGlow: lerp(from.outerGlow, to.outerGlow),\n mode: from.mode,\n };\n}\n\n// ─── Shell ────────────────────────────────────────────────────────────────────\n\nexport function CompactWidget({\n design: designOverride,\n overridePhase,\n time,\n location = '',\n flag,\n temperature,\n weather = null,\n weatherCategoryOverride,\n showFlag = false,\n showWeather = false,\n showTemperature = true,\n size = 'md',\n latitude,\n longitude,\n timezone,\n customPalettes,\n simulatedDate: simulatedDateProp,\n className = '',\n}: CompactWidgetProps) {\n const [mounted, setMounted] = useState(false);\n useLayoutEffect(() => setMounted(true), []);\n\n const ctx = useSolarTheme();\n\n // Register customPalettes into context so DevTools can read them\n useEffect(() => {\n ctx.setCustomPalettes(customPalettes);\n return () => ctx.setCustomPalettes(undefined);\n }, [customPalettes, ctx.setCustomPalettes]);\n\n // prop wins → context (devtools) → undefined (live)\n const simulatedDate = simulatedDateProp ?? ctx.simulatedDate;\n\n const skin = useMemo(() => {\n if (!designOverride || designOverride === ctx.design) return ctx.activeSkin;\n return ctx.activeSkin;\n }, [designOverride, ctx.design, ctx.activeSkin]);\n\n const phase = overridePhase ?? ctx.phase;\n const blend = overridePhase\n ? { phase: overridePhase, nextPhase: overridePhase, t: 0 }\n : ctx.blend;\n\n const blendedPalette = useMemo(() => blendPalette(skin, blend), [skin, blend]);\n\n const palette: WidgetPalette = useMemo(() => {\n if (!customPalettes?.[phase]) return blendedPalette;\n return { ...blendedPalette, bg: customPalettes[phase]?.bg };\n }, [blendedPalette, customPalettes, phase]);\n\n const resolvedLat = latitude ?? ctx.latitude;\n const resolvedLon = longitude ?? ctx.longitude;\n const resolvedTz = timezone ?? ctx.timezone;\n\n // ── Centralised weather fetch ────────────────────────────────────────────\n const liveWeather = useWeatherData(resolvedLat ?? null, resolvedLon ?? null);\n\n const liveWeatherCategory: WeatherCategory | null = showWeather\n ? (weatherCategoryOverride ?? weather ?? liveWeather?.category ?? null)\n : null;\n\n const liveTemperatureC: number | null = liveWeather?.temperatureC ?? null;\n\n // ── Resolve time string ──────────────────────────────────────────────────\n // If caller passes an explicit time string, use it. Otherwise derive from\n // simulatedDate (or real time) so the clock display matches the orb.\n const resolvedTime = useMemo(() => {\n if (time) return time;\n const d = simulatedDate ?? new Date();\n const opts: Intl.DateTimeFormatOptions = {\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n };\n if (resolvedTz) opts.timeZone = resolvedTz;\n const parts = new Intl.DateTimeFormat('en-GB', opts).formatToParts(d);\n const hh = parts.find((p) => p.type === 'hour')?.value ?? '00';\n const mm = parts.find((p) => p.type === 'minute')?.value ?? '00';\n return `${hh}:${mm}`;\n }, [time, simulatedDate, resolvedTz]);\n\n const props: CompactSkinProps = {\n phase,\n blend,\n time: resolvedTime,\n location,\n flag,\n temperature,\n weather,\n liveWeatherCategory,\n liveTemperatureC,\n latitude: resolvedLat,\n longitude: resolvedLon,\n timezone: resolvedTz,\n simulatedDate,\n showFlag,\n showWeather,\n showTemperature,\n size,\n palette,\n };\n\n const CompactComponent = (\n skin as SkinDefinition & {\n CompactComponent?: React.ComponentType<CompactSkinProps>;\n }\n ).CompactComponent;\n\n if (!CompactComponent) {\n return (\n <div className={className} style={{ opacity: 0.4, fontSize: 11, color: '#888' }}>\n Compact not implemented for {skin.id}\n </div>\n );\n }\n\n return (\n <div\n className={className}\n style={{ visibility: mounted ? 'visible' : 'hidden', isolation: 'isolate' }}\n >\n <CompactComponent {...props} />\n </div>\n );\n}\n","/**\n * shader-motion-profiles.ts\n *\n * Per-skin motion personalities for SolarShaderBg.\n *\n * This is the creative core of the background shader system. Colors come from\n * each skin's shaderPalettes — motion comes from here. Together they define\n * a complete atmospheric identity per skin per phase.\n *\n * Motion axes:\n * distortion — organic mesh noise. High = liquid, flowing. Low = crystalline, still.\n * swirl — vortex pull. High = spiral drama. Low = gentle drift.\n * speed — animation rate. 0 = frozen. 1 = reference. >1 = fast.\n * grainOverlay — film grain. High in dark/still skins, low in bright/active ones.\n *\n * ─── Design intent per skin ───────────────────────────────────────────────────\n *\n * VOID The anti-skin. Barely alive. Night = almost nothing moves.\n * Noon nudges up slightly — but \"slightly\" for Void is still near-zero.\n * Grain is highest at midnight because darkness has texture.\n *\n * PARCHMENT A document. Documents don't move. speed=0 everywhere.\n * The only \"motion\" is the CSS transition on color as the phase shifts.\n *\n * SIGNAL Brutalist terminal. No organic drift — that would feel wrong.\n * Instead: very low distortion, almost-zero swirl, high grain.\n * The background is raw, not smooth.\n *\n * MERIDIAN Airy and clean. Moves gently but constantly — like a well-ventilated\n * room. Half the universal values throughout.\n *\n * SUNDIAL Ancient, measured. The dial shadow moves once per hour.\n * Low speed, low swirl. Grain is stone-textured.\n *\n * PAPER Uncoated stock breathes. Organic, unhurried. Medium everything —\n * never dramatic, never static.\n *\n * FOUNDRY The reference skin. Uses the universal phase-energy curve:\n * low at night, high at noon, dramatic at sunrise/sunset.\n * Closest to the default PHASE_MOTION values.\n *\n * MINERAL Gemstones don't flow — they refract. Low distortion (facets, not\n * liquid), moderate swirl (internal light scattering), medium speed.\n * Grain is low — stones are smooth.\n *\n * TIDE The sea rolls. High distortion at all phases (waves never stop),\n * moderate swirl, speed follows tidal rhythm. Low grain — water is clear.\n *\n * AURORA The most variable. At midnight/night: maximum everything —\n * the lights are dancing. During day: gentle and warm, the aurora\n * is invisible. At dusk/sunset: the bands return.\n */\n\nimport type { SolarPhase } from '../hooks/useSolarPosition';\nimport type { DesignMode, ShaderMotion } from '../skins/types/widget-skin.types';\n\n// ─── Type ─────────────────────────────────────────────────────────────────────\n\nexport type MotionProfile = Record<SolarPhase, ShaderMotion>;\n\n// ─── Universal fallback — used when a skin has no shaderMotion ────────────────\n// The energy arc of the sun: slow at night, rising through dawn,\n// peaking near noon/sunset, unwinding through dusk.\n\nexport const UNIVERSAL_PHASE_MOTION: MotionProfile = {\n midnight: { distortion: 0.2, swirl: 0.04, speed: 0.1, grainOverlay: 0.18 },\n night: { distortion: 0.28, swirl: 0.06, speed: 0.15, grainOverlay: 0.15 },\n dawn: { distortion: 0.52, swirl: 0.14, speed: 0.26, grainOverlay: 0.1 },\n sunrise: { distortion: 0.68, swirl: 0.22, speed: 0.38, grainOverlay: 0.07 },\n morning: { distortion: 0.54, swirl: 0.13, speed: 0.5, grainOverlay: 0.05 },\n 'solar-noon': { distortion: 0.36, swirl: 0.07, speed: 0.64, grainOverlay: 0.04 },\n afternoon: { distortion: 0.44, swirl: 0.1, speed: 0.5, grainOverlay: 0.05 },\n sunset: { distortion: 0.72, swirl: 0.3, speed: 0.34, grainOverlay: 0.08 },\n dusk: { distortion: 0.44, swirl: 0.18, speed: 0.2, grainOverlay: 0.14 },\n};\n\n// ─── Per-skin motion profiles ─────────────────────────────────────────────────\n\n// VOID — barely alive. The orb is the only presence; the background should\n// not compete. Maximum grain at midnight because pure black has texture.\nconst VOID_MOTION: MotionProfile = {\n midnight: { distortion: 0.08, swirl: 0.01, speed: 0.04, grainOverlay: 0.28 },\n night: { distortion: 0.1, swirl: 0.02, speed: 0.05, grainOverlay: 0.22 },\n dawn: { distortion: 0.14, swirl: 0.03, speed: 0.07, grainOverlay: 0.16 },\n sunrise: { distortion: 0.16, swirl: 0.04, speed: 0.09, grainOverlay: 0.12 },\n morning: { distortion: 0.14, swirl: 0.03, speed: 0.1, grainOverlay: 0.1 },\n 'solar-noon': { distortion: 0.1, swirl: 0.02, speed: 0.12, grainOverlay: 0.08 },\n afternoon: { distortion: 0.12, swirl: 0.03, speed: 0.1, grainOverlay: 0.1 },\n sunset: { distortion: 0.18, swirl: 0.05, speed: 0.08, grainOverlay: 0.14 },\n dusk: { distortion: 0.12, swirl: 0.03, speed: 0.06, grainOverlay: 0.2 },\n};\n\n// PARCHMENT — a document doesn't move. speed=0 everywhere.\n// The phase shift is expressed only through the CSS color transition.\nconst PARCHMENT_MOTION: MotionProfile = {\n midnight: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.06 },\n night: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.05 },\n dawn: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.04 },\n sunrise: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.03 },\n morning: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.02 },\n 'solar-noon': { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.02 },\n afternoon: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.02 },\n sunset: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.03 },\n dusk: { distortion: 0.0, swirl: 0.0, speed: 0.0, grainOverlay: 0.05 },\n};\n\n// SIGNAL — brutalist terminal. No organic drift. Raw grain, mechanical.\n// Distortion is very low (pixels don't flow in a terminal).\n// Grain is high — CRT texture, not smoothness.\nconst SIGNAL_MOTION: MotionProfile = {\n midnight: { distortion: 0.06, swirl: 0.0, speed: 0.06, grainOverlay: 0.32 },\n night: { distortion: 0.06, swirl: 0.0, speed: 0.06, grainOverlay: 0.3 },\n dawn: { distortion: 0.08, swirl: 0.01, speed: 0.08, grainOverlay: 0.26 },\n sunrise: { distortion: 0.08, swirl: 0.01, speed: 0.08, grainOverlay: 0.24 },\n morning: { distortion: 0.1, swirl: 0.01, speed: 0.1, grainOverlay: 0.22 },\n 'solar-noon': { distortion: 0.1, swirl: 0.01, speed: 0.1, grainOverlay: 0.2 },\n afternoon: { distortion: 0.1, swirl: 0.01, speed: 0.1, grainOverlay: 0.22 },\n sunset: { distortion: 0.08, swirl: 0.01, speed: 0.08, grainOverlay: 0.26 },\n dusk: { distortion: 0.06, swirl: 0.0, speed: 0.06, grainOverlay: 0.3 },\n};\n\n// MERIDIAN — clean and airy. Constant gentle drift. Never dramatic.\n// Half the universal values across all phases.\nconst MERIDIAN_MOTION: MotionProfile = {\n midnight: { distortion: 0.12, swirl: 0.02, speed: 0.08, grainOverlay: 0.08 },\n night: { distortion: 0.14, swirl: 0.03, speed: 0.1, grainOverlay: 0.07 },\n dawn: { distortion: 0.24, swirl: 0.06, speed: 0.14, grainOverlay: 0.05 },\n sunrise: { distortion: 0.3, swirl: 0.09, speed: 0.2, grainOverlay: 0.04 },\n morning: { distortion: 0.26, swirl: 0.06, speed: 0.26, grainOverlay: 0.03 },\n 'solar-noon': { distortion: 0.18, swirl: 0.04, speed: 0.32, grainOverlay: 0.02 },\n afternoon: { distortion: 0.22, swirl: 0.05, speed: 0.26, grainOverlay: 0.03 },\n sunset: { distortion: 0.32, swirl: 0.12, speed: 0.18, grainOverlay: 0.04 },\n dusk: { distortion: 0.22, swirl: 0.08, speed: 0.12, grainOverlay: 0.06 },\n};\n\n// SUNDIAL — ancient, measured. The gnomon shadow moves once per hour.\n// Low speed, low swirl. Stone grain.\nconst SUNDIAL_MOTION: MotionProfile = {\n midnight: { distortion: 0.18, swirl: 0.03, speed: 0.07, grainOverlay: 0.2 },\n night: { distortion: 0.2, swirl: 0.04, speed: 0.08, grainOverlay: 0.18 },\n dawn: { distortion: 0.28, swirl: 0.07, speed: 0.14, grainOverlay: 0.12 },\n sunrise: { distortion: 0.34, swirl: 0.1, speed: 0.18, grainOverlay: 0.09 },\n morning: { distortion: 0.28, swirl: 0.06, speed: 0.22, grainOverlay: 0.08 },\n 'solar-noon': { distortion: 0.2, swirl: 0.04, speed: 0.28, grainOverlay: 0.06 },\n afternoon: { distortion: 0.24, swirl: 0.05, speed: 0.22, grainOverlay: 0.07 },\n sunset: { distortion: 0.36, swirl: 0.14, speed: 0.16, grainOverlay: 0.1 },\n dusk: { distortion: 0.24, swirl: 0.08, speed: 0.1, grainOverlay: 0.14 },\n};\n\n// PAPER — uncoated stock breathes. Organic, unhurried. Medium everything.\n// Never dramatic, never static. Grain increases slightly at night.\nconst PAPER_MOTION: MotionProfile = {\n midnight: { distortion: 0.22, swirl: 0.04, speed: 0.1, grainOverlay: 0.22 },\n night: { distortion: 0.26, swirl: 0.06, speed: 0.14, grainOverlay: 0.18 },\n dawn: { distortion: 0.38, swirl: 0.1, speed: 0.22, grainOverlay: 0.12 },\n sunrise: { distortion: 0.46, swirl: 0.14, speed: 0.3, grainOverlay: 0.09 },\n morning: { distortion: 0.38, swirl: 0.09, speed: 0.38, grainOverlay: 0.07 },\n 'solar-noon': { distortion: 0.28, swirl: 0.05, speed: 0.46, grainOverlay: 0.05 },\n afternoon: { distortion: 0.34, swirl: 0.07, speed: 0.38, grainOverlay: 0.06 },\n sunset: { distortion: 0.5, swirl: 0.2, speed: 0.26, grainOverlay: 0.1 },\n dusk: { distortion: 0.34, swirl: 0.12, speed: 0.16, grainOverlay: 0.14 },\n};\n\n// FOUNDRY — the reference skin. Full universal energy arc.\n// Rich, heavy, machined. Slightly more swirl than the bare universal.\nconst FOUNDRY_MOTION: MotionProfile = {\n midnight: { distortion: 0.22, swirl: 0.04, speed: 0.1, grainOverlay: 0.16 },\n night: { distortion: 0.3, swirl: 0.07, speed: 0.16, grainOverlay: 0.13 },\n dawn: { distortion: 0.55, swirl: 0.16, speed: 0.28, grainOverlay: 0.09 },\n sunrise: { distortion: 0.7, swirl: 0.26, speed: 0.4, grainOverlay: 0.06 },\n morning: { distortion: 0.56, swirl: 0.15, speed: 0.52, grainOverlay: 0.04 },\n 'solar-noon': { distortion: 0.38, swirl: 0.08, speed: 0.66, grainOverlay: 0.03 },\n afternoon: { distortion: 0.46, swirl: 0.12, speed: 0.52, grainOverlay: 0.04 },\n sunset: { distortion: 0.74, swirl: 0.34, speed: 0.36, grainOverlay: 0.07 },\n dusk: { distortion: 0.46, swirl: 0.2, speed: 0.22, grainOverlay: 0.12 },\n};\n\n// MINERAL — gemstones refract, they don't flow. Low distortion, moderate swirl\n// (internal scattering), medium speed. Grain is very low — facets are sharp.\nconst MINERAL_MOTION: MotionProfile = {\n midnight: { distortion: 0.14, swirl: 0.1, speed: 0.12, grainOverlay: 0.06 },\n night: { distortion: 0.16, swirl: 0.12, speed: 0.14, grainOverlay: 0.05 },\n dawn: { distortion: 0.22, swirl: 0.18, speed: 0.22, grainOverlay: 0.04 },\n sunrise: { distortion: 0.28, swirl: 0.24, speed: 0.32, grainOverlay: 0.03 },\n morning: { distortion: 0.22, swirl: 0.18, speed: 0.4, grainOverlay: 0.02 },\n 'solar-noon': { distortion: 0.16, swirl: 0.14, speed: 0.5, grainOverlay: 0.02 },\n afternoon: { distortion: 0.2, swirl: 0.16, speed: 0.4, grainOverlay: 0.02 },\n sunset: { distortion: 0.3, swirl: 0.28, speed: 0.28, grainOverlay: 0.03 },\n dusk: { distortion: 0.2, swirl: 0.2, speed: 0.18, grainOverlay: 0.04 },\n};\n\n// TIDE — the sea rolls. High distortion at all phases (waves never stop).\n// Speed follows a tidal rhythm: slower at dead of night, building through dawn.\n// Grain is very low — water is clear, not grainy.\nconst TIDE_MOTION: MotionProfile = {\n midnight: { distortion: 0.5, swirl: 0.08, speed: 0.18, grainOverlay: 0.03 },\n night: { distortion: 0.54, swirl: 0.1, speed: 0.22, grainOverlay: 0.03 },\n dawn: { distortion: 0.64, swirl: 0.16, speed: 0.34, grainOverlay: 0.02 },\n sunrise: { distortion: 0.72, swirl: 0.2, speed: 0.44, grainOverlay: 0.02 },\n morning: { distortion: 0.68, swirl: 0.16, speed: 0.54, grainOverlay: 0.01 },\n 'solar-noon': { distortion: 0.6, swirl: 0.12, speed: 0.62, grainOverlay: 0.01 },\n afternoon: { distortion: 0.64, swirl: 0.14, speed: 0.54, grainOverlay: 0.01 },\n sunset: { distortion: 0.74, swirl: 0.24, speed: 0.4, grainOverlay: 0.02 },\n dusk: { distortion: 0.6, swirl: 0.18, speed: 0.28, grainOverlay: 0.03 },\n};\n\n// AURORA — the most variable. At midnight/night the lights dance at full\n// intensity. During the day the aurora is invisible — calm, warm sky.\n// At dusk/sunset the bands return. Grain is very low — aurora is luminous.\nconst AURORA_MOTION: MotionProfile = {\n midnight: { distortion: 0.8, swirl: 0.44, speed: 0.7, grainOverlay: 0.02 },\n night: { distortion: 0.86, swirl: 0.5, speed: 0.8, grainOverlay: 0.02 },\n dawn: { distortion: 0.62, swirl: 0.32, speed: 0.52, grainOverlay: 0.03 },\n sunrise: { distortion: 0.38, swirl: 0.16, speed: 0.3, grainOverlay: 0.04 },\n morning: { distortion: 0.18, swirl: 0.05, speed: 0.18, grainOverlay: 0.04 },\n 'solar-noon': { distortion: 0.14, swirl: 0.03, speed: 0.14, grainOverlay: 0.03 },\n afternoon: { distortion: 0.18, swirl: 0.05, speed: 0.18, grainOverlay: 0.04 },\n sunset: { distortion: 0.46, swirl: 0.24, speed: 0.42, grainOverlay: 0.03 },\n dusk: { distortion: 0.68, swirl: 0.38, speed: 0.6, grainOverlay: 0.02 },\n};\n\n// ─── Skin → profile map ───────────────────────────────────────────────────────\n\nexport const SKIN_MOTION_PROFILES: Record<DesignMode, MotionProfile> = {\n void: VOID_MOTION,\n parchment: PARCHMENT_MOTION,\n signal: SIGNAL_MOTION,\n meridian: MERIDIAN_MOTION,\n sundial: SUNDIAL_MOTION,\n paper: PAPER_MOTION,\n foundry: FOUNDRY_MOTION,\n mineral: MINERAL_MOTION,\n tide: TIDE_MOTION,\n aurora: AURORA_MOTION,\n};\n","'use client';\n// ════════════════════════════════════════════════════════════════════════════\n// FILE: components/solar-shader-bg.client.tsx\n// ════════════════════════════════════════════════════════════════════════════\n\nimport {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { SolarBlend, SolarPhase } from '../hooks/useSolarPosition';\nimport {\n UNIVERSAL_SEASON_MODIFIERS,\n applySeasonalModifier,\n resolveSeasonalModifier,\n} from '../lib/seasonal-blend';\nimport { lerpColor } from '../lib/solar-lerp';\nimport type { SeasonalBlend } from '../lib/useSeason';\nimport { useSolarTheme } from '../provider/solar-theme-provider';\nimport type {\n ShaderImage,\n ShaderMotion,\n ShaderPalette,\n SkinDefinition,\n} from '../skins/types/widget-skin.types';\nimport { SKIN_MOTION_PROFILES, UNIVERSAL_PHASE_MOTION } from './shader-motion-profiles';\n\n// ─── Variant context ──────────────────────────────────────────────────────────\n\nexport type ShaderVariant = 'showcase' | 'dashboard' | 'editorial';\n\nconst ShaderVariantCtx = createContext<ShaderVariant>('showcase');\n\nexport function ShaderVariantProvider({\n variant,\n children,\n}: {\n variant: ShaderVariant;\n children: React.ReactNode;\n}) {\n return <ShaderVariantCtx.Provider value={variant}>{children}</ShaderVariantCtx.Provider>;\n}\n\nexport function useShaderVariant(): ShaderVariant {\n return useContext(ShaderVariantCtx);\n}\n\n// ─── Dashboard motion modifier ────────────────────────────────────────────────\n\nconst DASHBOARD_MOTION_SCALE = {\n speed: 0.45,\n distortion: 0.5,\n swirl: 0.4,\n grainOverlay: 1.3,\n} as const;\n\nconst DASHBOARD_OPACITY_SCALE = 0.65;\n\nfunction applyDashboardMotion(m: ShaderMotion): ShaderMotion {\n return {\n speed: m.speed * DASHBOARD_MOTION_SCALE.speed,\n distortion: m.distortion * DASHBOARD_MOTION_SCALE.distortion,\n swirl: m.swirl * DASHBOARD_MOTION_SCALE.swirl,\n grainOverlay: Math.min(1, m.grainOverlay * DASHBOARD_MOTION_SCALE.grainOverlay),\n };\n}\n\n// ─── Editorial motion modifier ────────────────────────────────────────────────\n\nconst EDITORIAL_MOTION_SCALE = {\n speed: 0.3,\n distortion: 0.35,\n swirl: 0.25,\n grainOverlay: 1.4,\n} as const;\n\nconst EDITORIAL_OPACITY_SCALE = 0.35;\n\nfunction applyEditorialMotion(m: ShaderMotion): ShaderMotion {\n return {\n speed: m.speed * EDITORIAL_MOTION_SCALE.speed,\n distortion: m.distortion * EDITORIAL_MOTION_SCALE.distortion,\n swirl: m.swirl * EDITORIAL_MOTION_SCALE.swirl,\n grainOverlay: Math.min(1, m.grainOverlay * EDITORIAL_MOTION_SCALE.grainOverlay),\n };\n}\n\nfunction mixToward(hex: string, target: string, mix: number): string {\n return lerpColor(hex, target, mix);\n}\n\nfunction applyEditorialPalette(p: ShaderPalette): ShaderPalette {\n const darkBase = '#08080c';\n const darkVignette = '#030306';\n return {\n ...p,\n colors: p.colors.map((c) => mixToward(c, darkBase, 0.92)) as [string, string, string, string],\n colorBack: mixToward(p.colorBack, darkBase, 0.95),\n vignette: mixToward(p.vignette, darkVignette, 0.9),\n cssFallback: `linear-gradient(135deg, ${darkBase} 0%, #0c0c14 100%)`,\n };\n}\n\n// ─── Interpolation helpers ────────────────────────────────────────────────────\n\nfunction lerpNum(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nfunction lerpMotion(a: ShaderMotion, b: ShaderMotion, t: number): ShaderMotion {\n return {\n distortion: lerpNum(a.distortion, b.distortion, t),\n swirl: lerpNum(a.swirl, b.swirl, t),\n speed: lerpNum(a.speed, b.speed, t),\n grainOverlay: lerpNum(a.grainOverlay, b.grainOverlay, t),\n };\n}\n\nfunction lerpPalette(a: ShaderPalette, b: ShaderPalette, t: number): ShaderPalette {\n return {\n colors: a.colors.map((ca, i) => lerpColor(ca, b.colors[i] ?? ca, t)) as [\n string,\n string,\n string,\n string,\n ],\n colorBack: lerpColor(a.colorBack, b.colorBack, t),\n opacity: lerpNum(a.opacity, b.opacity, t),\n vignette: lerpColor(a.vignette, b.vignette, t),\n cssFallback: a.cssFallback,\n image: a.image,\n };\n}\n\n// ─── Motion profile resolution ────────────────────────────────────────────────\n\nfunction resolveMotionProfile(skin: SkinDefinition): Record<SolarPhase, ShaderMotion> {\n if (skin.shaderMotion) return skin.shaderMotion;\n return SKIN_MOTION_PROFILES[skin.id] ?? UNIVERSAL_PHASE_MOTION;\n}\n\n// ─── Image config resolution ──────────────────────────────────────────────────\n\nfunction resolveImage(skin: SkinDefinition, palette: ShaderPalette): ShaderImage | undefined {\n return palette.image ?? skin.defaultImage;\n}\n\n// ─── Core config computation ──────────────────────────────────────────────────\n\nfunction computeConfig(\n skin: SkinDefinition,\n blend: SolarBlend,\n seasonal?: { blend: SeasonalBlend; disabled: boolean },\n) {\n const motionProfile = resolveMotionProfile(skin);\n const { phase, nextPhase, t } = blend;\n let palette = lerpPalette(skin.shaderPalettes[phase], skin.shaderPalettes[nextPhase], t);\n\n if (seasonal && !seasonal.disabled) {\n const mod = resolveSeasonalModifier(seasonal.blend, {\n ...UNIVERSAL_SEASON_MODIFIERS,\n ...skin.seasonalModifiers,\n });\n palette = applySeasonalModifier(palette, mod);\n }\n\n return {\n palette,\n motion: lerpMotion(motionProfile[phase], motionProfile[nextPhase], t),\n };\n}\n\n// ─── Hex → RGB helper ─────────────────────────────────────────────────────────\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? [\n Number.parseInt(result[1], 16) / 255,\n Number.parseInt(result[2], 16) / 255,\n Number.parseInt(result[3], 16) / 255,\n ]\n : [0, 0, 0];\n}\n\n// ─── WebGL SolarFlare shader ──────────────────────────────────────────────────\n// Inspired by a radiant solar flare effect. Uses the skin's 4 shader palette\n// colors as a blended flare color against the colorBack background.\n// Motion profile drives speed, intensity, spread and pulse.\n\nconst VS_SOURCE = `#version 300 es\n in vec2 a_position;\n void main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n }\n`;\n\nconst FS_SOURCE = `#version 300 es\n precision highp float;\n\n uniform vec2 r; // resolution\n uniform float t; // time\n uniform vec3 u_bg; // background color (colorBack)\n uniform vec3 u_c0; // palette color 0\n uniform vec3 u_c1; // palette color 1\n uniform vec3 u_c2; // palette color 2\n uniform vec3 u_c3; // palette color 3\n uniform float u_intensity;\n uniform float u_spread;\n uniform float u_pulseRate;\n uniform float u_speed;\n uniform float u_grain;\n\n out vec4 o;\n\n // Hash for grain\n float hash(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);\n }\n\n void main() {\n vec4 FC = gl_FragCoord;\n vec2 p = (FC.xy * 2. - r) / r.y;\n\n // Average all 4 palette colors\n vec3 avgColor = (u_c0 + u_c1 + u_c2 + u_c3) * 0.25;\n\n // Normalize to a fixed luminance so the flare shape is identical\n // across all skins regardless of how bright/dark the palette is.\n // Dark palettes (Void midnight ≈ 0.01) and bright palettes\n // (Paper morning ≈ 0.95) both produce the same-sized sun.\n float lum = dot(avgColor, vec3(0.299, 0.587, 0.114));\n vec3 flareColor = lum > 0.001\n ? avgColor * (0.30 / lum) // rescale to target luminance 0.30\n : vec3(0.30); // neutral grey fallback\n\n // Solar flare radiance — matches the reference SolarFlare shader.\n // length(p) creates a circular distance field from center.\n // The exp(mod(dot(...))) creates alive, pulsing texture inside the sun.\n float l = u_intensity - length(p);\n\n o = tanh(\n vec4(flareColor, 0.0)\n / max(l, -l * u_spread)\n / exp(\n mod(dot(FC, sin(FC.yxyx)) + t * u_speed, 2.0)\n + sin(t * u_speed + sin(t * u_speed / u_pulseRate + p.y))\n )\n );\n\n // Film grain\n float grain = (hash(FC.xy + fract(t)) - 0.5) * u_grain;\n\n // Composite: background + flare glow + grain\n o = vec4(u_bg + o.rgb + grain, 1.0);\n }\n`;\n\ninterface SolarFlareCanvasProps {\n backgroundColor: string;\n colors: [string, string, string, string];\n speed: number;\n intensity: number;\n spread: number;\n pulseRate: number;\n grain: number;\n}\n\nfunction SolarFlareCanvas({\n backgroundColor,\n colors,\n speed,\n intensity,\n spread,\n pulseRate,\n grain,\n}: SolarFlareCanvasProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const animationRef = useRef<number | null>(null);\n\n const configRef = useRef({\n backgroundColor,\n colors,\n speed,\n intensity,\n spread,\n pulseRate,\n grain,\n });\n\n useEffect(() => {\n configRef.current = {\n backgroundColor,\n colors,\n speed,\n intensity,\n spread,\n pulseRate,\n grain,\n };\n }, [backgroundColor, colors, speed, intensity, spread, pulseRate, grain]);\n\n // useLayoutEffect ensures WebGL context is created and the first frame is\n // drawn *before* the browser paints, preventing a blank-canvas flash on\n // client-side navigation remounts.\n useLayoutEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const gl = canvas.getContext('webgl2');\n if (!gl) return;\n\n const createShader = (type: number, source: string) => {\n const shader = gl.createShader(type);\n if (!shader) return null;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n gl.deleteShader(shader);\n return null;\n }\n return shader;\n };\n\n const vs = createShader(gl.VERTEX_SHADER, VS_SOURCE);\n const fs = createShader(gl.FRAGMENT_SHADER, FS_SOURCE);\n if (!vs || !fs) return;\n\n const program = gl.createProgram();\n if (!program) return;\n\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;\n\n const positionBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),\n gl.STATIC_DRAW,\n );\n\n const posLoc = gl.getAttribLocation(program, 'a_position');\n gl.enableVertexAttribArray(posLoc);\n gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);\n\n const loc = {\n r: gl.getUniformLocation(program, 'r'),\n t: gl.getUniformLocation(program, 't'),\n bg: gl.getUniformLocation(program, 'u_bg'),\n c0: gl.getUniformLocation(program, 'u_c0'),\n c1: gl.getUniformLocation(program, 'u_c1'),\n c2: gl.getUniformLocation(program, 'u_c2'),\n c3: gl.getUniformLocation(program, 'u_c3'),\n intensity: gl.getUniformLocation(program, 'u_intensity'),\n spread: gl.getUniformLocation(program, 'u_spread'),\n pulseRate: gl.getUniformLocation(program, 'u_pulseRate'),\n speed: gl.getUniformLocation(program, 'u_speed'),\n grain: gl.getUniformLocation(program, 'u_grain'),\n };\n\n const resizeCanvas = () => {\n const dpr = window.devicePixelRatio || 1;\n canvas.width = Math.round(canvas.offsetWidth * dpr);\n canvas.height = Math.round(canvas.offsetHeight * dpr);\n gl.viewport(0, 0, canvas.width, canvas.height);\n };\n\n window.addEventListener('resize', resizeCanvas);\n resizeCanvas();\n\n const startTime = performance.now();\n\n const render = () => {\n const cfg = configRef.current;\n gl.useProgram(program);\n\n gl.uniform2f(loc.r, canvas.width, canvas.height);\n gl.uniform1f(loc.t, (performance.now() - startTime) / 1000);\n\n const bg = hexToRgb(cfg.backgroundColor);\n gl.uniform3f(loc.bg, bg[0], bg[1], bg[2]);\n\n const c0 = hexToRgb(cfg.colors[0]);\n const c1 = hexToRgb(cfg.colors[1]);\n const c2 = hexToRgb(cfg.colors[2]);\n const c3 = hexToRgb(cfg.colors[3]);\n gl.uniform3f(loc.c0, c0[0], c0[1], c0[2]);\n gl.uniform3f(loc.c1, c1[0], c1[1], c1[2]);\n gl.uniform3f(loc.c2, c2[0], c2[1], c2[2]);\n gl.uniform3f(loc.c3, c3[0], c3[1], c3[2]);\n\n gl.uniform1f(loc.intensity, cfg.intensity);\n gl.uniform1f(loc.spread, cfg.spread);\n gl.uniform1f(loc.pulseRate, cfg.pulseRate);\n gl.uniform1f(loc.speed, cfg.speed);\n gl.uniform1f(loc.grain, cfg.grain);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n animationRef.current = requestAnimationFrame(render);\n };\n\n render();\n\n return () => {\n window.removeEventListener('resize', resizeCanvas);\n if (animationRef.current) cancelAnimationFrame(animationRef.current);\n gl.deleteProgram(program);\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n gl.deleteBuffer(positionBuffer);\n };\n }, []);\n\n return (\n <canvas\n ref={canvasRef}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n }}\n />\n );\n}\n\n// ─── Client-side mount tracking ───────────────────────────────────────────────\n// After the very first hydration, any subsequent mount is a client-side navigation\n// remount — no SSR/hydration mismatch is possible, so the canvas can render\n// immediately without the `mounted` gate (which would cause a one-frame blink).\nlet _hasHydrated = false;\n\n// ─── Props ────────────────────────────────────────────────────────────────────\n\nexport interface SolarShaderBgProps {\n skinOverride?: SkinDefinition;\n blendOverride?: SolarBlend;\n opacityOverride?: number;\n variant?: ShaderVariant;\n className?: string;\n style?: React.CSSProperties;\n}\n\n// ─── SolarShaderBg ────────────────────────────────────────────────────────────\n\nexport function SolarShaderBg({\n skinOverride,\n blendOverride,\n opacityOverride,\n variant: variantProp,\n className,\n style,\n}: SolarShaderBgProps = {}) {\n const theme = useSolarTheme();\n const { activeSkin, blend: contextBlend, seasonalBlend } = theme;\n const contextVariant = useShaderVariant();\n\n const skin = skinOverride ?? activeSkin;\n const blend = blendOverride ?? contextBlend;\n const variant = variantProp ?? contextVariant;\n const seasonal = { blend: seasonalBlend, disabled: false };\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentional fine-grained deps — blend object reference changes every render; subscribing to specific fields avoids thrashing computeConfig\n const { palette, motion: rawMotion } = useMemo(\n () => computeConfig(skin, blend, seasonal),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [blend.phase, blend.nextPhase, blend.t, skin, seasonalBlend.season, seasonalBlend.t],\n );\n\n const variantPalette = variant === 'editorial' ? applyEditorialPalette(palette) : palette;\n\n const shaderMotion =\n variant === 'dashboard'\n ? applyDashboardMotion(rawMotion)\n : variant === 'editorial'\n ? applyEditorialMotion(rawMotion)\n : rawMotion;\n\n const resolvedOpacity =\n opacityOverride ??\n (variant === 'dashboard'\n ? variantPalette.opacity * DASHBOARD_OPACITY_SCALE\n : variant === 'editorial'\n ? variantPalette.opacity * EDITORIAL_OPACITY_SCALE\n : variantPalette.opacity);\n\n const imageConfig = resolveImage(skin, variantPalette);\n\n // Gate dynamic layers behind a client-only flag to avoid hydration mismatches.\n // On the very first mount (SSR hydration), start as false and flip via effect.\n // On subsequent mounts (client-side navigation), start as true immediately\n // so the WebGL canvas renders without a one-frame blink.\n const [mounted, setMounted] = useState(_hasHydrated);\n useLayoutEffect(() => {\n _hasHydrated = true;\n setMounted(true);\n }, []);\n\n useEffect(() => {\n document.documentElement.style.setProperty('--solar-shader-bg', variantPalette.colorBack);\n return () => {\n document.documentElement.style.removeProperty('--solar-shader-bg');\n };\n }, [variantPalette.colorBack]);\n\n // Map shader motion → SolarFlare parameters\n // intensity ≥ 3.5 ensures the radial edge is always off-screen (max corner\n // distance in normalised coords is ~1.15), so the glow fills the entire\n // viewport as an atmospheric wash — no visible \"sun\" circle.\n const flareSpeed = 0.6 + shaderMotion.speed * 0.8;\n const flareIntensity = 3.5 + shaderMotion.distortion * 0.5;\n const flareSpread = 8.0 + shaderMotion.swirl * 8.0;\n const flarePulseRate = 0.4 + (1.0 - shaderMotion.swirl) * 0.5;\n const flareGrain = shaderMotion.grainOverlay * 0.12;\n\n return (\n <div\n className={className}\n suppressHydrationWarning\n style={{\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n background: variantPalette.cssFallback || variantPalette.colorBack,\n ...style,\n }}\n >\n {mounted && (\n <>\n {/* ── Layer 1: WebGL SolarFlare (renders instantly — no flash) ── */}\n <div className=\"absolute inset-0\" style={{ opacity: resolvedOpacity }}>\n <SolarFlareCanvas\n backgroundColor={variantPalette.colorBack}\n colors={variantPalette.colors}\n speed={flareSpeed}\n intensity={flareIntensity}\n spread={flareSpread}\n pulseRate={flarePulseRate}\n grain={flareGrain}\n />\n </div>\n\n {/* ── Layer 2: Atmospheric image ──────────────────────────────── */}\n {imageConfig && (\n <div\n className=\"absolute inset-0\"\n aria-hidden\n style={{\n opacity: imageConfig.opacity ?? 0.18,\n pointerEvents: 'none',\n background: 'var(--solar-accent)',\n WebkitMaskImage: `url(${imageConfig.src})`,\n maskImage: `url(${imageConfig.src})`,\n WebkitMaskSize: 'cover',\n maskSize: 'cover',\n WebkitMaskPosition: imageConfig.objectPosition ?? 'center center',\n maskPosition: imageConfig.objectPosition ?? 'center center',\n WebkitMaskRepeat: 'no-repeat',\n maskRepeat: 'no-repeat',\n }}\n />\n )}\n\n {/* ── Layer 3: Vignette ───────────────────────────────────────── */}\n <div\n className=\"absolute inset-0\"\n aria-hidden\n style={{\n pointerEvents: 'none',\n background: `radial-gradient(ellipse 85% 70% at 50% 50%, transparent 0%, ${variantPalette.vignette} 100%)`,\n }}\n />\n </>\n )}\n </div>\n );\n}\n\n// ─── SolarShaderBgFull ────────────────────────────────────────────────────────\n\nexport function SolarShaderBgFull(props: SolarShaderBgProps = {}) {\n return <SolarShaderBg {...props} style={{ zIndex: 0, ...props.style }} />;\n}\n\n// ─── useSolarShaderConfig ─────────────────────────────────────────────────────\n\nexport function useSolarShaderConfig(\n opts: {\n skinOverride?: SkinDefinition;\n blendOverride?: SolarBlend;\n } = {},\n) {\n const { activeSkin, blend: contextBlend, seasonalBlend } = useSolarTheme();\n const skin = opts.skinOverride ?? activeSkin;\n const blend = opts.blendOverride ?? contextBlend;\n const seasonal = { blend: seasonalBlend, disabled: false };\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentional fine-grained deps — blend object reference changes every render; subscribing to specific fields avoids thrashing computeConfig\n return useMemo(\n () => computeConfig(skin, blend, seasonal),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [blend.phase, blend.nextPhase, blend.t, skin, seasonalBlend.season, seasonalBlend.t],\n );\n}\n"],"mappings":"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"}
@@ -497,7 +497,7 @@ var require_tz = /* @__PURE__ */ __commonJS({ "node_modules/tz-lookup/tz.js": ((
497
497
  const RAD = Math.PI / 180;
498
498
  const DEG = 180 / Math.PI;
499
499
  /** Day of year (1–366) */
500
- function dayOfYear(date) {
500
+ function dayOfYear$1(date) {
501
501
  const start = new Date(date.getFullYear(), 0, 0);
502
502
  const diff = date.getTime() - start.getTime();
503
503
  return Math.floor(diff / 864e5);
@@ -548,7 +548,7 @@ function riseSetMinutes(latRad, declRad, altitudeDeg, longitudeDeg, utcOffsetMin
548
548
  * Compute full solar position for a given date + location
549
549
  */
550
550
  function computeSolarPosition(date, latitudeDeg, longitudeDeg, utcOffsetMinutes) {
551
- const { eot, decl } = equationOfTimeAndDeclination(dayOfYear(date));
551
+ const { eot, decl } = equationOfTimeAndDeclination(dayOfYear$1(date));
552
552
  const latRad = latitudeDeg * RAD;
553
553
  const declRad = decl * RAD;
554
554
  const localMinutes = date.getHours() * 60 + date.getMinutes() + date.getSeconds() / 60;
@@ -6818,6 +6818,172 @@ function injectWidgetCSS() {
6818
6818
  injected = true;
6819
6819
  }
6820
6820
 
6821
+ //#endregion
6822
+ //#region src/lib/seasonal-blend.ts
6823
+ const IDENTITY_MODIFIER = {
6824
+ saturationScale: 1,
6825
+ lightnessShift: 0,
6826
+ hueRotateDeg: 0,
6827
+ tintStrength: 0
6828
+ };
6829
+ const UNIVERSAL_SEASON_MODIFIERS = {
6830
+ spring: {
6831
+ saturationScale: 1.1,
6832
+ lightnessShift: .02,
6833
+ hueRotateDeg: 8,
6834
+ tintColor: "#a8d8a0",
6835
+ tintStrength: .06
6836
+ },
6837
+ summer: {
6838
+ saturationScale: 1.18,
6839
+ lightnessShift: .03,
6840
+ hueRotateDeg: 10,
6841
+ tintColor: "#ffe066",
6842
+ tintStrength: .07
6843
+ },
6844
+ autumn: {
6845
+ saturationScale: .9,
6846
+ lightnessShift: -.03,
6847
+ hueRotateDeg: -18,
6848
+ tintColor: "#c8692a",
6849
+ tintStrength: .1
6850
+ },
6851
+ winter: {
6852
+ saturationScale: .82,
6853
+ lightnessShift: -.04,
6854
+ hueRotateDeg: -25,
6855
+ tintColor: "#8ab4d4",
6856
+ tintStrength: .08
6857
+ }
6858
+ };
6859
+ function hexToRgb$2(hex$1) {
6860
+ const clean = hex$1.replace("#", "");
6861
+ return [
6862
+ Number.parseInt(clean.slice(0, 2), 16) / 255,
6863
+ Number.parseInt(clean.slice(2, 4), 16) / 255,
6864
+ Number.parseInt(clean.slice(4, 6), 16) / 255
6865
+ ];
6866
+ }
6867
+ function rgbToHsl(r, g, b) {
6868
+ const max = Math.max(r, g, b);
6869
+ const min = Math.min(r, g, b);
6870
+ const l = (max + min) / 2;
6871
+ let h = 0;
6872
+ let s = 0;
6873
+ if (max !== min) {
6874
+ const d = max - min;
6875
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
6876
+ switch (max) {
6877
+ case r:
6878
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
6879
+ break;
6880
+ case g:
6881
+ h = ((b - r) / d + 2) / 6;
6882
+ break;
6883
+ case b:
6884
+ h = ((r - g) / d + 4) / 6;
6885
+ break;
6886
+ }
6887
+ }
6888
+ return [
6889
+ h,
6890
+ s,
6891
+ l
6892
+ ];
6893
+ }
6894
+ function hslToRgb(h, s, l) {
6895
+ if (s === 0) return [
6896
+ l,
6897
+ l,
6898
+ l
6899
+ ];
6900
+ const hue2rgb = (p$1, q$1, _t) => {
6901
+ let t = _t;
6902
+ if (t < 0) t += 1;
6903
+ if (t > 1) t -= 1;
6904
+ if (t < 1 / 6) return p$1 + (q$1 - p$1) * 6 * t;
6905
+ if (t < 1 / 2) return q$1;
6906
+ if (t < 2 / 3) return p$1 + (q$1 - p$1) * (2 / 3 - t) * 6;
6907
+ return p$1;
6908
+ };
6909
+ const q = l < .5 ? l * (1 + s) : l + s - l * s;
6910
+ const p = 2 * l - q;
6911
+ return [
6912
+ hue2rgb(p, q, h + 1 / 3),
6913
+ hue2rgb(p, q, h),
6914
+ hue2rgb(p, q, h - 1 / 3)
6915
+ ];
6916
+ }
6917
+ function rgbToHex(r, g, b) {
6918
+ const toHex = (x) => Math.round(Math.max(0, Math.min(1, x)) * 255).toString(16).padStart(2, "0");
6919
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
6920
+ }
6921
+ function lerpNum$6(a, b, t) {
6922
+ return a + (b - a) * t;
6923
+ }
6924
+ /**
6925
+ * Apply a SeasonalModifier to a single hex color.
6926
+ * Works in HSL space for hue rotation + saturation + lightness,
6927
+ * then blends toward tintColor if provided.
6928
+ */
6929
+ function shiftColor(hex$1, mod) {
6930
+ if (!hex$1 || hex$1.length < 7) return hex$1;
6931
+ const [r, g, b] = hexToRgb$2(hex$1);
6932
+ let [h, s, l] = rgbToHsl(r, g, b);
6933
+ h = ((h + mod.hueRotateDeg / 360) % 1 + 1) % 1;
6934
+ s = Math.max(0, Math.min(1, s * mod.saturationScale));
6935
+ l = Math.max(0, Math.min(1, l + mod.lightnessShift));
6936
+ const [sr$5, sg, sb] = hslToRgb(h, s, l);
6937
+ let result = rgbToHex(sr$5, sg, sb);
6938
+ if (mod.tintColor && mod.tintStrength > 0) {
6939
+ const [tr, tg, tb] = hexToRgb$2(mod.tintColor);
6940
+ const [fr, fg, fb] = hexToRgb$2(result);
6941
+ result = rgbToHex(lerpNum$6(fr, tr, mod.tintStrength), lerpNum$6(fg, tg, mod.tintStrength), lerpNum$6(fb, tb, mod.tintStrength));
6942
+ }
6943
+ return result;
6944
+ }
6945
+ /**
6946
+ * Linearly interpolate between two SeasonalModifiers.
6947
+ * Used for smooth crossfades at solstice/equinox boundaries.
6948
+ */
6949
+ function lerpModifier(a, b, t) {
6950
+ return {
6951
+ saturationScale: lerpNum$6(a.saturationScale, b.saturationScale, t),
6952
+ lightnessShift: lerpNum$6(a.lightnessShift, b.lightnessShift, t),
6953
+ hueRotateDeg: lerpNum$6(a.hueRotateDeg, b.hueRotateDeg, t),
6954
+ tintStrength: lerpNum$6(a.tintStrength, b.tintStrength, t),
6955
+ tintColor: t < .5 ? a.tintColor : b.tintColor
6956
+ };
6957
+ }
6958
+ /**
6959
+ * Apply a SeasonalModifier to every color in a ShaderPalette.
6960
+ * Returns a new palette — the original is not mutated.
6961
+ *
6962
+ * The modifier shifts hue, saturation, and lightness then applies a tint wash.
6963
+ * The result is blended back toward the original at `strength` 0→1:
6964
+ * 0 = identity, 1 = fully modified.
6965
+ */
6966
+ function applySeasonalModifier(palette, mod, strength = 1) {
6967
+ if (strength <= 0) return palette;
6968
+ const effective = strength < 1 ? lerpModifier(IDENTITY_MODIFIER, mod, strength) : mod;
6969
+ return {
6970
+ ...palette,
6971
+ colors: palette.colors.map((c) => shiftColor(c, effective)),
6972
+ colorBack: shiftColor(palette.colorBack, effective),
6973
+ vignette: shiftColor(palette.vignette, effective)
6974
+ };
6975
+ }
6976
+ /**
6977
+ * Given a SeasonalBlend and a modifier map, return the interpolated modifier
6978
+ * ready to pass to applySeasonalModifier.
6979
+ *
6980
+ * @param blend The SeasonalBlend from useSeason()
6981
+ * @param modifiers Per-season modifier map (skin's or universal default)
6982
+ */
6983
+ function resolveSeasonalModifier(blend, modifiers = UNIVERSAL_SEASON_MODIFIERS) {
6984
+ return lerpModifier(modifiers[blend.season] ?? IDENTITY_MODIFIER, modifiers[blend.nextSeason] ?? IDENTITY_MODIFIER, blend.t);
6985
+ }
6986
+
6821
6987
  //#endregion
6822
6988
  //#region src/lib/solar-lerp.ts
6823
6989
  /**
@@ -7316,6 +7482,96 @@ function getBrowserTimezone() {
7316
7482
  }
7317
7483
  }
7318
7484
 
7485
+ //#endregion
7486
+ //#region src/lib/useSeason.ts
7487
+ /** Day-of-year for each northern hemisphere season start (approximate). */
7488
+ const SEASON_STARTS_NORTH = {
7489
+ spring: 79,
7490
+ summer: 172,
7491
+ autumn: 265,
7492
+ winter: 355
7493
+ };
7494
+ /** Days either side of a transition within which to apply crossfade. */
7495
+ const CROSSFADE_DAYS = 14;
7496
+ const SEASON_ORDER = [
7497
+ "spring",
7498
+ "summer",
7499
+ "autumn",
7500
+ "winter"
7501
+ ];
7502
+ function dayOfYear(date) {
7503
+ const start = new Date(date.getFullYear(), 0, 0);
7504
+ const diff = date.getTime() - start.getTime();
7505
+ return Math.floor(diff / 864e5);
7506
+ }
7507
+ function daysInYear(year) {
7508
+ return new Date(year, 1, 29).getMonth() === 1 ? 366 : 365;
7509
+ }
7510
+ function computeSeasonalBlend(doy, isNorthern, totalDays) {
7511
+ const adjustedDoy = isNorthern ? doy : (doy + 182) % totalDays || totalDays;
7512
+ let currentSeason = "winter";
7513
+ let currentIdx = 0;
7514
+ for (let i$1 = SEASON_ORDER.length - 1; i$1 >= 0; i$1--) if (adjustedDoy >= SEASON_STARTS_NORTH[SEASON_ORDER[i$1]]) {
7515
+ currentSeason = SEASON_ORDER[i$1];
7516
+ currentIdx = i$1;
7517
+ break;
7518
+ }
7519
+ const nextSeason = SEASON_ORDER[(currentIdx + 1) % SEASON_ORDER.length];
7520
+ let daysToNext = SEASON_STARTS_NORTH[nextSeason] - adjustedDoy;
7521
+ if (daysToNext < 0) daysToNext += totalDays;
7522
+ let t = 0;
7523
+ if (daysToNext <= CROSSFADE_DAYS) {
7524
+ const raw = (CROSSFADE_DAYS - daysToNext) / CROSSFADE_DAYS;
7525
+ const clamped = Math.max(0, Math.min(1, raw));
7526
+ t = clamped * clamped * (3 - 2 * clamped);
7527
+ }
7528
+ return {
7529
+ season: currentSeason,
7530
+ nextSeason,
7531
+ t
7532
+ };
7533
+ }
7534
+ /**
7535
+ * Pure function — compute the SeasonalBlend for a given date and latitude.
7536
+ *
7537
+ * @param date The date to evaluate (default: now)
7538
+ * @param latitudeN Decimal degrees, positive = north (default: 0)
7539
+ * @param override Force a fixed season, bypassing computation
7540
+ */
7541
+ function getSeasonalBlend(date = /* @__PURE__ */ new Date(), latitudeN = 0, override) {
7542
+ if (override) return {
7543
+ season: override,
7544
+ nextSeason: SEASON_ORDER[(SEASON_ORDER.indexOf(override) + 1) % SEASON_ORDER.length],
7545
+ t: 0
7546
+ };
7547
+ const isNorthern = latitudeN >= 0;
7548
+ return computeSeasonalBlend(dayOfYear(date), isNorthern, daysInYear(date.getFullYear()));
7549
+ }
7550
+ /**
7551
+ * Hook — returns the current SeasonalBlend, re-evaluated once per hour.
7552
+ *
7553
+ * @param latitudeN Decimal degrees, positive = north.
7554
+ * Pass null while coordinates are still loading.
7555
+ * @param override Force a specific season (for dev previews / user settings).
7556
+ * @param simulatedDate Use a specific date instead of now (for SolarDevTools).
7557
+ */
7558
+ function useSeason(latitudeN, override, simulatedDate) {
7559
+ const [blend, setBlend] = useState(() => getSeasonalBlend(simulatedDate ?? /* @__PURE__ */ new Date(), latitudeN ?? 0, override));
7560
+ useEffect(() => {
7561
+ const recompute = () => {
7562
+ setBlend(getSeasonalBlend(simulatedDate ?? /* @__PURE__ */ new Date(), latitudeN ?? 0, override));
7563
+ };
7564
+ recompute();
7565
+ const id$2 = setInterval(recompute, 3600 * 1e3);
7566
+ return () => clearInterval(id$2);
7567
+ }, [
7568
+ latitudeN,
7569
+ override,
7570
+ simulatedDate
7571
+ ]);
7572
+ return blend;
7573
+ }
7574
+
7319
7575
  //#endregion
7320
7576
  //#region node_modules/motion/dist/es/framer-motion/dist/es/context/LayoutGroupContext.mjs
7321
7577
  const LayoutGroupContext = createContext({});
@@ -54528,7 +54784,7 @@ function getAccentFg(accent) {
54528
54784
  * - scopeId = undefined → writes to :root (global / singleton mode)
54529
54785
  * - scopeId = "sol-scope-3" → writes to #sol-scope-3 (isolated mode)
54530
54786
  * The scoped vars override :root vars for all descendants of that wrapper div. */
54531
- function writeCssVars(skin, phase, t = 0, nextPhase, scopeId) {
54787
+ function writeCssVars(skin, phase, t = 0, nextPhase, scopeId, seasonal) {
54532
54788
  if (typeof document === "undefined") return;
54533
54789
  const from = skin.phaseVars[phase];
54534
54790
  const to = skin.phaseVars[nextPhase ?? phase];
@@ -54541,9 +54797,22 @@ function writeCssVars(skin, phase, t = 0, nextPhase, scopeId) {
54541
54797
  const surface = lerp(from.surface, to.surface);
54542
54798
  const shaderPalFrom = skin.shaderPalettes[phase];
54543
54799
  const shaderPalTo = skin.shaderPalettes[nextPhase ?? phase];
54544
- const shaderVignette = t > 0 ? lerpHex(shaderPalFrom.vignette, shaderPalTo.vignette, t) : shaderPalFrom.vignette;
54545
- const shaderColorBack = t > 0 ? lerpHex(shaderPalFrom.colorBack, shaderPalTo.colorBack, t) : shaderPalFrom.colorBack;
54800
+ let shaderVignette = t > 0 ? lerpHex(shaderPalFrom.vignette, shaderPalTo.vignette, t) : shaderPalFrom.vignette;
54801
+ let shaderColorBack = t > 0 ? lerpHex(shaderPalFrom.colorBack, shaderPalTo.colorBack, t) : shaderPalFrom.colorBack;
54546
54802
  const shaderFallback = shaderPalFrom.cssFallback;
54803
+ if (seasonal && !seasonal.disabled) {
54804
+ const mod = resolveSeasonalModifier(seasonal.blend, {
54805
+ ...UNIVERSAL_SEASON_MODIFIERS,
54806
+ ...skin.seasonalModifiers
54807
+ });
54808
+ const tempPalette = applySeasonalModifier({
54809
+ ...shaderPalFrom,
54810
+ vignette: shaderVignette,
54811
+ colorBack: shaderColorBack
54812
+ }, mod);
54813
+ shaderVignette = tempPalette.vignette;
54814
+ shaderColorBack = tempPalette.colorBack;
54815
+ }
54547
54816
  if (!scopeId) {
54548
54817
  const root = document.documentElement;
54549
54818
  if (root.getAttribute("data-solar-skin") !== skin.id) root.setAttribute("data-solar-skin", skin.id);
@@ -54585,12 +54854,19 @@ const SolarThemeCtx = createContext({
54585
54854
  simulatedDate: void 0,
54586
54855
  setSimulatedDate: noop,
54587
54856
  customPalettes: void 0,
54588
- setCustomPalettes: noop
54857
+ setCustomPalettes: noop,
54858
+ season: "spring",
54859
+ seasonalBlend: {
54860
+ season: "spring",
54861
+ nextSeason: "spring",
54862
+ t: 0
54863
+ },
54864
+ setSeasonOverride: noop
54589
54865
  });
54590
54866
  function useSolarTheme() {
54591
54867
  return useContext(SolarThemeCtx);
54592
54868
  }
54593
- function SolarThemeProvider({ children, initialDesign = "foundry", isolated = false }) {
54869
+ function SolarThemeProvider({ children, initialDesign = "foundry", isolated = false, seasonOverride: seasonOverrideProp, disableSeasonalBlend = false }) {
54594
54870
  const geo = useCountryCodeFromGeolocation({ immediate: true });
54595
54871
  const scopeId = useRef(isolated ? nextScopeId() : void 0).current;
54596
54872
  const wrapperRef = useRef(null);
@@ -54639,6 +54915,9 @@ function SolarThemeProvider({ children, initialDesign = "foundry", isolated = fa
54639
54915
  }
54640
54916
  }, [isolated]);
54641
54917
  const activeSkin = SKINS[design];
54918
+ const [seasonOverrideState, setSeasonOverrideState] = useState(seasonOverrideProp);
54919
+ const seasonalBlend = useSeason(latitude, seasonOverrideProp ?? seasonOverrideState, simulatedDate);
54920
+ const setSeasonOverride = useCallback((s) => setSeasonOverrideState(s ?? void 0), []);
54642
54921
  const setOverridePhase = useCallback((phase) => {
54643
54922
  setOverridePhaseState(phase);
54644
54923
  if (!isolated) setSessionPhaseOverride(phase);
@@ -54690,7 +54969,10 @@ function SolarThemeProvider({ children, initialDesign = "foundry", isolated = fa
54690
54969
  isolated
54691
54970
  ]);
54692
54971
  useLayoutEffect(() => {
54693
- writeCssVars(activeSkin, activeBlend.phase, activeBlend.t, activeBlend.nextPhase, scopeId);
54972
+ writeCssVars(activeSkin, activeBlend.phase, activeBlend.t, activeBlend.nextPhase, scopeId, {
54973
+ blend: seasonalBlend,
54974
+ disabled: disableSeasonalBlend
54975
+ });
54694
54976
  if (!isolated && !document.documentElement.hasAttribute("data-solar-ready")) requestAnimationFrame(() => {
54695
54977
  document.documentElement.setAttribute("data-solar-ready", "");
54696
54978
  });
@@ -54698,7 +54980,9 @@ function SolarThemeProvider({ children, initialDesign = "foundry", isolated = fa
54698
54980
  activeSkin,
54699
54981
  activeBlend,
54700
54982
  scopeId,
54701
- isolated
54983
+ isolated,
54984
+ seasonalBlend,
54985
+ disableSeasonalBlend
54702
54986
  ]);
54703
54987
  useEffect(() => {
54704
54988
  if (!scopeId) return;
@@ -54727,7 +55011,10 @@ function SolarThemeProvider({ children, initialDesign = "foundry", isolated = fa
54727
55011
  simulatedDate,
54728
55012
  setSimulatedDate,
54729
55013
  customPalettes,
54730
- setCustomPalettes
55014
+ setCustomPalettes,
55015
+ season: seasonalBlend.season,
55016
+ seasonalBlend,
55017
+ setSeasonOverride
54731
55018
  };
54732
55019
  if (isolated) return /* @__PURE__ */ jsx(SolarThemeCtx.Provider, {
54733
55020
  value: theme,
@@ -54747,5 +55034,5 @@ function SolarThemeProvider({ children, initialDesign = "foundry", isolated = fa
54747
55034
  }
54748
55035
 
54749
55036
  //#endregion
54750
- export { getSessionIsLive as a, setSessionTimeMinutes as c, clearSessionTimeMinutes as i, lerpHex as l, useSolarTheme as n, getSessionTimeMinutes as o, SKINS as r, setSessionLive as s, SolarThemeProvider as t };
54751
- //# sourceMappingURL=solar-theme-provider-6-EJ4jGB.js.map
55037
+ export { resolveSeasonalModifier as _, useSeason as a, getSessionTimeMinutes as c, lerpColor as d, lerpHex as f, lerpModifier as g, applySeasonalModifier as h, getSeasonalBlend as i, setSessionLive as l, UNIVERSAL_SEASON_MODIFIERS as m, useSolarTheme as n, clearSessionTimeMinutes as o, IDENTITY_MODIFIER as p, SKINS as r, getSessionIsLive as s, SolarThemeProvider as t, setSessionTimeMinutes as u };
55038
+ //# sourceMappingURL=solar-theme-provider-BcP2n4b7.js.map