@rnine/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +12 -2
  2. package/dist/_variables.scss +1 -0
  3. package/dist/components/Card/Card.module.js.map +1 -1
  4. package/dist/components/Skeleton/Skeleton.module.js.map +1 -1
  5. package/dist/components.css +1 -1
  6. package/dist/hooks/cardLift.d.ts +24 -0
  7. package/dist/hooks/cardLift.js +82 -0
  8. package/dist/hooks/cardLift.js.map +1 -0
  9. package/dist/hooks/mood.d.ts +5 -29
  10. package/dist/hooks/mood.js +59 -69
  11. package/dist/hooks/mood.js.map +1 -1
  12. package/dist/hooks/moodBoot.d.ts +44 -0
  13. package/dist/hooks/moodBoot.js +24 -0
  14. package/dist/hooks/moodBoot.js.map +1 -0
  15. package/dist/hooks/motionSetting.d.ts +2 -7
  16. package/dist/hooks/motionSetting.js +8 -7
  17. package/dist/hooks/motionSetting.js.map +1 -1
  18. package/dist/index.js +4 -3
  19. package/dist/kit.d.ts +2 -0
  20. package/dist/kit.js +3 -2
  21. package/dist/motion/TiltCard.d.ts +2 -1
  22. package/dist/motion/TiltCard.js +20 -19
  23. package/dist/motion/TiltCard.js.map +1 -1
  24. package/dist/motion/TiltCard.module.js.map +1 -1
  25. package/dist/pages/create.d.ts +31 -0
  26. package/dist/pages/create.js +25 -0
  27. package/dist/pages/create.js.map +1 -0
  28. package/dist/pages/email.d.ts +49 -0
  29. package/dist/pages/email.js +43 -0
  30. package/dist/pages/email.js.map +1 -0
  31. package/dist/pages/icon.d.ts +31 -0
  32. package/dist/pages/icon.js +24 -0
  33. package/dist/pages/icon.js.map +1 -0
  34. package/dist/pages/page.js +6 -0
  35. package/dist/pages/page.js.map +1 -0
  36. package/dist/pages/status.d.ts +60 -0
  37. package/dist/pages/status.js +57 -0
  38. package/dist/pages/status.js.map +1 -0
  39. package/dist/pages/theme.d.ts +77 -0
  40. package/dist/pages/theme.js +45 -0
  41. package/dist/pages/theme.js.map +1 -0
  42. package/dist/pages.d.ts +16 -0
  43. package/dist/pages.js +13 -0
  44. package/dist/pages.js.map +1 -0
  45. package/dist/provider/ThemeProvider.d.ts +3 -0
  46. package/dist/provider/ThemeProvider.js.map +1 -1
  47. package/dist/styles.css +2 -1
  48. package/dist/tokens.css +1 -0
  49. package/dist/tokens.d.ts +11 -1
  50. package/dist/tokens.js +10 -1
  51. package/package.json +6 -2
  52. package/scripts/build-tokens.mjs +18 -3
  53. package/scss/_mixins.scss +56 -4
  54. package/tokens/shape.json +5 -1
@@ -1,139 +1,129 @@
1
- import { MOTION_STORAGE_KEY as e, configureMotion as t } from "./motionSetting.js";
2
- import { useCallback as n, useSyncExternalStore as r } from "react";
1
+ import { MOOD_STORAGE_KEY as e, MOTION_STORAGE_KEY as t, PLAIN_NAMES as n, bootScript as r, checkKey as i } from "./moodBoot.js";
2
+ import { configureMotion as a } from "./motionSetting.js";
3
+ import { useCallback as o, useSyncExternalStore as s } from "react";
3
4
  //#region src/hooks/mood.ts
4
- var i = "mood", a = 6, o = 18, s = "(prefers-color-scheme: dark)", c = {
5
- light: {
6
- id: "light",
7
- label: "Light"
8
- },
9
- dark: {
10
- id: "dark",
11
- label: "Dark"
12
- }
13
- };
5
+ var c = "(prefers-color-scheme: dark)";
14
6
  function l() {
15
7
  try {
16
- return window.matchMedia(s).matches;
8
+ return window.matchMedia(c).matches;
17
9
  } catch {
18
10
  return !1;
19
11
  }
20
12
  }
21
- var u = c, d = "system", f, p = () => f ?? d;
13
+ var u = n, d = "system", f, p = () => f ?? d;
22
14
  function m(e = /* @__PURE__ */ new Date(), t = l(), n = p()) {
23
15
  if (n === "system") return t ? "dark" : "light";
24
16
  let r = e.getHours();
25
17
  return !(r >= 6 && r < 18) && t ? "dark" : "light";
26
18
  }
27
- var h, g = i, _ = y(g);
19
+ var h, g = e, _ = v(g);
28
20
  function v(e) {
29
- if (!/^[\w-]+$/.test(e)) throw Error(`Storage key "${e}": use letters, digits, - and _`);
30
- return e;
21
+ return RegExp(`(^|; )${i(e)}=([a-z-]+)`);
31
22
  }
32
23
  function y(e) {
33
- return RegExp(`(^|; )${v(e)}=([a-z-]+)`);
34
- }
35
- function b(e) {
36
24
  return e === "light" || e === "dark" || e === "auto" ? e : e === u.light.id ? "light" : e === u.dark.id ? "dark" : null;
37
25
  }
