@circadian/sol 0.2.9 → 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.
@@ -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, lerpColor as l, useSolarTheme as n, getSessionTimeMinutes as o, SKINS as r, setSessionLive as s, SolarThemeProvider as t, lerpHex as u };
54751
- //# sourceMappingURL=solar-theme-provider-CSustvmw.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