38
- function x() {
39
- E.size && (F = j(), N());
26
+ function b() {
27
+ T.size && (P = A(), M());
40
28
  }
41
- function S({ cookieDomain: n, storageKey: r = i, motionStorageKey: a = e, auto: o } = {}) {
42
- t(v(a));
43
- let s = n?.replace(/^\./, "") || void 0;
44
- s === h && r === g && o === f || (_ = y(r), h = s, g = r, f = o, x());
29
+ function x({ cookieDomain: n, storageKey: r = e, motionStorageKey: o = t, auto: s } = {}) {
30
+ a(i(o));
31
+ let c = n?.replace(/^\./, "") || void 0;
32
+ c === h && r === g && s === f || (_ = v(r), h = c, g = r, f = s, b());
45
33
  }
46
- function C(e) {
47
- e.names === u && e.auto === d || (u = e.names, d = e.auto, x());
34
+ function S(e) {
35
+ e.names === u && e.auto === d || (u = e.names, d = e.auto, b());
48
36
  }
49
- function w() {
37
+ function C() {
50
38
  return u;
51
39
  }
52
- function T({ storageKey: t = i, motionStorageKey: n = e, auto: r = d, names: a = u } = {}) {
53
- v(t), v(n);
54
- let o = [a.light.id === "light" ? "" : `m==='${a.light.id}'?'light':`, a.dark.id === "dark" ? "" : `m==='${a.dark.id}'?'dark':`].join(""), s = r === "system" ? "d?'dark':'light'" : "h>=6&&h<18||!d?'light':'dark'";
55
- return `try{var r=document.documentElement,m=(document.cookie.match(/(^|; )${t}=([a-z-]+)/)||[])[2],h=new Date().getHours(),d=!!(window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches);try{m=m||localStorage.getItem('${t}')}catch(e){}` + (o ? `m=${o}m;` : "") + `r.setAttribute('data-scheme',m==='light'||m==='dark'?m:(${s}));if(localStorage.getItem('${n}')==='reduced')r.setAttribute('data-motion','reduced')}catch(e){}`;
40
+ function w(e = {}) {
41
+ return r({
42
+ ...e,
43
+ auto: e.auto ?? d,
44
+ names: e.names ?? u
45
+ });
56
46
  }
57
- var E = /* @__PURE__ */ new Set(), D = null;
58
- function O() {
47
+ var T = /* @__PURE__ */ new Set(), E = null;
48
+ function D() {
59
49
  try {
60
- return b(document.cookie.match(_)?.[2]);
50
+ return y(document.cookie.match(_)?.[2]);
61
51
  } catch {
62
52
  return null;
63
53
  }
64
54
  }
65
- function k(e) {
55
+ function O(e) {
66
56
  try {
67
57
  let t = window.location.hostname, n = h && (t === h || t.endsWith(`.${h}`)) ? `; Domain=${h}` : "", r = window.location.protocol === "https:" ? "; Secure" : "";
68
58
  document.cookie = `${g}=${e}; Path=/; Max-Age=31536000; SameSite=Lax${n}${r}`;
69
59
  } catch {}
70
60
  }
71
- function A() {
72
- let e = O();
61
+ function k() {
62
+ let e = D();
73
63
  if (e) return e;
74
64
  try {
75
- let e = b(localStorage.getItem(g));
65
+ let e = y(localStorage.getItem(g));
76
66
  return e === "light" || e === "dark" ? e : "auto";
77
67
  } catch {
78
- return D ?? "auto";
68
+ return E ?? "auto";
79
69
  }
80
70
  }
81
- function j() {
82
- let e = A();
71
+ function A() {
72
+ let e = k();
83
73
  return e === "auto" ? m() : e;
84
74
  }
75
+ function j() {
76
+ document.documentElement.setAttribute("data-scheme", A());
77
+ }
85
78
  function M() {
86
- document.documentElement.setAttribute("data-scheme", j());
79
+ j(), T.forEach((e) => e());
87
80
  }
88
- function N() {
89
- M(), E.forEach((e) => e());
81
+ var N = null, P = null;
82
+ function F() {
83
+ let e = A();
84
+ e !== P && (P = e, M());
90
85
  }
91
- var P = null, F = null;
92
- function I() {
93
- let e = j();
94
- e !== F && (F = e, N());
86
+ function I(e) {
87
+ (e.key === g || e.key === null) && M();
95
88
  }
96
89
  function L(e) {
97
- (e.key === g || e.key === null) && N();
98
- }
99
- function R(e) {
100
- if (E.add(e), E.size === 1) {
101
- F = j(), P = setInterval(I, 6e4), window.addEventListener("storage", L), document.addEventListener("visibilitychange", I);
90
+ if (T.add(e), T.size === 1) {
91
+ P = A(), N = setInterval(F, 6e4), window.addEventListener("storage", I), document.addEventListener("visibilitychange", F);
102
92
  try {
103
- window.matchMedia(s).addEventListener("change", I);
93
+ window.matchMedia(c).addEventListener("change", F);
104
94
  } catch {}
105
95
  }
106
96
  return () => {
107
- if (E.delete(e), E.size === 0) {
108
- P && clearInterval(P), P = null, window.removeEventListener("storage", L), document.removeEventListener("visibilitychange", I);
97
+ if (T.delete(e), T.size === 0) {
98
+ N && clearInterval(N), N = null, window.removeEventListener("storage", I), document.removeEventListener("visibilitychange", F);
109
99
  try {
110
- window.matchMedia(s).removeEventListener("change", I);
100
+ window.matchMedia(c).removeEventListener("change", F);
111
101
  } catch {}
112
102
  }
113
103
  };
114
104
  }
115
- function z() {
116
- return M(), R(() => {});
105
+ function R() {
106
+ return j(), L(() => {});
117
107
  }
118
- function B(e) {
119
- k(e);
108
+ function z(e) {
109
+ O(e);
120
110
  try {
121
111
  e === "auto" ? localStorage.removeItem(g) : localStorage.setItem(g, e);
122
112
  } catch {}
123
- D = e === "auto" ? null : e, F = j(), N();
113
+ E = e === "auto" ? null : e, P = A(), M();
124
114
  }
125
- function V() {
126
- let e = r(R, A, () => "auto"), t = r(R, j, () => "light"), i = n((e) => B(e), []);
115
+ function B() {
116
+ let e = s(L, k, () => "auto"), t = s(L, A, () => "light"), n = o((e) => z(e), []);
127
117
  return {
128
118
  setting: e,
129
119
  scheme: t,
130
120
  mood: u[t],
131
121
  moods: u,
132
- setMood: i
122
+ setMood: n
133
123
  };
134
124
  }
135
- var H = T();
125
+ var V = w();
136
126
  //#endregion
137
- export { a as DAY_STARTS, H as MOOD_BOOT_SCRIPT, i as MOOD_STORAGE_KEY, o as NIGHT_STARTS, M as applyMood, S as configureMood, T as moodBootScript, w as moodNames, m as schemeAt, B as setMood, C as setMoodDefaults, l as systemDark, V as useMood, z as watchMood };
127
+ export { V as MOOD_BOOT_SCRIPT, j as applyMood, x as configureMood, w as moodBootScript, C as moodNames, m as schemeAt, z as setMood, S as setMoodDefaults, l as systemDark, B as useMood, R as watchMood };
138
128
 
139
129
  //# sourceMappingURL=mood.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mood.js","names":[],"sources":["../../src/hooks/mood.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { MOTION_STORAGE_KEY, configureMotion } from './motionSetting';\n\n/** The two color schemes every library has a mood for. */\nexport type Scheme = 'light' | 'dark';\n/** 'auto' follows the library's Auto rule (see AutoRule). */\nexport type MoodSetting = Scheme | 'auto';\n/**\n * What Auto does. system: follow the system's light/dark setting.\n * night: light by day (DAY_STARTS-NIGHT_STARTS, local time); at night, dark\n * only when the system is set to dark.\n */\nexport type AutoRule = 'system' | 'night';\n/** A library's name for one of its moods (NineOS: Clear and Cipher). */\nexport type MoodName = { id: string; label: string; description?: string };\nexport type MoodNames = Record<Scheme, MoodName>;\n\n/** The default key for the chosen mood: localStorage and the cookie name (see configureMood). */\nexport const MOOD_STORAGE_KEY = 'mood';\n/** The night rule: light from this hour (local time)... */\nexport const DAY_STARTS = 6;\n/** ...until this hour, then dark for a dark system. */\nexport const NIGHT_STARTS = 18;\n\nconst DARK = '(prefers-color-scheme: dark)';\nconst PLAIN_NAMES: MoodNames = { light: { id: 'light', label: 'Light' }, dark: { id: 'dark', label: 'Dark' } };\n\n/** Whether the viewer's system is set to dark. */\nexport function systemDark(): boolean {\n try {\n return window.matchMedia(DARK).matches;\n } catch {\n return false;\n }\n}\n\n// The library's defaults (ThemeProvider sets them), then the app's options\nlet names: MoodNames = PLAIN_NAMES;\nlet libraryAuto: AutoRule = 'system';\nlet appAuto: AutoRule | undefined;\nconst autoRule = () => appAuto ?? libraryAuto;\n\n/**\n * The scheme Auto picks for a moment. With the system rule, the system\n * setting. With the night rule, light by day (06:00-18:00, the viewer's local\n * time) and, at night, dark only when the system is set to dark: a light-mode\n * viewer is never surprised by the dark mood.\n */\nexport function schemeAt(date: Date = new Date(), dark: boolean = systemDark(), auto: AutoRule = autoRule()): Scheme {\n if (auto === 'system') return dark ? 'dark' : 'light';\n const hour = date.getHours();\n const day = hour >= DAY_STARTS && hour < NIGHT_STARTS;\n return !day && dark ? 'dark' : 'light';\n}\n\nexport type MoodOptions = {\n /**\n * Share the chosen mood between a site's hosts: the cookie is set on this\n * domain and every subdomain (e.g. \"example.com\" covers app.example.com and\n * auth.example.com, which can't read each other's localStorage). Only used\n * on that domain; anywhere else (localhost, previews) the cookie stays on\n * the current host. Default: none, the cookie stays on the current host.\n */\n cookieDomain?: string;\n /** The localStorage key and cookie name for the chosen mood. Letters, digits, - and _. Default \"mood\". */\n storageKey?: string;\n /** The localStorage key for the motion setting (Motion or Still). Letters, digits, - and _. Default \"motion\". */\n motionStorageKey?: string;\n /** Override the library's Auto rule (NineOS: night; Plain: system). */\n auto?: AutoRule;\n};\n\nlet cookieDomain: string | undefined;\nlet storageKey = MOOD_STORAGE_KEY;\nlet cookie = cookieMatcher(storageKey);\n\nfunction checkKey(key: string) {\n if (!/^[\\w-]+$/.test(key)) throw new Error(`Storage key \"${key}\": use letters, digits, - and _`);\n return key;\n}\n\nfunction cookieMatcher(key: string) {\n return new RegExp(`(^|; )${checkKey(key)}=([a-z-]+)`);\n}\n\n/** A stored value as a setting: a scheme, auto, or one of the library's mood ids (kept by older versions). */\nfunction asSetting(value: string | null | undefined): MoodSetting | null {\n if (value === 'light' || value === 'dark' || value === 'auto') return value;\n if (value === names.light.id) return 'light';\n if (value === names.dark.id) return 'dark';\n return null;\n}\n\nfunction refresh() {\n if (listeners.size) {\n lastActive = readActive();\n notify();\n }\n}\n\n/**\n * Sets where the chosen mood is kept (and, optionally, the Auto rule). Call\n * it once before the first render (ThemeProvider's mood prop does).\n * Unchanged options do nothing.\n */\nexport function configureMood({ cookieDomain: domain, storageKey: key = MOOD_STORAGE_KEY, motionStorageKey = MOTION_STORAGE_KEY, auto }: MoodOptions = {}) {\n configureMotion(checkKey(motionStorageKey));\n const next = domain?.replace(/^\\./, '') || undefined;\n if (next === cookieDomain && key === storageKey && auto === appAuto) return;\n cookie = cookieMatcher(key);\n cookieDomain = next;\n storageKey = key;\n appAuto = auto;\n refresh();\n}\n\n/** For libraries (ThemeProvider): their mood names and Auto rule. */\nexport function setMoodDefaults(defaults: { names: MoodNames; auto: AutoRule }) {\n if (defaults.names === names && defaults.auto === libraryAuto) return;\n names = defaults.names;\n libraryAuto = defaults.auto;\n refresh();\n}\n\n/** The library's mood names, by scheme. */\nexport function moodNames(): MoodNames {\n return names;\n}\n\nexport type BootScriptOptions = Pick<MoodOptions, 'storageKey' | 'motionStorageKey' | 'auto'> & {\n /** Mood ids an older version may have saved, read as their scheme. Default: the library's. */\n names?: MoodNames;\n};\n\n/**\n * The script to paste into each app's index.html <head> as an inline <script>,\n * so the first paint already has the right mood (the shared cookie, else the\n * saved choice, else the Auto rule) and the saved motion setting. Pass the\n * same storageKey, motionStorageKey and auto as configureMood. Each library exports its own,\n * with its Auto rule as the default.\n */\nexport function moodBootScript({ storageKey: key = MOOD_STORAGE_KEY, motionStorageKey: motionKey = MOTION_STORAGE_KEY, auto = libraryAuto, names: ids = names }: BootScriptOptions = {}): string {\n checkKey(key);\n checkKey(motionKey);\n const legacy = [ids.light.id !== 'light' ? `m==='${ids.light.id}'?'light':` : '', ids.dark.id !== 'dark' ? `m==='${ids.dark.id}'?'dark':` : ''].join('');\n const rule = auto === 'system' ? \"d?'dark':'light'\" : \"h>=6&&h<18||!d?'light':'dark'\";\n return (\n `try{var r=document.documentElement,m=(document.cookie.match(/(^|; )${key}=([a-z-]+)/)||[])[2],h=new Date().getHours(),` +\n \"d=!!(window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches);\" +\n `try{m=m||localStorage.getItem('${key}')}catch(e){}` +\n (legacy ? `m=${legacy}m;` : '') +\n `r.setAttribute('data-scheme',m==='light'||m==='dark'?m:(${rule}));` +\n `if(localStorage.getItem('${motionKey}')==='reduced')r.setAttribute('data-motion','reduced')}catch(e){}`\n );\n}\n\nconst listeners = new Set<() => void>();\n// Used when storage is blocked, so a choice still lasts for this visit\nlet unsaved: Scheme | null = null;\n\n/** The mood shared across the site's hosts (the cookie), if one was set. */\nfunction readShared(): MoodSetting | null {\n try {\n return asSetting(document.cookie.match(cookie)?.[2]);\n } catch {\n return null;\n }\n}\n\nfunction writeShared(next: MoodSetting) {\n try {\n const host = window.location.hostname;\n const shared = cookieDomain && (host === cookieDomain || host.endsWith(`.${cookieDomain}`));\n const domain = shared ? `; Domain=${cookieDomain}` : '';\n const secure = window.location.protocol === 'https:' ? '; Secure' : '';\n document.cookie = `${storageKey}=${next}; Path=/; Max-Age=31536000; SameSite=Lax${domain}${secure}`;\n } catch {\n // Cookies can be blocked; localStorage still remembers it on this site\n }\n}\n\nfunction readSetting(): MoodSetting {\n const shared = readShared();\n if (shared) return shared;\n try {\n const saved = asSetting(localStorage.getItem(storageKey));\n return saved === 'light' || saved === 'dark' ? saved : 'auto';\n } catch {\n return unsaved ?? 'auto';\n }\n}\n\nfunction readActive(): Scheme {\n const setting = readSetting();\n return setting === 'auto' ? schemeAt() : setting;\n}\n\n/** Puts the current scheme on <html data-scheme>. Safe to call any time. */\nexport function applyMood() {\n document.documentElement.setAttribute('data-scheme', readActive());\n}\n\nfunction notify() {\n applyMood();\n listeners.forEach((l) => l());\n}\n\n// While anything listens: re-check each minute (dusk and dawn flip the night\n// rule), when the system switches light/dark, when the tab comes back (a\n// choice made on another host sharing the cookie), and follow choices made in\n// other tabs.\nlet clock: ReturnType<typeof setInterval> | null = null;\nlet lastActive: Scheme | null = null;\n\nfunction tick() {\n const active = readActive();\n if (active !== lastActive) {\n lastActive = active;\n notify();\n }\n}\n\nfunction onStorage(e: StorageEvent) {\n if (e.key === storageKey || e.key === null) notify();\n}\n\nfunction subscribe(cb: () => void) {\n listeners.add(cb);\n if (listeners.size === 1) {\n lastActive = readActive();\n clock = setInterval(tick, 60_000);\n window.addEventListener('storage', onStorage);\n document.addEventListener('visibilitychange', tick);\n try {\n window.matchMedia(DARK).addEventListener('change', tick);\n } catch {\n // No matchMedia: the clock alone\n }\n }\n return () => {\n listeners.delete(cb);\n if (listeners.size === 0) {\n if (clock) clearInterval(clock);\n clock = null;\n window.removeEventListener('storage', onStorage);\n document.removeEventListener('visibilitychange', tick);\n try {\n window.matchMedia(DARK).removeEventListener('change', tick);\n } catch {\n // Nothing was added\n }\n }\n };\n}\n\n/**\n * Keeps <html data-scheme> on the right scheme while mounted (ThemeProvider\n * calls it, so Auto follows the system and the clock even with no toggle on\n * screen). Returns the cleanup.\n */\nexport function watchMood(): () => void {\n applyMood();\n return subscribe(() => {});\n}\n\n/**\n * Sets and remembers the mood, on this site and on every host that shares\n * the cookie (configureMood cookieDomain). 'auto' forgets the choice and\n * follows the Auto rule again.\n */\nexport function setMood(next: MoodSetting) {\n writeShared(next);\n try {\n if (next === 'auto') localStorage.removeItem(storageKey);\n else localStorage.setItem(storageKey, next);\n } catch {\n // Storage can be blocked; the mood still applies for this visit\n }\n unsaved = next === 'auto' ? null : next;\n lastActive = readActive();\n notify();\n}\n\n/**\n * The chosen setting, the scheme showing, the library's mood for it (its id\n * and label: NineOS's dark mood is Cipher), every mood's name, and a setter\n * that remembers the choice.\n */\nexport function useMood() {\n const setting = useSyncExternalStore(subscribe, readSetting, () => 'auto' as MoodSetting);\n const scheme = useSyncExternalStore(subscribe, readActive, () => 'light' as Scheme);\n const set = useCallback((next: MoodSetting) => setMood(next), []);\n return { setting, scheme, mood: names[scheme], moods: names, setMood: set };\n}\n\n/** moodBootScript() with the defaults (Plain's Auto rule, the default key). Libraries export their own. */\nexport const MOOD_BOOT_SCRIPT = moodBootScript();\n"],"mappings":"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"}
1
+ {"version":3,"file":"mood.js","names":[],"sources":["../../src/hooks/mood.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { MOTION_STORAGE_KEY, configureMotion } from './motionSetting';\n\nimport { DAY_STARTS, MOOD_STORAGE_KEY, NIGHT_STARTS, PLAIN_NAMES, bootScript, checkKey } from './moodBoot';\nimport type { AutoRule, BootScriptOptions, MoodNames, MoodSetting, Scheme } from './moodBoot';\n\nexport { DAY_STARTS, MOOD_STORAGE_KEY, NIGHT_STARTS } from './moodBoot';\nexport type { AutoRule, BootScriptOptions, MoodName, MoodNames, MoodSetting, Scheme } from './moodBoot';\n\nconst DARK = '(prefers-color-scheme: dark)';\n\n/** Whether the viewer's system is set to dark. */\nexport function systemDark(): boolean {\n try {\n return window.matchMedia(DARK).matches;\n } catch {\n return false;\n }\n}\n\n// The library's defaults (ThemeProvider sets them), then the app's options\nlet names: MoodNames = PLAIN_NAMES;\nlet libraryAuto: AutoRule = 'system';\nlet appAuto: AutoRule | undefined;\nconst autoRule = () => appAuto ?? libraryAuto;\n\n/**\n * The scheme Auto picks for a moment. With the system rule, the system\n * setting. With the night rule, light by day (06:00-18:00, the viewer's local\n * time) and, at night, dark only when the system is set to dark: a light-mode\n * viewer is never surprised by the dark mood.\n */\nexport function schemeAt(date: Date = new Date(), dark: boolean = systemDark(), auto: AutoRule = autoRule()): Scheme {\n if (auto === 'system') return dark ? 'dark' : 'light';\n const hour = date.getHours();\n const day = hour >= DAY_STARTS && hour < NIGHT_STARTS;\n return !day && dark ? 'dark' : 'light';\n}\n\nexport type MoodOptions = {\n /**\n * Share the chosen mood between a site's hosts: the cookie is set on this\n * domain and every subdomain (e.g. \"example.com\" covers app.example.com and\n * auth.example.com, which can't read each other's localStorage). Only used\n * on that domain; anywhere else (localhost, previews) the cookie stays on\n * the current host. Default: none, the cookie stays on the current host.\n */\n cookieDomain?: string;\n /** The localStorage key and cookie name for the chosen mood. Letters, digits, - and _. Default \"mood\". */\n storageKey?: string;\n /** The localStorage key for the motion setting (Motion or Still). Letters, digits, - and _. Default \"motion\". */\n motionStorageKey?: string;\n /** Override the library's Auto rule (NineOS: night; Plain: system). */\n auto?: AutoRule;\n};\n\nlet cookieDomain: string | undefined;\nlet storageKey = MOOD_STORAGE_KEY;\nlet cookie = cookieMatcher(storageKey);\n\nfunction cookieMatcher(key: string) {\n return new RegExp(`(^|; )${checkKey(key)}=([a-z-]+)`);\n}\n\n/** A stored value as a setting: a scheme, auto, or one of the library's mood ids (kept by older versions). */\nfunction asSetting(value: string | null | undefined): MoodSetting | null {\n if (value === 'light' || value === 'dark' || value === 'auto') return value;\n if (value === names.light.id) return 'light';\n if (value === names.dark.id) return 'dark';\n return null;\n}\n\nfunction refresh() {\n if (listeners.size) {\n lastActive = readActive();\n notify();\n }\n}\n\n/**\n * Sets where the chosen mood is kept (and, optionally, the Auto rule). Call\n * it once before the first render (ThemeProvider's mood prop does).\n * Unchanged options do nothing.\n */\nexport function configureMood({ cookieDomain: domain, storageKey: key = MOOD_STORAGE_KEY, motionStorageKey = MOTION_STORAGE_KEY, auto }: MoodOptions = {}) {\n configureMotion(checkKey(motionStorageKey));\n const next = domain?.replace(/^\\./, '') || undefined;\n if (next === cookieDomain && key === storageKey && auto === appAuto) return;\n cookie = cookieMatcher(key);\n cookieDomain = next;\n storageKey = key;\n appAuto = auto;\n refresh();\n}\n\n/** For libraries (ThemeProvider): their mood names and Auto rule. */\nexport function setMoodDefaults(defaults: { names: MoodNames; auto: AutoRule }) {\n if (defaults.names === names && defaults.auto === libraryAuto) return;\n names = defaults.names;\n libraryAuto = defaults.auto;\n refresh();\n}\n\n/** The library's mood names, by scheme. */\nexport function moodNames(): MoodNames {\n return names;\n}\n\n/**\n * The script to paste into each app's index.html <head> as an inline <script>,\n * so the first paint already has the right mood (the shared cookie, else the\n * saved choice, else the Auto rule) and the saved motion setting. Pass the\n * same storageKey, motionStorageKey and auto as configureMood. Each library exports its own,\n * with its Auto rule as the default.\n */\nexport function moodBootScript(options: BootScriptOptions = {}): string {\n return bootScript({ ...options, auto: options.auto ?? libraryAuto, names: options.names ?? names });\n}\n\nconst listeners = new Set<() => void>();\n// Used when storage is blocked, so a choice still lasts for this visit\nlet unsaved: Scheme | null = null;\n\n/** The mood shared across the site's hosts (the cookie), if one was set. */\nfunction readShared(): MoodSetting | null {\n try {\n return asSetting(document.cookie.match(cookie)?.[2]);\n } catch {\n return null;\n }\n}\n\nfunction writeShared(next: MoodSetting) {\n try {\n const host = window.location.hostname;\n const shared = cookieDomain && (host === cookieDomain || host.endsWith(`.${cookieDomain}`));\n const domain = shared ? `; Domain=${cookieDomain}` : '';\n const secure = window.location.protocol === 'https:' ? '; Secure' : '';\n document.cookie = `${storageKey}=${next}; Path=/; Max-Age=31536000; SameSite=Lax${domain}${secure}`;\n } catch {\n // Cookies can be blocked; localStorage still remembers it on this site\n }\n}\n\nfunction readSetting(): MoodSetting {\n const shared = readShared();\n if (shared) return shared;\n try {\n const saved = asSetting(localStorage.getItem(storageKey));\n return saved === 'light' || saved === 'dark' ? saved : 'auto';\n } catch {\n return unsaved ?? 'auto';\n }\n}\n\nfunction readActive(): Scheme {\n const setting = readSetting();\n return setting === 'auto' ? schemeAt() : setting;\n}\n\n/** Puts the current scheme on <html data-scheme>. Safe to call any time. */\nexport function applyMood() {\n document.documentElement.setAttribute('data-scheme', readActive());\n}\n\nfunction notify() {\n applyMood();\n listeners.forEach((l) => l());\n}\n\n// While anything listens: re-check each minute (dusk and dawn flip the night\n// rule), when the system switches light/dark, when the tab comes back (a\n// choice made on another host sharing the cookie), and follow choices made in\n// other tabs.\nlet clock: ReturnType<typeof setInterval> | null = null;\nlet lastActive: Scheme | null = null;\n\nfunction tick() {\n const active = readActive();\n if (active !== lastActive) {\n lastActive = active;\n notify();\n }\n}\n\nfunction onStorage(e: StorageEvent) {\n if (e.key === storageKey || e.key === null) notify();\n}\n\nfunction subscribe(cb: () => void) {\n listeners.add(cb);\n if (listeners.size === 1) {\n lastActive = readActive();\n clock = setInterval(tick, 60_000);\n window.addEventListener('storage', onStorage);\n document.addEventListener('visibilitychange', tick);\n try {\n window.matchMedia(DARK).addEventListener('change', tick);\n } catch {\n // No matchMedia: the clock alone\n }\n }\n return () => {\n listeners.delete(cb);\n if (listeners.size === 0) {\n if (clock) clearInterval(clock);\n clock = null;\n window.removeEventListener('storage', onStorage);\n document.removeEventListener('visibilitychange', tick);\n try {\n window.matchMedia(DARK).removeEventListener('change', tick);\n } catch {\n // Nothing was added\n }\n }\n };\n}\n\n/**\n * Keeps <html data-scheme> on the right scheme while mounted (ThemeProvider\n * calls it, so Auto follows the system and the clock even with no toggle on\n * screen). Returns the cleanup.\n */\nexport function watchMood(): () => void {\n applyMood();\n return subscribe(() => {});\n}\n\n/**\n * Sets and remembers the mood, on this site and on every host that shares\n * the cookie (configureMood cookieDomain). 'auto' forgets the choice and\n * follows the Auto rule again.\n */\nexport function setMood(next: MoodSetting) {\n writeShared(next);\n try {\n if (next === 'auto') localStorage.removeItem(storageKey);\n else localStorage.setItem(storageKey, next);\n } catch {\n // Storage can be blocked; the mood still applies for this visit\n }\n unsaved = next === 'auto' ? null : next;\n lastActive = readActive();\n notify();\n}\n\n/**\n * The chosen setting, the scheme showing, the library's mood for it (its id\n * and label: NineOS's dark mood is Cipher), every mood's name, and a setter\n * that remembers the choice.\n */\nexport function useMood() {\n const setting = useSyncExternalStore(subscribe, readSetting, () => 'auto' as MoodSetting);\n const scheme = useSyncExternalStore(subscribe, readActive, () => 'light' as Scheme);\n const set = useCallback((next: MoodSetting) => setMood(next), []);\n return { setting, scheme, mood: names[scheme], moods: names, setMood: set };\n}\n\n/** moodBootScript() with the defaults (Plain's Auto rule, the default key). Libraries export their own. */\nexport const MOOD_BOOT_SCRIPT = moodBootScript();\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /** The two color schemes every library has a mood for. */
2
+ export type Scheme = 'light' | 'dark';
3
+ /** 'auto' follows the library's Auto rule (see AutoRule). */
4
+ export type MoodSetting = Scheme | 'auto';
5
+ /**
6
+ * What Auto does. system: follow the system's light/dark setting.
7
+ * night: light by day (DAY_STARTS-NIGHT_STARTS, local time); at night, dark
8
+ * only when the system is set to dark.
9
+ */
10
+ export type AutoRule = 'system' | 'night';
11
+ /** A library's name for one of its moods (NineOS: Clear and Cipher). */
12
+ export type MoodName = {
13
+ id: string;
14
+ label: string;
15
+ description?: string;
16
+ };
17
+ export type MoodNames = Record<Scheme, MoodName>;
18
+ /** The default key for the chosen mood: localStorage and the cookie name (see configureMood). */
19
+ export declare const MOOD_STORAGE_KEY = "mood";
20
+ /** The default localStorage key for the motion setting (Motion or Still). */
21
+ export declare const MOTION_STORAGE_KEY = "motion";
22
+ /** The night rule: light from this hour (local time)... */
23
+ export declare const DAY_STARTS = 6;
24
+ /** ...until this hour, then dark for a dark system. */
25
+ export declare const NIGHT_STARTS = 18;
26
+ export declare const PLAIN_NAMES: MoodNames;
27
+ /** A storage key as given, or an error: letters, digits, - and _ only (it goes into a cookie and a script). */
28
+ export declare function checkKey(key: string): string;
29
+ export type BootScriptOptions = {
30
+ /** The localStorage key and cookie name for the chosen mood. Default "mood". */
31
+ storageKey?: string;
32
+ /** The localStorage key for the motion setting. Default "motion". */
33
+ motionStorageKey?: string;
34
+ /** The Auto rule. Default: the library's. */
35
+ auto?: AutoRule;
36
+ /** Mood ids an older version may have saved, read as their scheme. Default: the library's. */
37
+ names?: MoodNames;
38
+ };
39
+ /**
40
+ * The boot script itself, from explicit options (Plain's defaults). Sets
41
+ * <html data-scheme> from the shared cookie, else the saved choice, else the
42
+ * Auto rule, and data-motion="reduced" for a saved Still setting.
43
+ */
44
+ export declare function bootScript({ storageKey: key, motionStorageKey: motionKey, auto, names: ids }?: BootScriptOptions): string;
@@ -0,0 +1,24 @@
1
+ //#region src/hooks/moodBoot.ts
2
+ var e = "mood", t = "motion", n = 6, r = 18, i = {
3
+ light: {
4
+ id: "light",
5
+ label: "Light"
6
+ },
7
+ dark: {
8
+ id: "dark",
9
+ label: "Dark"
10
+ }
11
+ };
12
+ function a(e) {
13
+ if (!/^[\w-]+$/.test(e)) throw Error(`Storage key "${e}": use letters, digits, - and _`);
14
+ return e;
15
+ }
16
+ function o({ storageKey: n = e, motionStorageKey: r = t, auto: o = "system", names: s = i } = {}) {
17
+ a(n), a(r);
18
+ let c = [s.light.id === "light" ? "" : `m==='${s.light.id}'?'light':`, s.dark.id === "dark" ? "" : `m==='${s.dark.id}'?'dark':`].join(""), l = o === "system" ? "d?'dark':'light'" : "h>=6&&h<18||!d?'light':'dark'";
19
+ return `try{var r=document.documentElement,m=(document.cookie.match(/(^|; )${n}=([a-z-]+)/)||[])[2],h=new Date().getHours(),d=!!(window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches);try{m=m||localStorage.getItem('${n}')}catch(e){}` + (c ? `m=${c}m;` : "") + `r.setAttribute('data-scheme',m==='light'||m==='dark'?m:(${l}));if(localStorage.getItem('${r}')==='reduced')r.setAttribute('data-motion','reduced')}catch(e){}`;
20
+ }
21
+ //#endregion
22
+ export { n as DAY_STARTS, e as MOOD_STORAGE_KEY, t as MOTION_STORAGE_KEY, r as NIGHT_STARTS, i as PLAIN_NAMES, o as bootScript, a as checkKey };
23
+
24
+ //# sourceMappingURL=moodBoot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moodBoot.js","names":[],"sources":["../../src/hooks/moodBoot.ts"],"sourcesContent":["// The mood's types, keys and boot script, without React: the hooks (mood.ts)\n// build on it, and so do static pages (@rnine/core/pages), which run in\n// Workers and Node.\n\n/** The two color schemes every library has a mood for. */\nexport type Scheme = 'light' | 'dark';\n/** 'auto' follows the library's Auto rule (see AutoRule). */\nexport type MoodSetting = Scheme | 'auto';\n/**\n * What Auto does. system: follow the system's light/dark setting.\n * night: light by day (DAY_STARTS-NIGHT_STARTS, local time); at night, dark\n * only when the system is set to dark.\n */\nexport type AutoRule = 'system' | 'night';\n/** A library's name for one of its moods (NineOS: Clear and Cipher). */\nexport type MoodName = { id: string; label: string; description?: string };\nexport type MoodNames = Record<Scheme, MoodName>;\n\n/** The default key for the chosen mood: localStorage and the cookie name (see configureMood). */\nexport const MOOD_STORAGE_KEY = 'mood';\n/** The default localStorage key for the motion setting (Motion or Still). */\nexport const MOTION_STORAGE_KEY = 'motion';\n/** The night rule: light from this hour (local time)... */\nexport const DAY_STARTS = 6;\n/** ...until this hour, then dark for a dark system. */\nexport const NIGHT_STARTS = 18;\n\nexport const PLAIN_NAMES: MoodNames = { light: { id: 'light', label: 'Light' }, dark: { id: 'dark', label: 'Dark' } };\n\n/** A storage key as given, or an error: letters, digits, - and _ only (it goes into a cookie and a script). */\nexport function checkKey(key: string) {\n if (!/^[\\w-]+$/.test(key)) throw new Error(`Storage key \"${key}\": use letters, digits, - and _`);\n return key;\n}\n\nexport type BootScriptOptions = {\n /** The localStorage key and cookie name for the chosen mood. Default \"mood\". */\n storageKey?: string;\n /** The localStorage key for the motion setting. Default \"motion\". */\n motionStorageKey?: string;\n /** The Auto rule. Default: the library's. */\n auto?: AutoRule;\n /** Mood ids an older version may have saved, read as their scheme. Default: the library's. */\n names?: MoodNames;\n};\n\n/**\n * The boot script itself, from explicit options (Plain's defaults). Sets\n * <html data-scheme> from the shared cookie, else the saved choice, else the\n * Auto rule, and data-motion=\"reduced\" for a saved Still setting.\n */\nexport function bootScript({ storageKey: key = MOOD_STORAGE_KEY, motionStorageKey: motionKey = MOTION_STORAGE_KEY, auto = 'system', names: ids = PLAIN_NAMES }: BootScriptOptions = {}): string {\n checkKey(key);\n checkKey(motionKey);\n const legacy = [ids.light.id !== 'light' ? `m==='${ids.light.id}'?'light':` : '', ids.dark.id !== 'dark' ? `m==='${ids.dark.id}'?'dark':` : ''].join('');\n const rule = auto === 'system' ? \"d?'dark':'light'\" : `h>=${DAY_STARTS}&&h<${NIGHT_STARTS}||!d?'light':'dark'`;\n return (\n `try{var r=document.documentElement,m=(document.cookie.match(/(^|; )${key}=([a-z-]+)/)||[])[2],h=new Date().getHours(),` +\n \"d=!!(window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches);\" +\n `try{m=m||localStorage.getItem('${key}')}catch(e){}` +\n (legacy ? `m=${legacy}m;` : '') +\n `r.setAttribute('data-scheme',m==='light'||m==='dark'?m:(${rule}));` +\n `if(localStorage.getItem('${motionKey}')==='reduced')r.setAttribute('data-motion','reduced')}catch(e){}`\n );\n}\n"],"mappings":";AAmBA,IAAa,IAAmB,QAEnB,IAAqB,UAErB,IAAa,GAEb,IAAe,IAEf,IAAyB;CAAE,OAAO;EAAE,IAAI;EAAS,OAAO;CAAQ;CAAG,MAAM;EAAE,IAAI;EAAQ,OAAO;CAAO;AAAE;AAGpH,SAAgB,EAAS,GAAa;CACpC,IAAI,CAAC,WAAW,KAAK,CAAG,GAAG,MAAU,MAAM,gBAAgB,EAAI,gCAAgC;CAC/F,OAAO;AACT;AAkBA,SAAgB,EAAW,EAAE,YAAY,IAAM,GAAkB,kBAAkB,IAAY,GAAoB,UAAO,UAAU,OAAO,IAAM,MAAmC,CAAC,GAAW;CAE9L,AADA,EAAS,CAAG,GACZ,EAAS,CAAS;CAClB,IAAM,IAAS,CAAC,EAAI,MAAM,OAAO,UAA6C,KAAnC,QAAQ,EAAI,MAAM,GAAG,aAAkB,EAAI,KAAK,OAAO,SAA0C,KAAjC,QAAQ,EAAI,KAAK,GAAG,UAAe,EAAE,KAAK,EAAE,GACjJ,IAAO,MAAS,WAAW,qBAAqB;CACtD,OACE,sEAAsE,EAAI,0JAExC,EAAI,kBACrC,IAAS,KAAK,EAAO,MAAM,MAC5B,2DAA2D,EAAK,8BACpC,EAAU;AAE1C"}
@@ -1,10 +1,5 @@
1
- /**
2
- * The viewer's own motion setting ("Motion" or "Still"), for people who want
3
- * less movement but never set their system to reduce motion. Remembered in
4
- * their browser like the mood; Still puts data-motion="reduced" on <html>, which
5
- * stops CSS animation (base.css) and every effect that asks useReducedMotion().
6
- */
7
- export declare const MOTION_STORAGE_KEY = "motion";
1
+ import { MOTION_STORAGE_KEY } from './moodBoot';
2
+ export { MOTION_STORAGE_KEY };
8
3
  /**
9
4
  * Sets the localStorage key for the motion setting (configureMood's
10
5
  * motionStorageKey, which ThemeProvider's mood prop passes). Unchanged keys do nothing.
@@ -1,6 +1,7 @@
1
- import { useCallback as e, useSyncExternalStore as t } from "react";
1
+ import { MOTION_STORAGE_KEY as e } from "./moodBoot.js";
2
+ import { useCallback as t, useSyncExternalStore as n } from "react";
2
3
  //#region src/hooks/motionSetting.ts
3
- var n = "motion", r = n, i = /* @__PURE__ */ new Set(), a = null;
4
+ var r = e, i = /* @__PURE__ */ new Set(), a = null;
4
5
  function o() {
5
6
  try {
6
7
  return localStorage.getItem(r) === "reduced";
@@ -8,8 +9,8 @@ function o() {
8
9
  return a ?? !1;
9
10
  }
10
11
  }
11
- function s(e = n) {
12
- e !== r && (r = e, a = null, c(), i.forEach((e) => e()));
12
+ function s(t = e) {
13
+ t !== r && (r = t, a = null, c(), i.forEach((e) => e()));
13
14
  }
14
15
  function c() {
15
16
  o() ? document.documentElement.setAttribute("data-motion", "reduced") : document.documentElement.removeAttribute("data-motion");
@@ -31,11 +32,11 @@ function u(e) {
31
32
  }
32
33
  function d() {
33
34
  return {
34
- still: t(l, o, () => !1),
35
- setStill: e((e) => u(e), [])
35
+ still: n(l, o, () => !1),
36
+ setStill: t((e) => u(e), [])
36
37
  };
37
38
  }
38
39
  //#endregion
39
- export { n as MOTION_STORAGE_KEY, c as applyMotion, s as configureMotion, u as setStill, d as useMotionSetting };
40
+ export { c as applyMotion, s as configureMotion, u as setStill, d as useMotionSetting };
40
41
 
41
42
  //# sourceMappingURL=motionSetting.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"motionSetting.js","names":[],"sources":["../../src/hooks/motionSetting.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\n\n/**\n * The viewer's own motion setting (\"Motion\" or \"Still\"), for people who want\n * less movement but never set their system to reduce motion. Remembered in\n * their browser like the mood; Still puts data-motion=\"reduced\" on <html>, which\n * stops CSS animation (base.css) and every effect that asks useReducedMotion().\n */\nexport const MOTION_STORAGE_KEY = 'motion';\n\nlet storageKey = MOTION_STORAGE_KEY;\nconst listeners = new Set<() => void>();\nlet unsaved: boolean | null = null;\n\nfunction readStill(): boolean {\n try {\n return localStorage.getItem(storageKey) === 'reduced';\n } catch {\n return unsaved ?? false;\n }\n}\n\n/**\n * Sets the localStorage key for the motion setting (configureMood's\n * motionStorageKey, which ThemeProvider's mood prop passes). Unchanged keys do nothing.\n */\nexport function configureMotion(key: string = MOTION_STORAGE_KEY) {\n if (key === storageKey) return;\n storageKey = key;\n unsaved = null;\n applyMotion();\n listeners.forEach((l) => l());\n}\n\n/** Puts the saved setting on <html>. Safe to call any time. */\nexport function applyMotion() {\n if (readStill()) document.documentElement.setAttribute('data-motion', 'reduced');\n else document.documentElement.removeAttribute('data-motion');\n}\n\nfunction subscribe(cb: () => void) {\n listeners.add(cb);\n const onStorage = (e: StorageEvent) => {\n if (e.key === storageKey || e.key === null) {\n applyMotion();\n cb();\n }\n };\n window.addEventListener('storage', onStorage);\n return () => {\n listeners.delete(cb);\n window.removeEventListener('storage', onStorage);\n };\n}\n\n/** Turns motion off (still) or back on, and remembers it. */\nexport function setStill(still: boolean) {\n try {\n if (still) localStorage.setItem(storageKey, 'reduced');\n else localStorage.removeItem(storageKey);\n } catch {\n // Storage can be blocked; the setting still applies for this visit\n }\n unsaved = still;\n applyMotion();\n listeners.forEach((l) => l());\n}\n\n/** Whether the viewer chose Still, and a setter that remembers it. */\nexport function useMotionSetting() {\n const still = useSyncExternalStore(subscribe, readStill, () => false);\n const set = useCallback((next: boolean) => setStill(next), []);\n return { still, setStill: set };\n}\n"],"mappings":";;AAQA,IAAa,IAAqB,UAE9B,IAAa,GACX,oBAAY,IAAI,IAAgB,GAClC,IAA0B;AAE9B,SAAS,IAAqB;CAC5B,IAAI;EACF,OAAO,aAAa,QAAQ,CAAU,MAAM;CAC9C,QAAQ;EACN,OAAO,KAAW;CACpB;AACF;AAMA,SAAgB,EAAgB,IAAc,GAAoB;CAC5D,MAAQ,MACZ,IAAa,GACb,IAAU,MACV,EAAY,GACZ,EAAU,SAAS,MAAM,EAAE,CAAC;AAC9B;AAGA,SAAgB,IAAc;CAC5B,AAAI,EAAU,IAAG,SAAS,gBAAgB,aAAa,eAAe,SAAS,IAC1E,SAAS,gBAAgB,gBAAgB,aAAa;AAC7D;AAEA,SAAS,EAAU,GAAgB;CACjC,EAAU,IAAI,CAAE;CAChB,IAAM,KAAa,MAAoB;EACrC,CAAI,EAAE,QAAQ,KAAc,EAAE,QAAQ,UACpC,EAAY,GACZ,EAAG;CAEP;CAEA,OADA,OAAO,iBAAiB,WAAW,CAAS,SAC/B;EAEX,AADA,EAAU,OAAO,CAAE,GACnB,OAAO,oBAAoB,WAAW,CAAS;CACjD;AACF;AAGA,SAAgB,EAAS,GAAgB;CACvC,IAAI;EACF,AAAI,IAAO,aAAa,QAAQ,GAAY,SAAS,IAChD,aAAa,WAAW,CAAU;CACzC,QAAQ,CAER;CAGA,AAFA,IAAU,GACV,EAAY,GACZ,EAAU,SAAS,MAAM,EAAE,CAAC;AAC9B;AAGA,SAAgB,IAAmB;CAGjC,OAAO;EAAE,OAFK,EAAqB,GAAW,SAAiB,EAEtD;EAAO,UADJ,GAAa,MAAkB,EAAS,CAAI,GAAG,CAAC,CAClC;CAAI;AAChC"}
1
+ {"version":3,"file":"motionSetting.js","names":[],"sources":["../../src/hooks/motionSetting.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { MOTION_STORAGE_KEY } from './moodBoot';\n\nexport { MOTION_STORAGE_KEY };\n\n/**\n * The viewer's own motion setting (\"Motion\" or \"Still\"), for people who want\n * less movement but never set their system to reduce motion. Remembered in\n * their browser like the mood; Still puts data-motion=\"reduced\" on <html>, which\n * stops CSS animation (base.css) and every effect that asks useReducedMotion().\n * Its default key, MOTION_STORAGE_KEY, lives in moodBoot.ts (no React).\n */\n\nlet storageKey = MOTION_STORAGE_KEY;\nconst listeners = new Set<() => void>();\nlet unsaved: boolean | null = null;\n\nfunction readStill(): boolean {\n try {\n return localStorage.getItem(storageKey) === 'reduced';\n } catch {\n return unsaved ?? false;\n }\n}\n\n/**\n * Sets the localStorage key for the motion setting (configureMood's\n * motionStorageKey, which ThemeProvider's mood prop passes). Unchanged keys do nothing.\n */\nexport function configureMotion(key: string = MOTION_STORAGE_KEY) {\n if (key === storageKey) return;\n storageKey = key;\n unsaved = null;\n applyMotion();\n listeners.forEach((l) => l());\n}\n\n/** Puts the saved setting on <html>. Safe to call any time. */\nexport function applyMotion() {\n if (readStill()) document.documentElement.setAttribute('data-motion', 'reduced');\n else document.documentElement.removeAttribute('data-motion');\n}\n\nfunction subscribe(cb: () => void) {\n listeners.add(cb);\n const onStorage = (e: StorageEvent) => {\n if (e.key === storageKey || e.key === null) {\n applyMotion();\n cb();\n }\n };\n window.addEventListener('storage', onStorage);\n return () => {\n listeners.delete(cb);\n window.removeEventListener('storage', onStorage);\n };\n}\n\n/** Turns motion off (still) or back on, and remembers it. */\nexport function setStill(still: boolean) {\n try {\n if (still) localStorage.setItem(storageKey, 'reduced');\n else localStorage.removeItem(storageKey);\n } catch {\n // Storage can be blocked; the setting still applies for this visit\n }\n unsaved = still;\n applyMotion();\n listeners.forEach((l) => l());\n}\n\n/** Whether the viewer chose Still, and a setter that remembers it. */\nexport function useMotionSetting() {\n const still = useSyncExternalStore(subscribe, readStill, () => false);\n const set = useCallback((next: boolean) => setStill(next), []);\n return { still, setStill: set };\n}\n"],"mappings":";;;AAaA,IAAI,IAAa,GACX,oBAAY,IAAI,IAAgB,GAClC,IAA0B;AAE9B,SAAS,IAAqB;CAC5B,IAAI;EACF,OAAO,aAAa,QAAQ,CAAU,MAAM;CAC9C,QAAQ;EACN,OAAO,KAAW;CACpB;AACF;AAMA,SAAgB,EAAgB,IAAc,GAAoB;CAC5D,MAAQ,MACZ,IAAa,GACb,IAAU,MACV,EAAY,GACZ,EAAU,SAAS,MAAM,EAAE,CAAC;AAC9B;AAGA,SAAgB,IAAc;CAC5B,AAAI,EAAU,IAAG,SAAS,gBAAgB,aAAa,eAAe,SAAS,IAC1E,SAAS,gBAAgB,gBAAgB,aAAa;AAC7D;AAEA,SAAS,EAAU,GAAgB;CACjC,EAAU,IAAI,CAAE;CAChB,IAAM,KAAa,MAAoB;EACrC,CAAI,EAAE,QAAQ,KAAc,EAAE,QAAQ,UACpC,EAAY,GACZ,EAAG;CAEP;CAEA,OADA,OAAO,iBAAiB,WAAW,CAAS,SAC/B;EAEX,AADA,EAAU,OAAO,CAAE,GACnB,OAAO,oBAAoB,WAAW,CAAS;CACjD;AACF;AAGA,SAAgB,EAAS,GAAgB;CACvC,IAAI;EACF,AAAI,IAAO,aAAa,QAAQ,GAAY,SAAS,IAChD,aAAa,WAAW,CAAU;CACzC,QAAQ,CAER;CAGA,AAFA,IAAU,GACV,EAAY,GACZ,EAAU,SAAS,MAAM,EAAE,CAAC;AAC9B;AAGA,SAAgB,IAAmB;CAGjC,OAAO;EAAE,OAFK,EAAqB,GAAW,SAAiB,EAEtD;EAAO,UADJ,GAAa,MAAkB,EAAS,CAAI,GAAG,CAAC,CAClC;CAAI;AAChC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { MOTION_STORAGE_KEY as e, applyMotion as t, configureMotion as n, setStill as r, useMotionSetting as i } from "./hooks/motionSetting.js";
2
- import { DAY_STARTS as a, MOOD_BOOT_SCRIPT as o, MOOD_STORAGE_KEY as s, NIGHT_STARTS as c, applyMood as l, configureMood as u, moodBootScript as d, moodNames as f, schemeAt as p, setMood as m, systemDark as h, useMood as g } from "./hooks/mood.js";
1
+ import { DAY_STARTS as e, MOOD_STORAGE_KEY as t, MOTION_STORAGE_KEY as n, NIGHT_STARTS as r } from "./hooks/moodBoot.js";
2
+ import { applyMotion as i, configureMotion as a, setStill as o, useMotionSetting as s } from "./hooks/motionSetting.js";
3
+ import { MOOD_BOOT_SCRIPT as c, applyMood as l, configureMood as u, moodBootScript as d, moodNames as f, schemeAt as p, setMood as m, systemDark as h, useMood as g } from "./hooks/mood.js";
3
4
  import { useFinePointer as _, useReducedMotion as v } from "./hooks/motion.js";
4
5
  import { MarkProvider as y, PLACEHOLDER_MARK as b, useMark as x } from "./provider/mark.js";
5
6
  import { ThemeProvider as S, useTheme as C } from "./provider/ThemeProvider.js";
@@ -37,4 +38,4 @@ import { VideoPlayer as ue } from "./components/VideoPlayer/VideoPlayer.js";
37
38
  import { Container as de, Eyebrow as fe, Section as pe, Stack as me } from "./components/Layout/Layout.js";
38
39
  import { MoodToggle as he, MotionToggle as ge, Wordmark as _e } from "./components/Brand/Brand.js";
39
40
  import { Mark as ve } from "./motion/Mark.js";
40
- export { z as Alert, Y as Avatar, Z as BackToTop, J as Breadcrumbs, w as Button, M as Card, se as CaseStudy, k as Checkbox, R as ContactIcon, de as Container, a as DAY_STARTS, fe as Eyebrow, ie as ListRow, o as MOOD_BOOT_SCRIPT, s as MOOD_STORAGE_KEY, e as MOTION_STORAGE_KEY, ve as Mark, y as MarkProvider, le as MediaGallery, ne as MenuRail, H as Modal, he as MoodToggle, ge as MotionToggle, c as NIGHT_STARTS, b as PLACEHOLDER_MARK, q as Pagination, ee as Panel, te as PanelBar, V as Progress, ce as Prose, A as RadioGroup, ae as RowList, oe as ScreenshotCarousel, pe as Section, re as SegmentedControl, E as Select, X as Skeleton, Q as SkipLink, me as Stack, N as Status, j as Switch, K as Table, B as Tabs, P as Tag, T as Text, D as TextArea, O as TextField, S as ThemeProvider, U as ToastProvider, G as Tooltip, ue as VideoPlayer, _e as Wordmark, l as applyMood, t as applyMotion, F as brandOf, u as configureMood, n as configureMotion, I as contactKind, L as groupContacts, d as moodBootScript, f as moodNames, p as schemeAt, m as setMood, r as setStill, h as systemDark, _ as useFinePointer, x as useMark, g as useMood, i as useMotionSetting, $ as usePanelEnterDelay, v as useReducedMotion, C as useTheme, W as useToast };
41
+ export { z as Alert, Y as Avatar, Z as BackToTop, J as Breadcrumbs, w as Button, M as Card, se as CaseStudy, k as Checkbox, R as ContactIcon, de as Container, e as DAY_STARTS, fe as Eyebrow, ie as ListRow, c as MOOD_BOOT_SCRIPT, t as MOOD_STORAGE_KEY, n as MOTION_STORAGE_KEY, ve as Mark, y as MarkProvider, le as MediaGallery, ne as MenuRail, H as Modal, he as MoodToggle, ge as MotionToggle, r as NIGHT_STARTS, b as PLACEHOLDER_MARK, q as Pagination, ee as Panel, te as PanelBar, V as Progress, ce as Prose, A as RadioGroup, ae as RowList, oe as ScreenshotCarousel, pe as Section, re as SegmentedControl, E as Select, X as Skeleton, Q as SkipLink, me as Stack, N as Status, j as Switch, K as Table, B as Tabs, P as Tag, T as Text, D as TextArea, O as TextField, S as ThemeProvider, U as ToastProvider, G as Tooltip, ue as VideoPlayer, _e as Wordmark, l as applyMood, i as applyMotion, F as brandOf, u as configureMood, a as configureMotion, I as contactKind, L as groupContacts, d as moodBootScript, f as moodNames, p as schemeAt, m as setMood, o as setStill, h as systemDark, _ as useFinePointer, x as useMark, g as useMood, s as useMotionSetting, $ as usePanelEnterDelay, v as useReducedMotion, C as useTheme, W as useToast };
package/dist/kit.d.ts CHANGED
@@ -3,6 +3,8 @@ export type { ThemeConfig, ThemeProviderProps, ThemeSlots } from './provider/The
3
3
  export { setMoodDefaults } from './hooks/mood';
4
4
  export { MarkArt } from './provider/mark';
5
5
  export { RevealOnce } from './hooks/motion';
6
+ export { useCardLift } from './hooks/cardLift';
7
+ export type { CardLift } from './hooks/cardLift';
6
8
  export { PanelStageContext } from './components/Panel/stageContext';
7
9
  export type { StageContextValue } from './components/Panel/stageContext';
8
10
  export { cx } from './utils/cx';
package/dist/kit.js CHANGED
@@ -3,5 +3,6 @@ import { RevealOnce as t } from "./hooks/motion.js";
3
3
  import { MarkArt as n } from "./provider/mark.js";
4
4
  import { PLAIN_THEME as r, ThemeConfigProvider as i, ThemeRoot as a } from "./provider/ThemeProvider.js";
5
5
  import { cx as o } from "./utils/cx.js";
6
- import { PanelStageContext as s } from "./components/Panel/stageContext.js";
7
- export { n as MarkArt, r as PLAIN_THEME, s as PanelStageContext, t as RevealOnce, i as ThemeConfigProvider, a as ThemeRoot, o as cx, e as setMoodDefaults };
6
+ import { useCardLift as s } from "./hooks/cardLift.js";
7
+ import { PanelStageContext as c } from "./components/Panel/stageContext.js";
8
+ export { n as MarkArt, r as PLAIN_THEME, c as PanelStageContext, t as RevealOnce, i as ThemeConfigProvider, a as ThemeRoot, o as cx, e as setMoodDefaults, s as useCardLift };
@@ -8,7 +8,8 @@ type Props = {
8
8
  };
9
9
  /**
10
10
  * Card with a cursor spotlight and a slight 3D tilt. Its cut corner is lit
11
- * accent: the card's accent mark.
11
+ * accent: the card's accent mark. Where the library lifts cards (ThemeConfig
12
+ * lift), the cut corner also lifts toward the pointer (useCardLift).
12
13
  */
13
14
  export declare function TiltCard({ children, index, className, onEnter }: Props): import("react").JSX.Element;
14
15
  export {};
@@ -1,34 +1,35 @@
1
1
  import { useFinePointer as e, useReducedMotion as t } from "../hooks/motion.js";
2
- import n from "./TiltCard.module.js";
3
- import { useRef as r } from "react";
4
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
2
+ import { useCardLift as n } from "../hooks/cardLift.js";
3
+ import r from "./TiltCard.module.js";
4
+ import { useRef as i } from "react";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
5
6
  //#region src/motion/TiltCard.tsx
6
- function o({ children: o, index: s, className: c, onEnter: l }) {
7
- let u = r(null), d = t(), f = e();
8
- return /* @__PURE__ */ i("div", {
9
- className: n.frame,
10
- children: /* @__PURE__ */ a("div", {
11
- ref: u,
12
- className: [n.card, c].filter(Boolean).join(" "),
7
+ function s({ children: s, index: c, className: l, onEnter: u }) {
8
+ let d = i(null), f = t(), p = e();
9
+ return n(d), /* @__PURE__ */ a("div", {
10
+ className: r.frame,
11
+ children: /* @__PURE__ */ o("div", {
12
+ ref: d,
13
+ className: [r.card, l].filter(Boolean).join(" "),
13
14
  onPointerMove: (e) => {
14
- let t = u.current;
15
+ let t = d.current;
15
16
  if (!t) return;
16
17
  let n = t.getBoundingClientRect(), r = (e.clientX - n.left) / n.width, i = (e.clientY - n.top) / n.height;
17
- t.style.setProperty("--mx", `${r * 100}%`), t.style.setProperty("--my", `${i * 100}%`), f && !d && (t.dataset.live = "", t.style.setProperty("--rx", `${((.5 - i) * 5).toFixed(2)}deg`), t.style.setProperty("--ry", `${((r - .5) * 6).toFixed(2)}deg`));
18
+ t.style.setProperty("--mx", `${r * 100}%`), t.style.setProperty("--my", `${i * 100}%`), p && !f && (t.dataset.live = "", t.style.setProperty("--rx", `${((.5 - i) * 5).toFixed(2)}deg`), t.style.setProperty("--ry", `${((r - .5) * 6).toFixed(2)}deg`));
18
19
  },
19
20
  onPointerLeave: () => {
20
- let e = u.current;
21
+ let e = d.current;
21
22
  e && (delete e.dataset.live, e.style.setProperty("--rx", "0deg"), e.style.setProperty("--ry", "0deg"), e.style.setProperty("--my", "-40%"));
22
23
  },
23
- onPointerEnter: l,
24
- children: [s && /* @__PURE__ */ i("span", {
25
- className: n.index,
26
- children: s
27
- }), o]
24
+ onPointerEnter: u,
25
+ children: [c && /* @__PURE__ */ a("span", {
26
+ className: r.index,
27
+ children: c
28
+ }), s]
28
29
  })
29
30
  });
30
31
  }
31
32
  //#endregion
32
- export { o as TiltCard };
33
+ export { s as TiltCard };
33
34
 
34
35
  //# sourceMappingURL=TiltCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TiltCard.js","names":[],"sources":["../../src/motion/TiltCard.tsx"],"sourcesContent":["import { useRef, type PointerEvent, type ReactNode } from 'react';\nimport { useFinePointer, useReducedMotion } from '../hooks/motion';\nimport styles from './TiltCard.module.scss';\n\ntype Props = {\n children: ReactNode;\n /** Small inverse chip in the top-left corner, e.g. \"PRJ_01\" */\n index?: string;\n className?: string;\n onEnter?: () => void;\n};\n\n/**\n * Card with a cursor spotlight and a slight 3D tilt. Its cut corner is lit\n * accent: the card's accent mark.\n */\nexport function TiltCard({ children, index, className, onEnter }: Props) {\n const ref = useRef<HTMLDivElement>(null);\n const reduced = useReducedMotion();\n const fine = useFinePointer();\n\n const onMove = (e: PointerEvent) => {\n const el = ref.current;\n if (!el) return;\n const r = el.getBoundingClientRect();\n const x = (e.clientX - r.left) / r.width;\n const y = (e.clientY - r.top) / r.height;\n el.style.setProperty('--mx', `${x * 100}%`);\n el.style.setProperty('--my', `${y * 100}%`);\n if (fine && !reduced) {\n el.dataset.live = '';\n el.style.setProperty('--rx', `${((0.5 - y) * 5).toFixed(2)}deg`);\n el.style.setProperty('--ry', `${((x - 0.5) * 6).toFixed(2)}deg`);\n }\n };\n\n const onLeave = () => {\n const el = ref.current;\n if (!el) return;\n delete el.dataset.live;\n el.style.setProperty('--rx', '0deg');\n el.style.setProperty('--ry', '0deg');\n el.style.setProperty('--my', '-40%');\n };\n\n return (\n <div className={styles.frame}>\n <div\n ref={ref}\n className={[styles.card, className].filter(Boolean).join(' ')}\n onPointerMove={onMove}\n onPointerLeave={onLeave}\n onPointerEnter={onEnter}\n >\n {index && <span className={styles.index}>{index}</span>}\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAgBA,SAAgB,EAAS,EAAE,aAAU,UAAO,cAAW,cAAkB;CACvE,IAAM,IAAM,EAAuB,IAAI,GACjC,IAAU,EAAiB,GAC3B,IAAO,EAAe;CA0B5B,OACE,kBAAC,OAAD;EAAK,WAAW,EAAO;YACrB,kBAAC,OAAD;GACO;GACL,WAAW,CAAC,EAAO,MAAM,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;GAC5D,gBA7BU,MAAoB;IAClC,IAAM,IAAK,EAAI;IACf,IAAI,CAAC,GAAI;IACT,IAAM,IAAI,EAAG,sBAAsB,GAC7B,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAC7B,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE;IAGlC,AAFA,EAAG,MAAM,YAAY,QAAQ,GAAG,IAAI,IAAI,EAAE,GAC1C,EAAG,MAAM,YAAY,QAAQ,GAAG,IAAI,IAAI,EAAE,GACtC,KAAQ,CAAC,MACX,EAAG,QAAQ,OAAO,IAClB,EAAG,MAAM,YAAY,QAAQ,KAAK,KAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,IAAI,GAC/D,EAAG,MAAM,YAAY,QAAQ,KAAK,IAAI,MAAO,GAAG,QAAQ,CAAC,EAAE,IAAI;GAEnE;GAiBM,sBAfgB;IACpB,IAAM,IAAK,EAAI;IACV,MACL,OAAO,EAAG,QAAQ,MAClB,EAAG,MAAM,YAAY,QAAQ,MAAM,GACnC,EAAG,MAAM,YAAY,QAAQ,MAAM,GACnC,EAAG,MAAM,YAAY,QAAQ,MAAM;GACrC;GASM,gBAAgB;aALlB,CAOG,KAAS,kBAAC,QAAD;IAAM,WAAW,EAAO;cAAQ;GAAY,CAAA,GACrD,CACE;;CACF,CAAA;AAET"}
1
+ {"version":3,"file":"TiltCard.js","names":[],"sources":["../../src/motion/TiltCard.tsx"],"sourcesContent":["import { useRef, type PointerEvent, type ReactNode } from 'react';\nimport { useCardLift } from '../hooks/cardLift';\nimport { useFinePointer, useReducedMotion } from '../hooks/motion';\nimport styles from './TiltCard.module.scss';\n\ntype Props = {\n children: ReactNode;\n /** Small inverse chip in the top-left corner, e.g. \"PRJ_01\" */\n index?: string;\n className?: string;\n onEnter?: () => void;\n};\n\n/**\n * Card with a cursor spotlight and a slight 3D tilt. Its cut corner is lit\n * accent: the card's accent mark. Where the library lifts cards (ThemeConfig\n * lift), the cut corner also lifts toward the pointer (useCardLift).\n */\nexport function TiltCard({ children, index, className, onEnter }: Props) {\n const ref = useRef<HTMLDivElement>(null);\n const reduced = useReducedMotion();\n const fine = useFinePointer();\n useCardLift(ref);\n\n const onMove = (e: PointerEvent) => {\n const el = ref.current;\n if (!el) return;\n const r = el.getBoundingClientRect();\n const x = (e.clientX - r.left) / r.width;\n const y = (e.clientY - r.top) / r.height;\n el.style.setProperty('--mx', `${x * 100}%`);\n el.style.setProperty('--my', `${y * 100}%`);\n if (fine && !reduced) {\n el.dataset.live = '';\n el.style.setProperty('--rx', `${((0.5 - y) * 5).toFixed(2)}deg`);\n el.style.setProperty('--ry', `${((x - 0.5) * 6).toFixed(2)}deg`);\n }\n };\n\n const onLeave = () => {\n const el = ref.current;\n if (!el) return;\n delete el.dataset.live;\n el.style.setProperty('--rx', '0deg');\n el.style.setProperty('--ry', '0deg');\n el.style.setProperty('--my', '-40%');\n };\n\n return (\n <div className={styles.frame}>\n <div\n ref={ref}\n className={[styles.card, className].filter(Boolean).join(' ')}\n onPointerMove={onMove}\n onPointerLeave={onLeave}\n onPointerEnter={onEnter}\n >\n {index && <span className={styles.index}>{index}</span>}\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,EAAS,EAAE,aAAU,UAAO,cAAW,cAAkB;CACvE,IAAM,IAAM,EAAuB,IAAI,GACjC,IAAU,EAAiB,GAC3B,IAAO,EAAe;CA2B5B,OA1BA,EAAY,CAAG,GA2Bb,kBAAC,OAAD;EAAK,WAAW,EAAO;YACrB,kBAAC,OAAD;GACO;GACL,WAAW,CAAC,EAAO,MAAM,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;GAC5D,gBA7BU,MAAoB;IAClC,IAAM,IAAK,EAAI;IACf,IAAI,CAAC,GAAI;IACT,IAAM,IAAI,EAAG,sBAAsB,GAC7B,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAC7B,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE;IAGlC,AAFA,EAAG,MAAM,YAAY,QAAQ,GAAG,IAAI,IAAI,EAAE,GAC1C,EAAG,MAAM,YAAY,QAAQ,GAAG,IAAI,IAAI,EAAE,GACtC,KAAQ,CAAC,MACX,EAAG,QAAQ,OAAO,IAClB,EAAG,MAAM,YAAY,QAAQ,KAAK,KAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,IAAI,GAC/D,EAAG,MAAM,YAAY,QAAQ,KAAK,IAAI,MAAO,GAAG,QAAQ,CAAC,EAAE,IAAI;GAEnE;GAiBM,sBAfgB;IACpB,IAAM,IAAK,EAAI;IACV,MACL,OAAO,EAAG,QAAQ,MAClB,EAAG,MAAM,YAAY,QAAQ,MAAM,GACnC,EAAG,MAAM,YAAY,QAAQ,MAAM,GACnC,EAAG,MAAM,YAAY,QAAQ,MAAM;GACrC;GASM,gBAAgB;aALlB,CAOG,KAAS,kBAAC,QAAD;IAAM,WAAW,EAAO;cAAQ;GAAY,CAAA,GACrD,CACE;;CACF,CAAA;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"TiltCard.module.js","names":[],"sources":["../../src/motion/TiltCard.module.scss"],"sourcesContent":["@use \"@rnine/core/scss\" as r9;\n\n.frame {\n perspective: 900px;\n min-width: 0;\n display: flex;\n}\n\n.card {\n position: relative;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: r9.$space-3;\n padding: r9.$space-10 r9.$space-5 r9.$space-5;\n background:\n radial-gradient(260px circle at var(--mx, 50%) var(--my, -40%), color-mix(in srgb, #{r9.$signal} 13%, transparent), transparent 70%),\n r9.$surface;\n @include r9.corner(lg, tr);\n @include r9.hairline;\n transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));\n transition: transform r9.$duration-slow r9.$easing-out, box-shadow r9.$duration-base;\n\n // The accent mark: the cut corner, lit\n &::after { @include r9.corner-ornament; }\n\n &[data-live] { transition: transform 90ms linear, box-shadow r9.$duration-base; }\n &:hover { @include r9.hairline(color-mix(in srgb, #{r9.$accent} 45%, #{r9.$line})); }\n}\n\n.index {\n position: absolute;\n top: 0;\n left: 0;\n @include r9.type(caption);\n padding: r9.$space-1 r9.$space-3;\n background: r9.$inverse-bg;\n color: r9.$inverse-fg;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"TiltCard.module.js","names":[],"sources":["../../src/motion/TiltCard.module.scss"],"sourcesContent":["@use \"@rnine/core/scss\" as r9;\n\n.frame {\n perspective: 900px;\n min-width: 0;\n display: flex;\n}\n\n.card {\n position: relative;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: r9.$space-3;\n padding: r9.$space-10 r9.$space-5 r9.$space-5;\n background:\n radial-gradient(260px circle at var(--mx, 50%) var(--my, -40%), color-mix(in srgb, #{r9.$signal} 13%, transparent), transparent 70%),\n r9.$surface;\n @include r9.card-corner(lg);\n @include r9.hairline;\n transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));\n transition: transform r9.$duration-slow r9.$easing-out, box-shadow r9.$duration-base;\n\n // The accent mark: the cut corner, lit\n &::after { @include r9.card-ornament; }\n\n &[data-live] { transition: transform 90ms linear, box-shadow r9.$duration-base; }\n &:hover { @include r9.hairline(color-mix(in srgb, #{r9.$accent} 45%, #{r9.$line})); }\n}\n\n.index {\n position: absolute;\n top: 0;\n left: 0;\n @include r9.type(caption);\n padding: r9.$space-1 r9.$space-3;\n background: r9.$inverse-bg;\n color: r9.$inverse-fg;\n}\n"],"mappings":""}
@@ -0,0 +1,31 @@
1
+ import { type BootScriptOptions } from '../hooks/moodBoot';
2
+ import { type EmailLayoutOptions, type EmailTheme } from './email';
3
+ import { type MarkIconOptions } from './icon';
4
+ import { type StatusPageOptions } from './status';
5
+ import type { PageFont, PageTheme } from './theme';
6
+ export type Pages = {
7
+ /** A complete, self-contained status page (maintenance, coming soon, an error). */
8
+ statusPage(options: StatusPageOptions): string;
9
+ /** The frame every email shares, in the library's look. */
10
+ emailLayout(options: EmailLayoutOptions): string;
11
+ /** The library's look as resolved values, for the parts of an email you write yourself. */
12
+ emailTheme(options?: {
13
+ palette?: string | null;
14
+ }): EmailTheme;
15
+ /** The site's mark on an accent plate in the library's corner shape: a favicon or app icon (SVG). */
16
+ markIcon(options: MarkIconOptions): string;
17
+ /** The inline script that sets the mood before first paint, with the library's Auto rule. */
18
+ moodBootScript(options?: BootScriptOptions): string;
19
+ /** <meta name="theme-color"> for both moods. */
20
+ themeColorMeta(options?: {
21
+ palette?: string | null;
22
+ }): string;
23
+ /** The font files the pages and emails use: serve each at <fontsUrl><id>.woff2. */
24
+ pageFonts: readonly PageFont[];
25
+ };
26
+ /**
27
+ * A library's static pages: status pages, emails, icons. Each library's
28
+ * "<library>/pages" export is createPages with its tokens (core's own is
29
+ * Plain), so the same calls take the look of whichever library is imported.
30
+ */
31
+ export declare function createPages(theme: PageTheme): Pages;
@@ -0,0 +1,25 @@
1
+ import { bootScript as e } from "../hooks/moodBoot.js";
2
+ import { renderStatusPage as t, themeColorMeta as n } from "./status.js";
3
+ import { emailTheme as r, renderEmailLayout as i } from "./email.js";
4
+ import { renderMarkIcon as a } from "./icon.js";
5
+ //#region src/pages/create.ts
6
+ function o(o) {
7
+ let { tokens: s } = o;
8
+ return {
9
+ statusPage: (e) => t(o, e),
10
+ emailLayout: (e) => i(s, e),
11
+ emailTheme: (e) => r(s, e),
12
+ markIcon: (e) => a(s, e),
13
+ moodBootScript: (t = {}) => e({
14
+ ...t,
15
+ auto: t.auto ?? s.auto,
16
+ names: t.names ?? s.moods
17
+ }),
18
+ themeColorMeta: (e) => n(s, e),
19
+ pageFonts: s.fonts ?? []
20
+ };
21
+ }
22
+ //#endregion
23
+ export { o as createPages };
24
+
25
+ //# sourceMappingURL=create.js.map