rei-kit 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -24,9 +24,21 @@ Supabase entry. Hibi moves onto it next.
24
24
  pnpm add rei-kit
25
25
  ```
26
26
 
27
- `vue`, `tailwindcss` and `lucide-vue-next` are peer dependencies the app
28
- supplies them, so there is never a second copy of the Vue runtime.
29
- `vue-i18n` is optional and only needed for the i18n runtime.
27
+ Everything the kit expects from the app is a peer dependency, so the app's copy
28
+ is the only copy:
29
+
30
+ | peer | needed for |
31
+ | ----------------------- | --------------------------------- |
32
+ | `vue` | everything |
33
+ | `tailwindcss` | the tokens and utilities |
34
+ | `lucide-vue-next` | component icons |
35
+ | `vue-router` | `TabBar`, `LocaleLinks` |
36
+ | `vue-i18n` | the i18n runtime only |
37
+ | `@supabase/supabase-js` | the `rei-kit/supabase` entry only |
38
+
39
+ This is not a formality. A second copy of a library that works through
40
+ provide/inject is not a spare copy -- it is a different injection key, so the
41
+ app's own provider becomes invisible and the component throws on mount.
30
42
 
31
43
  ## Use
32
44
 
@@ -45,17 +57,71 @@ downloads it:
45
57
  import { createSupabaseClient } from 'rei-kit/supabase'
46
58
  ```
47
59
 
60
+ ### Wiring the styles
61
+
62
+ Three lines, and all three are load-bearing:
63
+
48
64
  ```css
49
65
  /* your app's main.css */
50
66
  @import 'tailwindcss';
51
- @import 'rei-kit/tokens.css'; /* Tailwind source: tokens and utilities */
67
+ @import 'rei-kit/tokens.css'; /* colour roles, the dark variant, utilities */
52
68
  @import 'rei-kit/styles.css'; /* compiled component styles */
53
69
 
54
- /* the whole rebrand */
70
+ /* Tailwind generates a utility only where it has seen the class, and it does
71
+ not walk node_modules on its own. Without this the kit's components render
72
+ with every class present in the markup and absent from the stylesheet. */
73
+ @source '../../node_modules/rei-kit/dist';
74
+ ```
75
+
76
+ The path is relative to the CSS file, so adjust the `../` depth to where your
77
+ `main.css` sits.
78
+
79
+ Leaving any of the three out fails quietly: the build succeeds, the components
80
+ mount, and they come out unstyled. Nothing type-checks this, so it is worth a
81
+ test -- see _Not breaking the apps that use it_.
82
+
83
+ ### Prerendering
84
+
85
+ The kit imports and renders on a server, so an app can prerender with
86
+ `vite-ssg` or any other SSR build. Two things stay the app's job, because only
87
+ the app knows the answer:
88
+
89
+ - **The theme.** `applyTheme` does nothing without a document, so prerendered
90
+ HTML carries no `.dark`. Set it before hydration with a small synchronous
91
+ script in `index.html`, or the first paint flashes light.
92
+ - **Today's date.** `useToday()` on a server is the *server's* today, a
93
+ different day from the visitor's either side of midnight. Render anything
94
+ derived from it on the client.
95
+
96
+ ### Colours
97
+
98
+ `tokens.css` defines all eleven roles, a `.dark` block for each surface, and the
99
+ `dark` variant. A new app rebrands by overriding values, never by renaming:
100
+
101
+ ```css
55
102
  @theme {
56
- --color-primary: #6b4de6;
103
+ --color-primary: #6b4de6; /* main action */
104
+ --color-accent: #3b2f8f;
57
105
  --color-positive: #2fa36b;
58
106
  --color-negative: #d1453b;
107
+ --color-warning: #d89a3e;
108
+ --color-muted: #efeaff; /* calm surface, empty cell */
109
+ --color-canvas: #faf9ff; /* behind the shell */
110
+ --color-surface: #ffffff; /* card */
111
+ --color-ink: #17132b; /* text */
112
+ --color-ink-soft: #6a6484; /* secondary text */
113
+ --color-hair: #e8e4f2; /* rule */
114
+ }
115
+ ```
116
+
117
+ An app that already has its own palette does not have to rename it. Alias the
118
+ roles onto the names it already uses, and keep them as `var()` references so
119
+ the app's own dark-mode overrides carry into the kit's components:
120
+
121
+ ```css
122
+ @theme {
123
+ --color-primary: var(--color-sea);
124
+ --color-muted: var(--color-mist);
59
125
  }
60
126
  ```
61
127
 
@@ -4,7 +4,13 @@ export type ThemePreference = 'system' | 'light' | 'dark';
4
4
  export declare function isThemePreference(value: unknown): value is ThemePreference;
5
5
  /** Reads the stored preference, falling back to `system`. */
6
6
  export declare function readStoredTheme(): ThemePreference;
7
- /** Adds or removes `.dark` on `<html>`, resolving `system` against the OS. */
7
+ /**
8
+ * Adds or removes `.dark` on `<html>`, resolving `system` against the OS.
9
+ *
10
+ * A no-op without a document. There is no OS preference to read on a server and
11
+ * no `<html>` to write to, so a prerender leaves the class off and the app
12
+ * decides the theme before hydration — see the note in the README.
13
+ */
8
14
  export declare function applyTheme(preference: ThemePreference): void;
9
15
  /**
10
16
  * Sets where the preference is stored.
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * @returns Read-only ref holding today's `YYYY-MM-DD` key.
3
3
  *
4
+ * Rendered on a server this is the *server's* today, which is a different day
5
+ * from the visitor's either side of midnight. Anything prerendered from it
6
+ * would hydrate to a different value; render it on the client.
7
+ *
4
8
  * @example
5
9
  * ```ts
6
10
  * const today = useToday()
@@ -12,7 +12,9 @@ export interface VisualViewportRect {
12
12
  * sized in `dvh` sitting partly underneath the keyboard.
13
13
  *
14
14
  * `null` means the API is unavailable, which callers should read as "trust the
15
- * layout viewport" rather than as zero.
15
+ * layout viewport" rather than as zero. A server has no viewport at all, so it
16
+ * gets that same `null` — this runs during `setup`, and a component using it
17
+ * has to survive being rendered there.
16
18
  *
17
19
  * @example
18
20
  * ```ts
package/dist/index.js CHANGED
@@ -358,8 +358,15 @@ function storeTheme(preference) {
358
358
  localStorage.setItem(storageKey, preference);
359
359
  } catch {}
360
360
  }
361
- /** Adds or removes `.dark` on `<html>`, resolving `system` against the OS. */
361
+ /**
362
+ * Adds or removes `.dark` on `<html>`, resolving `system` against the OS.
363
+ *
364
+ * A no-op without a document. There is no OS preference to read on a server and
365
+ * no `<html>` to write to, so a prerender leaves the class off and the app
366
+ * decides the theme before hydration — see the note in the README.
367
+ */
362
368
  function applyTheme(preference) {
369
+ if (typeof document === "undefined") return;
363
370
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
364
371
  const isDark = preference === "dark" || preference === "system" && prefersDark;
365
372
  document.documentElement.classList.toggle("dark", isDark);
@@ -379,7 +386,7 @@ function controller() {
379
386
  storeTheme(next);
380
387
  applyTheme(next);
381
388
  }, { immediate: true });
382
- window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
389
+ if (typeof window !== "undefined") window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
383
390
  if (preference?.value === "system") applyTheme("system");
384
391
  });
385
392
  return preference;
@@ -416,6 +423,7 @@ function useTheme() {
416
423
  */
417
424
  var current = ref(todayKey());
418
425
  var timer;
426
+ var watching = false;
419
427
  /** A second past midnight, so a fast timer cannot fire on the old date. */
420
428
  function msUntilMidnight() {
421
429
  const now = /* @__PURE__ */ new Date();
@@ -431,15 +439,32 @@ function schedule() {
431
439
  schedule();
432
440
  }, msUntilMidnight());
433
441
  }
434
- schedule();
435
- document.addEventListener("visibilitychange", () => {
436
- if (document.visibilityState !== "visible") return;
437
- refresh();
442
+ /**
443
+ * Starts the clock, once, and only where there is a clock to watch.
444
+ *
445
+ * This used to run at import time, which made the module impossible to load on
446
+ * a server: `document` is not defined there, and a barrel export means one
447
+ * `import { BaseButton } from 'rei-kit'` pulls this file in. Deferring it to
448
+ * the first `useToday()` also means an app that never asks for today never
449
+ * arms a timer.
450
+ */
451
+ function watchTheClock() {
452
+ if (watching || typeof document === "undefined") return;
453
+ watching = true;
438
454
  schedule();
439
- });
455
+ document.addEventListener("visibilitychange", () => {
456
+ if (document.visibilityState !== "visible") return;
457
+ refresh();
458
+ schedule();
459
+ });
460
+ }
440
461
  /**
441
462
  * @returns Read-only ref holding today's `YYYY-MM-DD` key.
442
463
  *
464
+ * Rendered on a server this is the *server's* today, which is a different day
465
+ * from the visitor's either side of midnight. Anything prerendered from it
466
+ * would hydrate to a different value; render it on the client.
467
+ *
443
468
  * @example
444
469
  * ```ts
445
470
  * const today = useToday()
@@ -447,6 +472,7 @@ document.addEventListener("visibilitychange", () => {
447
472
  * ```
448
473
  */
449
474
  function useToday() {
475
+ watchTheClock();
450
476
  return readonly(current);
451
477
  }
452
478
  //#endregion
@@ -622,7 +648,9 @@ function useDragScroll(target) {
622
648
  * sized in `dvh` sitting partly underneath the keyboard.
623
649
  *
624
650
  * `null` means the API is unavailable, which callers should read as "trust the
625
- * layout viewport" rather than as zero.
651
+ * layout viewport" rather than as zero. A server has no viewport at all, so it
652
+ * gets that same `null` — this runs during `setup`, and a component using it
653
+ * has to survive being rendered there.
626
654
  *
627
655
  * @example
628
656
  * ```ts
@@ -632,7 +660,7 @@ function useDragScroll(target) {
632
660
  */
633
661
  function useVisualViewport() {
634
662
  const rect = ref(null);
635
- const viewport = window.visualViewport;
663
+ const viewport = typeof window === "undefined" ? void 0 : window.visualViewport;
636
664
  if (!viewport) return readonly(rect);
637
665
  function read() {
638
666
  if (!viewport) return;
@@ -890,7 +918,7 @@ var BaseSheet_default = /*#__PURE__*/ _plugin_vue_export_helper_default(BaseShee
890
918
  var _hoisted_1$10 = { class: "flex flex-col items-center gap-3 px-6 py-10 text-center" };
891
919
  var _hoisted_2$9 = {
892
920
  key: 0,
893
- class: "bg-muted text-primary rounded-card flex size-12 items-center"
921
+ class: "bg-muted text-primary rounded-card flex size-12 items-center justify-center"
894
922
  };
895
923
  var _hoisted_3$5 = { class: "text-ink text-base font-semibold" };
896
924
  var _hoisted_4$4 = {
@@ -1303,6 +1331,7 @@ function createI18nRuntime(options) {
1303
1331
  * match is the best one — not simply the first entry.
1304
1332
  */
1305
1333
  function detectSystemLocale() {
1334
+ if (typeof document === "undefined") return fallback;
1306
1335
  for (const tag of navigator.languages ?? [navigator.language]) {
1307
1336
  const base = tag.split("-")[0]?.toLowerCase();
1308
1337
  if (base && isSupported(base)) return base;
@@ -1360,7 +1389,7 @@ function createI18nRuntime(options) {
1360
1389
  watchEffect(() => {
1361
1390
  core.locale.value = activeLocale.value;
1362
1391
  setFormatLocale(intlLocale.value);
1363
- document.documentElement.lang = activeLocale.value;
1392
+ if (typeof document !== "undefined") document.documentElement.lang = activeLocale.value;
1364
1393
  });
1365
1394
  /** Read and write the language preference. */
1366
1395
  function useLocalePreference() {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["$attrs","$slots"],"sources":["../src/utils/date.ts","../src/utils/format.ts","../src/utils/day-label.ts","../src/utils/download.ts","../src/utils/redirect.ts","../src/utils/haptics.ts","../src/utils/platform.ts","../src/composables/use-theme.ts","../src/composables/use-today.ts","../src/composables/use-online.ts","../src/composables/use-debounced-callback.ts","../src/composables/use-drag-scroll.ts","../src/composables/use-visual-viewport.ts","../src/components/BaseButton.vue","../src/components/BaseButton.vue","../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/components/BaseSheet.vue","../src/components/BaseSheet.vue","../src/components/EmptyState.vue","../src/components/EmptyState.vue","../src/components/PageHeader.vue","../src/components/PageHeader.vue","../src/components/ToneDot.vue","../src/components/ToneDot.vue","../src/components/SectionHeading.vue","../src/components/SectionHeading.vue","../src/components/SegmentedControl.vue","../src/components/SegmentedControl.vue","../src/components/SettingsGroup.vue","../src/components/SettingsGroup.vue","../src/components/SettingsRow.vue","../src/components/SettingsRow.vue","../src/components/SkeletonList.vue","../src/components/SkeletonList.vue","../src/components/StatCard.vue","../src/components/StatCard.vue","../src/components/LocaleLinks.vue","../src/components/LocaleLinks.vue","../src/components/GoogleButton.vue","../src/components/GoogleButton.vue","../src/components/TabBar.vue","../src/components/TabBar.vue","../src/i18n/runtime.ts","../src/index.ts"],"sourcesContent":["/**\n * Local calendar-day helpers.\n *\n * Every function is pure and works on `YYYY-MM-DD` keys, the same shape as the\n * `date` columns in Postgres. Nothing here calls `toISOString`: that converts to\n * UTC, so in a UTC+9 timezone every entry made between midnight and 09:00 would\n * be written to the previous day.\n */\n\n/**\n * Formats a `Date` as a local `YYYY-MM-DD` key.\n *\n * @param date - Any `Date`; only its local year, month and day are read.\n * @returns The calendar day in the runtime's own timezone.\n *\n * @example\n * ```ts\n * // 2026-08-23 01:30 in Tokyo\n * toDateKey(new Date()) // '2026-08-23'\n * new Date().toISOString() // '2026-08-22T16:30…' ← the bug\n * ```\n */\nexport function toDateKey(date: Date): string {\n const year = String(date.getFullYear()).padStart(4, '0')\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n\n return `${year}-${month}-${day}`\n}\n\n/** Today's key in the user's own timezone. */\nexport function todayKey(): string {\n return toDateKey(new Date())\n}\n\n/**\n * Parses a `YYYY-MM-DD` key into a `Date` at local midnight.\n *\n * @param key - A key produced by {@link toDateKey}.\n * @returns Local midnight of that calendar day.\n * @throws If the key is not three numeric parts.\n *\n * @example\n * ```ts\n * fromDateKey('2026-08-23') // local midnight, correct\n * new Date('2026-08-23') // UTC midnight — shifts a day in some zones\n * ```\n */\nexport function fromDateKey(key: string): Date {\n const [year, month, day] = key.split('-').map(Number)\n\n if (year === undefined || month === undefined || day === undefined) {\n throw new Error(`Invalid date key: ${key}`)\n }\n\n return new Date(year, month - 1, day)\n}\n\n/**\n * Shifts a date key by whole calendar days.\n *\n * Uses `setDate`, which is calendar-aware: it rolls over month and year ends,\n * and stays correct across daylight-saving transitions. Adding\n * `days * 86_400_000` milliseconds would not — a DST day is 23 or 25 hours long.\n *\n * @param key - Starting `YYYY-MM-DD` key.\n * @param days - Days to add; negative goes back.\n * @returns The resulting key.\n *\n * @example\n * ```ts\n * addDays('2026-01-31', 1) // '2026-02-01'\n * addDays('2026-01-01', -1) // '2025-12-31'\n * addDays('2028-02-28', 1) // '2028-02-29' — leap year\n * ```\n */\nexport function addDays(key: string, days: number): string {\n const date = fromDateKey(key)\n date.setDate(date.getDate() + days)\n\n return toDateKey(date)\n}\n\n/**\n * The last `count` days ending today, oldest first.\n *\n * `today` is a parameter so the function stays pure and testable; call sites\n * normally omit it.\n *\n * @param count - How many days to return, including `today`.\n * @param today - End of the range. Defaults to the real today.\n * @returns Keys in ascending order.\n *\n * @example\n * ```ts\n * lastNDays(3, '2026-08-23') // ['2026-08-21', '2026-08-22', '2026-08-23']\n * ```\n */\nexport function lastNDays(count: number, today: string = todayKey()): string[] {\n const keys: string[] = []\n\n for (let offset = count - 1; offset >= 0; offset -= 1) {\n keys.push(addDays(today, -offset))\n }\n\n return keys\n}\n\n/** 0 = week starts on Sunday, 1 = on Monday. Mirrors `profiles.week_starts_on`. */\nexport type WeekStart = 0 | 1\n\n/**\n * The first day of the week containing `key`.\n *\n * The user's preference is a parameter, not a module-level setting: changing it\n * in Profile has to re-render the week grid and the year heatmap immediately,\n * and a global would make that a hidden dependency.\n *\n * @param key - Any day in the week.\n * @param weekStartsOn - 0 for Sunday, 1 for Monday.\n * @returns Key of that week's first day.\n *\n * @example\n * ```ts\n * // 2026-08-23 is a Sunday\n * startOfWeek('2026-08-23', 1) // '2026-08-17' — previous Monday\n * startOfWeek('2026-08-23', 0) // '2026-08-23' — already Sunday\n * ```\n */\nexport function startOfWeek(key: string, weekStartsOn: WeekStart): string {\n const weekday = fromDateKey(key).getDay()\n const offset = (weekday - weekStartsOn + 7) % 7\n\n return addDays(key, -offset)\n}\n\n/**\n * Every day of a calendar year, in order.\n *\n * Leap years fall out of the loop for free: it walks day by day until the year\n * rolls over, so February 29 is included when it exists.\n *\n * @param year - Four-digit year.\n * @returns 365 or 366 keys, oldest first.\n */\nexport function eachDayOfYear(year: number): string[] {\n const keys: string[] = []\n const date = new Date(year, 0, 1)\n\n while (date.getFullYear() === year) {\n keys.push(toDateKey(date))\n date.setDate(date.getDate() + 1)\n }\n\n return keys\n}\n\n/**\n * Empty cells before a block's first day in a seven-row column grid.\n *\n * The grid fills column by column, so the first column is only partly used\n * unless the block starts exactly on the week's first day. An off-by-one here\n * shifts the whole block by a row, so this is unit tested.\n *\n * @param firstDayKey - First day of the block, e.g. `'2026-02-01'`.\n * @param weekStartsOn - 0 for Sunday, 1 for Monday.\n * @returns 0-6 blank cells.\n *\n * @example\n * ```ts\n * leadingBlanks('2026-01-01', 1) // 3 — a Thursday, Mon-Wed are blank\n * leadingBlanks('2024-01-01', 1) // 0 — a Monday\n * ```\n */\nexport function leadingBlanks(firstDayKey: string, weekStartsOn: WeekStart): number {\n return (fromDateKey(firstDayKey).getDay() - weekStartsOn + 7) % 7\n}\n","import { ref } from 'vue'\n\n/**\n * The locale `Intl` formatting uses.\n *\n * Held here rather than imported from an i18n runtime so the utilities have no\n * i18n dependency at all: an app that never installs vue-i18n still gets dates\n * in the right language. `createI18nRuntime` sets this when it is used.\n */\nconst locale = ref<string>(typeof navigator === 'undefined' ? 'en' : (navigator.language ?? 'en'))\n\n/**\n * Points every formatter at a new locale.\n *\n * @example\n * ```ts\n * setFormatLocale('tr-TR')\n * ```\n */\nexport function setFormatLocale(next: string): void {\n locale.value = next\n}\n\n/**\n * `Intl.DateTimeFormat` is expensive to construct, so instances are cached per\n * locale and option set. The key includes the locale, which is what lets the\n * cache survive a language change instead of returning stale formatters.\n */\nconst cache = new Map<string, Intl.DateTimeFormat>()\n\n/**\n * Formats a date in the active locale.\n *\n * Reading the locale ref here is deliberate: called from a `computed`, the\n * result re-evaluates when the language changes.\n *\n * @param date - Date to format.\n * @param options - Passed straight to `Intl.DateTimeFormat`.\n *\n * @example\n * ```ts\n * formatDate(new Date(), { weekday: 'narrow' }) // 'T'\n * ```\n */\nexport function formatDate(date: Date, options: Intl.DateTimeFormatOptions): string {\n const tag = locale.value\n const key = `${tag}:${JSON.stringify(options)}`\n\n let formatter = cache.get(key)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(tag, options)\n cache.set(key, formatter)\n }\n\n return formatter.format(date)\n}\n","import { addDays, fromDateKey } from './date'\nimport { formatDate } from './format'\n\n/** The two days worth naming rather than numbering. */\nexport interface DayLabels {\n today: string\n yesterday: string\n}\n\n/**\n * A short name for a day, relative to today.\n *\n * \"Today\" and \"Yesterday\" are worth spelling out — they are the two a user\n * actually reaches for. Anything older gets its weekday, which inside a\n * five-day window is unambiguous and stays two or three characters in every\n * language.\n *\n * The two words are arguments rather than translated here: a library that calls\n * `t()` forces every consumer onto one i18n setup.\n *\n * @param dateKey - The day to label (`YYYY-MM-DD`).\n * @param today - Today's key, passed in so the caller controls the clock.\n * @param labels - What to call today and yesterday.\n *\n * @example\n * ```ts\n * relativeDayLabel('2026-08-28', '2026-08-31', { today: 'Today', yesterday: 'Yesterday' })\n * // 'Fri'\n * ```\n */\nexport function relativeDayLabel(dateKey: string, today: string, labels: DayLabels): string {\n if (dateKey === today) return labels.today\n if (dateKey === addDays(today, -1)) return labels.yesterday\n\n return formatDate(fromDateKey(dateKey), { weekday: 'short' })\n}\n","/**\n * Hands the user a file without a server round trip.\n *\n * @param data - Anything `JSON.stringify` can serialise.\n * @param filename - Suggested name, e.g. `hibi-export-2026-08-24.json`.\n */\nexport function downloadJson(data: unknown, filename: string): void {\n const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n\n link.href = url\n link.download = filename\n link.click()\n\n URL.revokeObjectURL(url)\n}\n","/**\n * What a router hands back for one query key.\n *\n * Inlined rather than imported from vue-router: the shape is `string | null`\n * either way, and a helper this small should not drag a router into the\n * package's dependencies.\n */\nexport type QueryValue = string | null\n\n/**\n * Resolves a `?redirect=` query value into a safe in-app path.\n *\n * Only same-origin paths are accepted. Anything else falls back to `/`,\n * so a crafted link cannot bounce a user from the real login page to a\n * phishing clone.\n *\n * Pure: takes the query value instead of reading the router, so it also\n * works inside navigation guards and can be unit tested.\n *\n * @param target - Raw `route.query.redirect` value. May be a string, an\n * array (repeated query key), `null`, or `undefined`.\n * @returns A path starting with a single `/`. Defaults to `/`.\n *\n * @example\n * ```ts\n * // in a view\n * await router.push(safeRedirect(route.query.redirect))\n *\n * // in a guard\n * return safeRedirect(to.query.redirect)\n * ```\n *\n * @example\n * ```ts\n * safeRedirect('/week') // '/week'\n * safeRedirect('https://evil.com') // '/'\n * safeRedirect('//evil.com') // '/' (protocol-relative URL)\n * safeRedirect(['/a', '/b']) // '/'\n * safeRedirect(undefined) // '/'\n * ```\n */\nexport function safeRedirect(target: QueryValue | QueryValue[] | undefined): string {\n if (typeof target === 'string' && target.startsWith('/') && !target.startsWith('//')) {\n return target\n }\n\n return '/'\n}\n","/**\n * A short vibration for a confirmed tap.\n *\n * Optional chaining is not decoration: iOS Safari has no `vibrate` at all, and\n * calling it unguarded would throw on every marked day.\n *\n * @param duration - Milliseconds. Keep it under ~15ms; longer reads as an alert.\n */\nexport function tapFeedback(duration = 10): void {\n navigator.vibrate?.(duration)\n}\n","/**\n * Whether the app is running from the Home Screen rather than a browser tab.\n *\n * Two checks because iOS predates the standard one: `display-mode: standalone`\n * is the modern signal, `navigator.standalone` is Safari's own.\n */\nexport function isInstalled(): boolean {\n if (typeof window === 'undefined') return false\n\n return (\n window.matchMedia('(display-mode: standalone)').matches ||\n (navigator as Navigator & { standalone?: boolean }).standalone === true\n )\n}\n\n/** iPhone and iPad, including iPadOS reporting itself as a Mac. */\nexport function isApplePortable(): boolean {\n if (typeof window === 'undefined') return false\n\n return (\n /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)\n )\n}\n\n/**\n * Whether this device can only receive notifications once the app is installed.\n *\n * Safari on iOS grants notification permission to an installed web app and to\n * nothing else — in a normal tab the request does not even prompt. Telling the\n * user to allow notifications there is asking for something the browser will\n * not offer, so the UI has to say \"add to Home Screen\" instead.\n *\n * @example\n * ```ts\n * if (needsIosInstall()) // show the Home Screen instruction, not the button\n * ```\n */\nexport function needsIosInstall(): boolean {\n return isApplePortable() && !isInstalled()\n}\n","import { ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\n/** What the user asked for; `system` follows the OS. */\nexport type ThemePreference = 'system' | 'light' | 'dark'\n\n/**\n * Namespaced by the app, not by this package.\n *\n * Two rei-kit apps served from the same origin would otherwise share one theme\n * setting — and during development on localhost, they will be.\n */\nlet storageKey = 'rei-theme'\n\nexport function isThemePreference(value: unknown): value is ThemePreference {\n return value === 'system' || value === 'light' || value === 'dark'\n}\n\n/** Reads the stored preference, falling back to `system`. */\nexport function readStoredTheme(): ThemePreference {\n try {\n const stored = localStorage.getItem(storageKey)\n\n return isThemePreference(stored) ? stored : 'system'\n } catch {\n return 'system'\n }\n}\n\nfunction storeTheme(preference: ThemePreference): void {\n try {\n localStorage.setItem(storageKey, preference)\n } catch {\n // Private mode or blocked storage: the choice just will not persist.\n }\n}\n\n/** Adds or removes `.dark` on `<html>`, resolving `system` against the OS. */\nexport function applyTheme(preference: ThemePreference): void {\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\n const isDark = preference === 'dark' || (preference === 'system' && prefersDark)\n\n document.documentElement.classList.toggle('dark', isDark)\n}\n\n/**\n * The shared preference, created on first use rather than at import.\n *\n * Lazy on purpose: reading storage at import time would lock in the default key\n * before an app had a chance to set its own, leaving the controller reading one\n * key and writing another.\n */\nlet preference: Ref<ThemePreference> | null = null\n\nfunction controller(): Ref<ThemePreference> {\n if (preference) return preference\n\n preference = ref<ThemePreference>(readStoredTheme())\n\n watch(\n preference,\n (next) => {\n storeTheme(next)\n applyTheme(next)\n },\n { immediate: true },\n )\n\n // While on `system`, follow the OS if the user flips it at night.\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {\n if (preference?.value === 'system') applyTheme('system')\n })\n\n return preference\n}\n\n/**\n * Sets where the preference is stored.\n *\n * Safe in either order: called before the first `useTheme()` it simply changes\n * the key, and called after it re-reads under the new one, so the controller\n * never reads from one key while writing to another.\n *\n * @example\n * ```ts\n * setThemeStorageKey('hibi-theme') // once, at startup\n * ```\n */\nexport function setThemeStorageKey(key: string): void {\n storageKey = key\n if (preference) preference.value = readStoredTheme()\n}\n\n/** @returns The shared preference ref; assigning to it stores and applies it. */\nexport function useTheme(): Ref<ThemePreference> {\n return controller()\n}\n","import { readonly, ref } from 'vue'\n\nimport { todayKey } from '../utils/date'\n\n/**\n * Today's date key, kept current while the app stays open.\n *\n * `todayKey()` called once in `setup` freezes the date for the lifetime of the\n * component. Nobody notices in a session that lasts minutes, but a phone left\n * on the Today screen overnight would keep marking yesterday, and the Week grid\n * would disable the column that just became today.\n */\nconst current = ref(todayKey())\n\nlet timer: ReturnType<typeof setTimeout> | undefined\n\n/** A second past midnight, so a fast timer cannot fire on the old date. */\nfunction msUntilMidnight(): number {\n const now = new Date()\n const next = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 1)\n\n return next.getTime() - now.getTime()\n}\n\nfunction refresh() {\n current.value = todayKey()\n}\n\nfunction schedule() {\n clearTimeout(timer)\n timer = setTimeout(() => {\n refresh()\n schedule()\n }, msUntilMidnight())\n}\n\nschedule()\n\n// A sleeping phone does not run timers reliably, so the tab also re-checks the\n// moment it comes back — which is when the user would see a stale date.\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return\n\n refresh()\n schedule()\n})\n\n/**\n * @returns Read-only ref holding today's `YYYY-MM-DD` key.\n *\n * @example\n * ```ts\n * const today = useToday()\n * const isFuture = computed(() => day > today.value)\n * ```\n */\nexport function useToday() {\n return readonly(current)\n}\n","import { onMounted, onUnmounted, readonly, ref } from 'vue'\n\n/**\n * Tracks whether the browser thinks it has a network connection.\n *\n * Note the limit: `navigator.onLine` only reports whether a network interface\n * is up, not whether requests actually succeed. Treat it as a hint for the UI,\n * never as a reason to skip error handling.\n *\n * Listeners are removed on unmount, so the composable is safe to call per view.\n *\n * @returns A readonly ref that flips with the browser's online/offline events.\n *\n * @example\n * ```ts\n * const isOnline = useOnline()\n * // <p v-if=\"!isOnline\">You're offline.</p>\n * ```\n */\nexport function useOnline() {\n const isOnline = ref(true)\n\n function update() {\n isOnline.value = navigator.onLine\n }\n\n onMounted(() => {\n update()\n window.addEventListener('online', update)\n window.addEventListener('offline', update)\n })\n\n onUnmounted(() => {\n window.removeEventListener('online', update)\n window.removeEventListener('offline', update)\n })\n\n return readonly(isOnline)\n}\n","import { onScopeDispose } from 'vue'\n\n/**\n * Delays a callback until the caller stops calling it.\n *\n * Used for note autosave: a request per keystroke would be wasteful, but losing\n * the last keystrokes when the user navigates away would be worse — so the\n * pending call is flushed on dispose, and `flush` is exposed for route guards.\n *\n * @param callback - Runs with the arguments of the most recent call.\n * @param delay - Quiet period in milliseconds.\n * @returns `run` to schedule, `flush` to run now, `cancel` to drop.\n *\n * @example\n * ```ts\n * const save = useDebouncedCallback((body: string) => mutate(body), 800)\n * watch(text, (value) => save.run(value))\n * onBeforeRouteLeave(() => save.flush())\n * ```\n */\nexport function useDebouncedCallback<A extends unknown[]>(\n callback: (...args: A) => void,\n delay = 800,\n) {\n let timer: ReturnType<typeof setTimeout> | null = null\n let pending: A | null = null\n\n /** Runs the pending call right now, if there is one. */\n function flush() {\n if (timer !== null) clearTimeout(timer)\n timer = null\n\n if (pending !== null) {\n const args = pending\n pending = null\n callback(...args)\n }\n }\n\n /** Drops the pending call without running it. */\n function cancel() {\n if (timer !== null) clearTimeout(timer)\n timer = null\n pending = null\n }\n\n function run(...args: A) {\n pending = args\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(flush, delay)\n }\n\n // A closing sheet or an unmounting view must not eat the last keystrokes.\n onScopeDispose(flush)\n\n return { run, flush, cancel }\n}\n","import { onScopeDispose, watch } from 'vue'\nimport type { Ref } from 'vue'\n\n/** Movement before a press counts as a drag rather than a tap. */\nconst DRAG_THRESHOLD_PX = 6\n\n/**\n * Drag-to-scroll for a horizontally scrolling element.\n *\n * The app puts `touch-action: pan-y` on the page content so the tab-swipe\n * gesture keeps its pointer events — the browser never claims a horizontal\n * drag, which also means it never pans this element natively. Rather than give\n * that up, horizontal scrolling is driven here.\n *\n * @param target - The scroll container.\n * @returns `didDrag`, so a click handler can ignore the press that ended a drag.\n *\n * @example\n * ```ts\n * const scroller = ref<HTMLElement | null>(null)\n * const { didDrag } = useDragScroll(scroller)\n *\n * function onClick() {\n * if (didDrag()) return\n * // …treat as a tap\n * }\n * ```\n */\nexport function useDragScroll(target: Ref<HTMLElement | null>) {\n let pointerId: number | null = null\n let startX = 0\n let startScroll = 0\n let dragged = false\n\n function onPointerDown(event: PointerEvent) {\n const element = target.value\n if (!element || event.pointerType === 'mouse') return\n\n pointerId = event.pointerId\n startX = event.clientX\n startScroll = element.scrollLeft\n dragged = false\n }\n\n function onPointerMove(event: PointerEvent) {\n const element = target.value\n if (!element || event.pointerId !== pointerId) return\n\n const dx = event.clientX - startX\n if (!dragged && Math.abs(dx) < DRAG_THRESHOLD_PX) return\n\n // Capture only once the gesture is clearly horizontal, so a vertical scroll\n // that happens to start here still belongs to the page.\n if (!dragged) {\n dragged = true\n element.setPointerCapture(event.pointerId)\n }\n\n element.scrollLeft = startScroll - dx\n }\n\n function onPointerUp(event: PointerEvent) {\n const element = target.value\n if (element?.hasPointerCapture(event.pointerId)) {\n element.releasePointerCapture(event.pointerId)\n }\n\n pointerId = null\n }\n\n function bind(element: HTMLElement) {\n element.addEventListener('pointerdown', onPointerDown)\n element.addEventListener('pointermove', onPointerMove)\n element.addEventListener('pointerup', onPointerUp)\n element.addEventListener('pointercancel', onPointerUp)\n }\n\n function unbind(element: HTMLElement) {\n element.removeEventListener('pointerdown', onPointerDown)\n element.removeEventListener('pointermove', onPointerMove)\n element.removeEventListener('pointerup', onPointerUp)\n element.removeEventListener('pointercancel', onPointerUp)\n }\n\n watch(\n target,\n (element, previous) => {\n if (previous) unbind(previous)\n if (element) bind(element)\n },\n { immediate: true },\n )\n\n onScopeDispose(() => {\n if (target.value) unbind(target.value)\n })\n\n return { didDrag: () => dragged }\n}\n","import { onScopeDispose, readonly, ref } from 'vue'\n\n/** The visible area, once the on-screen keyboard has taken its share. */\nexport interface VisualViewportRect {\n height: number\n offsetTop: number\n}\n\n/**\n * Tracks the visual viewport.\n *\n * Chrome and Android browsers honour `interactive-widget=resizes-content`, so\n * the layout viewport already shrinks for the keyboard there. Safari on iOS\n * does not implement it: it shrinks only the *visual* viewport, leaving a sheet\n * sized in `dvh` sitting partly underneath the keyboard.\n *\n * `null` means the API is unavailable, which callers should read as \"trust the\n * layout viewport\" rather than as zero.\n *\n * @example\n * ```ts\n * const viewport = useVisualViewport()\n * // :style=\"viewport ? { height: `${viewport.height}px` } : undefined\"\n * ```\n */\nexport function useVisualViewport() {\n const rect = ref<VisualViewportRect | null>(null)\n\n const viewport = window.visualViewport\n if (!viewport) return readonly(rect)\n\n function read() {\n if (!viewport) return\n\n rect.value = { height: viewport.height, offsetTop: viewport.offsetTop }\n }\n\n read()\n\n // `scroll` matters as much as `resize`: iOS shifts the visual viewport up to\n // keep the focused field visible, without changing its height.\n viewport.addEventListener('resize', read)\n viewport.addEventListener('scroll', read)\n\n onScopeDispose(() => {\n viewport.removeEventListener('resize', read)\n viewport.removeEventListener('scroll', read)\n })\n\n return readonly(rect)\n}\n","<script setup lang=\"ts\">\nconst {\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled = false,\n type = 'button',\n} = defineProps<{\n variant?: 'primary' | 'ghost' | 'danger'\n size?: 'sm' | 'md'\n loading?: boolean\n disabled?: boolean\n type?: 'button' | 'submit'\n}>()\n\nconst VARIANT_CLASS = {\n primary: 'bg-primary text-white hover:bg-primary/90',\n ghost: 'bg-transparent text-ink hover:bg-muted',\n danger: 'bg-negative text-white hover:bg-negative/90',\n} as const\n\nconst SIZE_CLASS = {\n sm: 'h-9 px-3 text-sm',\n md: 'h-11 px-4 text-base',\n} as const\n</script>\n\n<template>\n <button\n :type=\"type\"\n :disabled=\"disabled || loading\"\n :aria-busy=\"loading\"\n class=\"rounded-card focus-visible:outline-primary inline-flex items-center justify-center gap-2 font-medium transition-transform duration-100 select-none focus-visible:outline-2 focus-visible:outline-offset-2 active:scale-95 disabled:pointer-events-none disabled:opacity-50\"\n :class=\"[VARIANT_CLASS[variant], SIZE_CLASS[size]]\"\n >\n <span\n v-if=\"loading\"\n class=\"size-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n aria-hidden=\"true\"\n />\n <slot />\n </button>\n</template>\n","<script setup lang=\"ts\">\nconst {\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled = false,\n type = 'button',\n} = defineProps<{\n variant?: 'primary' | 'ghost' | 'danger'\n size?: 'sm' | 'md'\n loading?: boolean\n disabled?: boolean\n type?: 'button' | 'submit'\n}>()\n\nconst VARIANT_CLASS = {\n primary: 'bg-primary text-white hover:bg-primary/90',\n ghost: 'bg-transparent text-ink hover:bg-muted',\n danger: 'bg-negative text-white hover:bg-negative/90',\n} as const\n\nconst SIZE_CLASS = {\n sm: 'h-9 px-3 text-sm',\n md: 'h-11 px-4 text-base',\n} as const\n</script>\n\n<template>\n <button\n :type=\"type\"\n :disabled=\"disabled || loading\"\n :aria-busy=\"loading\"\n class=\"rounded-card focus-visible:outline-primary inline-flex items-center justify-center gap-2 font-medium transition-transform duration-100 select-none focus-visible:outline-2 focus-visible:outline-offset-2 active:scale-95 disabled:pointer-events-none disabled:opacity-50\"\n :class=\"[VARIANT_CLASS[variant], SIZE_CLASS[size]]\"\n >\n <span\n v-if=\"loading\"\n class=\"size-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n aria-hidden=\"true\"\n />\n <slot />\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst {\n label,\n error = '',\n hint = '',\n type = 'text',\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the input with no accessible name at all.\n */\n labelHidden?: boolean\n type?: 'text' | 'email' | 'password' | 'number'\n}>()\n\nconst model = defineModel<string | undefined>()\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label :for=\"id\" class=\"text-ink text-sm font-medium\" :class=\"labelHidden ? 'sr-only' : ''\">\n {{ label }}\n </label>\n\n <input\n :id=\"id\"\n v-model=\"model\"\n :type=\"type\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n v-bind=\"$attrs\"\n class=\"border-hair bg-surface text-ink rounded-card focus-visible:outline-primary h-11 border px-3 focus-visible:outline-2 focus-visible:outline-offset-1\"\n :class=\"error ? 'border-negative' : ''\"\n />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst {\n label,\n error = '',\n hint = '',\n type = 'text',\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the input with no accessible name at all.\n */\n labelHidden?: boolean\n type?: 'text' | 'email' | 'password' | 'number'\n}>()\n\nconst model = defineModel<string | undefined>()\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label :for=\"id\" class=\"text-ink text-sm font-medium\" :class=\"labelHidden ? 'sr-only' : ''\">\n {{ label }}\n </label>\n\n <input\n :id=\"id\"\n v-model=\"model\"\n :type=\"type\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n v-bind=\"$attrs\"\n class=\"border-hair bg-surface text-ink rounded-card focus-visible:outline-primary h-11 border px-3 focus-visible:outline-2 focus-visible:outline-offset-1\"\n :class=\"error ? 'border-negative' : ''\"\n />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string\n}>()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n lastFocused?.focus()\n lastFocused = null\n setBackgroundInert(false)\n }\n})\n\n/**\n * `inert` takes the whole app out of tab order and pointer events while the\n * sheet is open — a real focus trap without keydown bookkeeping.\n *\n * The sheet itself is teleported to `#sheet-root`, a sibling of `#app`, so it\n * stays interactive.\n */\nfunction setBackgroundInert(isInert: boolean) {\n document.getElementById('app')?.toggleAttribute('inert', isInert)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport to=\"#sheet-root\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel bg-surface relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[28px] shadow-2xl outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <button\n type=\"button\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </button>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity 200ms ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string\n}>()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n lastFocused?.focus()\n lastFocused = null\n setBackgroundInert(false)\n }\n})\n\n/**\n * `inert` takes the whole app out of tab order and pointer events while the\n * sheet is open — a real focus trap without keydown bookkeeping.\n *\n * The sheet itself is teleported to `#sheet-root`, a sibling of `#app`, so it\n * stays interactive.\n */\nfunction setBackgroundInert(isInert: boolean) {\n document.getElementById('app')?.toggleAttribute('inert', isInert)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport to=\"#sheet-root\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel bg-surface relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[28px] shadow-2xl outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <button\n type=\"button\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </button>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity 200ms ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script lang=\"ts\" setup>\nconst { title, description = '' } = defineProps<{\n title: string\n description?: string | undefined\n}>()\n</script>\n\n<template>\n <div class=\"flex flex-col items-center gap-3 px-6 py-10 text-center\">\n <div v-if=\"$slots.icon\" class=\"bg-muted text-primary rounded-card flex size-12 items-center\">\n <slot name=\"icon\" />\n </div>\n\n <h3 class=\"text-ink text-base font-semibold\">{{ title }}</h3>\n <p v-if=\"description\" class=\"text-ink-soft max-w-[36ch] text-sm\">\n {{ description }}\n </p>\n\n <div v-if=\"$slots.action\" class=\"mt-2 flex w-full flex-col gap-2\">\n <slot name=\"action\" />\n </div>\n </div>\n</template>\n\n<style></style>\n","<script lang=\"ts\" setup>\nconst { title, description = '' } = defineProps<{\n title: string\n description?: string | undefined\n}>()\n</script>\n\n<template>\n <div class=\"flex flex-col items-center gap-3 px-6 py-10 text-center\">\n <div v-if=\"$slots.icon\" class=\"bg-muted text-primary rounded-card flex size-12 items-center\">\n <slot name=\"icon\" />\n </div>\n\n <h3 class=\"text-ink text-base font-semibold\">{{ title }}</h3>\n <p v-if=\"description\" class=\"text-ink-soft max-w-[36ch] text-sm\">\n {{ description }}\n </p>\n\n <div v-if=\"$slots.action\" class=\"mt-2 flex w-full flex-col gap-2\">\n <slot name=\"action\" />\n </div>\n </div>\n</template>\n\n<style></style>\n","<script setup lang=\"ts\">\nconst { title } = defineProps<{ title: string }>()\n</script>\n\n<template>\n <header class=\"grid h-12 shrink-0 grid-cols-[2.5rem_1fr_2.5rem] items-center\">\n <div class=\"justify-self-start\"><slot name=\"left\" /></div>\n\n <h1 class=\"text-ink flex min-w-0 justify-center text-base font-semibold tabular-nums\">\n <slot name=\"title\">\n <span class=\"truncate\">{{ title }}</span>\n </slot>\n </h1>\n\n <div class=\"justify-self-end\"><slot name=\"right\" /></div>\n </header>\n</template>\n","<script setup lang=\"ts\">\nconst { title } = defineProps<{ title: string }>()\n</script>\n\n<template>\n <header class=\"grid h-12 shrink-0 grid-cols-[2.5rem_1fr_2.5rem] items-center\">\n <div class=\"justify-self-start\"><slot name=\"left\" /></div>\n\n <h1 class=\"text-ink flex min-w-0 justify-center text-base font-semibold tabular-nums\">\n <slot name=\"title\">\n <span class=\"truncate\">{{ title }}</span>\n </slot>\n </h1>\n\n <div class=\"justify-self-end\"><slot name=\"right\" /></div>\n </header>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A small coloured dot, optionally labelled.\n *\n * Takes the colour as a class rather than a category, so an app can key it off\n * whatever its own domain calls a category — habit kinds, expense types,\n * priorities — without this component knowing about any of them.\n */\nconst { fill, label = '' } = defineProps<{\n /** Background utility for the dot, e.g. `bg-positive`. */\n fill: string\n /** Optional text after the dot. Omit for a bare marker. */\n label?: string\n}>()\n</script>\n\n<template>\n <span class=\"inline-flex items-center gap-1.5\">\n <span class=\"size-2 rounded-full\" :class=\"fill\" />\n <span v-if=\"label\" class=\"text-ink-soft text-xs font-medium\">{{ label }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A small coloured dot, optionally labelled.\n *\n * Takes the colour as a class rather than a category, so an app can key it off\n * whatever its own domain calls a category — habit kinds, expense types,\n * priorities — without this component knowing about any of them.\n */\nconst { fill, label = '' } = defineProps<{\n /** Background utility for the dot, e.g. `bg-positive`. */\n fill: string\n /** Optional text after the dot. Omit for a bare marker. */\n label?: string\n}>()\n</script>\n\n<template>\n <span class=\"inline-flex items-center gap-1.5\">\n <span class=\"size-2 rounded-full\" :class=\"fill\" />\n <span v-if=\"label\" class=\"text-ink-soft text-xs font-medium\">{{ label }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\nimport ToneDot from './ToneDot.vue'\n\n/** The three classes a category needs to colour a heading. */\nexport interface Tone {\n /** Solid background for the dot, e.g. `bg-positive`. */\n fill: string\n /** Tinted surface for the pill, e.g. `bg-positive/5 border-positive/25`. */\n card: string\n /** Foreground that pairs with the surface, e.g. `text-positive`. */\n text: string\n}\n\n/**\n * A pill heading for a group of things.\n *\n * The tone arrives as three class strings rather than a category name: Tailwind\n * reads source files as plain text, so a class assembled at runtime never\n * reaches the stylesheet — the app has to write them out, and it is the app\n * that knows its own categories anyway.\n */\nconst {\n tone,\n label,\n count = 0,\n} = defineProps<{\n tone: Tone\n label: string\n /** Hidden when zero, so an empty group's heading stays quiet. */\n count?: number\n}>()\n</script>\n\n<template>\n <h2 class=\"flex items-center gap-2 self-start rounded-full border px-3 py-1\" :class=\"tone.card\">\n <ToneDot :fill=\"tone.fill\" />\n <span class=\"text-xs font-semibold tracking-wide uppercase\" :class=\"tone.text\">\n {{ label }}\n </span>\n <span v-if=\"count > 0\" class=\"text-ink-soft text-xs tabular-nums\">{{ count }}</span>\n </h2>\n</template>\n","<script setup lang=\"ts\">\nimport ToneDot from './ToneDot.vue'\n\n/** The three classes a category needs to colour a heading. */\nexport interface Tone {\n /** Solid background for the dot, e.g. `bg-positive`. */\n fill: string\n /** Tinted surface for the pill, e.g. `bg-positive/5 border-positive/25`. */\n card: string\n /** Foreground that pairs with the surface, e.g. `text-positive`. */\n text: string\n}\n\n/**\n * A pill heading for a group of things.\n *\n * The tone arrives as three class strings rather than a category name: Tailwind\n * reads source files as plain text, so a class assembled at runtime never\n * reaches the stylesheet — the app has to write them out, and it is the app\n * that knows its own categories anyway.\n */\nconst {\n tone,\n label,\n count = 0,\n} = defineProps<{\n tone: Tone\n label: string\n /** Hidden when zero, so an empty group's heading stays quiet. */\n count?: number\n}>()\n</script>\n\n<template>\n <h2 class=\"flex items-center gap-2 self-start rounded-full border px-3 py-1\" :class=\"tone.card\">\n <ToneDot :fill=\"tone.fill\" />\n <span class=\"text-xs font-semibold tracking-wide uppercase\" :class=\"tone.text\">\n {{ label }}\n </span>\n <span v-if=\"count > 0\" class=\"text-ink-soft text-xs tabular-nums\">{{ count }}</span>\n </h2>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends string | number\">\nimport { useId } from 'vue'\n\n/**\n * A row of mutually exclusive choices.\n *\n * Radio inputs rather than buttons: it is a single choice out of a small set,\n * so arrow-key navigation and the \"one of N selected\" announcement come free.\n */\nconst { options } = defineProps<{\n options: readonly { value: T; label: string }[]\n}>()\n\nconst model = defineModel<T>({ required: true })\n\nconst name = useId()\n</script>\n\n<template>\n <div class=\"bg-muted rounded-card flex w-full gap-1 p-1\">\n <label v-for=\"option in options\" :key=\"String(option.value)\" class=\"flex-1 cursor-pointer\">\n <input v-model=\"model\" type=\"radio\" :value=\"option.value\" :name=\"name\" class=\"sr-only\" />\n <span\n class=\"flex h-10 items-center justify-center rounded-xl px-2 text-sm font-medium transition-colors select-none\"\n :class=\"model === option.value ? 'bg-surface text-ink shadow-sm' : 'text-ink-soft'\"\n >\n {{ option.label }}\n </span>\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends string | number\">\nimport { useId } from 'vue'\n\n/**\n * A row of mutually exclusive choices.\n *\n * Radio inputs rather than buttons: it is a single choice out of a small set,\n * so arrow-key navigation and the \"one of N selected\" announcement come free.\n */\nconst { options } = defineProps<{\n options: readonly { value: T; label: string }[]\n}>()\n\nconst model = defineModel<T>({ required: true })\n\nconst name = useId()\n</script>\n\n<template>\n <div class=\"bg-muted rounded-card flex w-full gap-1 p-1\">\n <label v-for=\"option in options\" :key=\"String(option.value)\" class=\"flex-1 cursor-pointer\">\n <input v-model=\"model\" type=\"radio\" :value=\"option.value\" :name=\"name\" class=\"sr-only\" />\n <span\n class=\"flex h-10 items-center justify-center rounded-xl px-2 text-sm font-medium transition-colors select-none\"\n :class=\"model === option.value ? 'bg-surface text-ink shadow-sm' : 'text-ink-soft'\"\n >\n {{ option.label }}\n </span>\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\">\ndefineProps<{ title: string }>()\n</script>\n\n<template>\n <section class=\"flex flex-col gap-2\">\n <h2 class=\"text-ink-soft px-1 text-xs font-semibold tracking-wide uppercase\">{{ title }}</h2>\n\n <!-- One card per group, rows divided by hairlines. Loose fields floating on\n the page gave no sense of what belonged with what. -->\n <div class=\"border-hair bg-surface rounded-card divide-hair divide-y overflow-hidden border\">\n <slot />\n </div>\n </section>\n</template>\n","<script setup lang=\"ts\">\ndefineProps<{ title: string }>()\n</script>\n\n<template>\n <section class=\"flex flex-col gap-2\">\n <h2 class=\"text-ink-soft px-1 text-xs font-semibold tracking-wide uppercase\">{{ title }}</h2>\n\n <!-- One card per group, rows divided by hairlines. Loose fields floating on\n the page gave no sense of what belonged with what. -->\n <div class=\"border-hair bg-surface rounded-card divide-hair divide-y overflow-hidden border\">\n <slot />\n </div>\n </section>\n</template>\n","<script setup lang=\"ts\">\nimport { ChevronRight } from 'lucide-vue-next'\nimport type { Component } from 'vue'\n\n/**\n * One line in a settings card.\n *\n * `as` decides the element: a row that navigates has to be a button, and a row\n * that merely holds a control must not be, or the control becomes unreachable.\n */\nconst {\n label,\n description = '',\n icon = undefined,\n interactive = false,\n stacked = false,\n} = defineProps<{\n label: string\n description?: string\n icon?: Component | undefined\n /** Renders the row as a button with a chevron. */\n interactive?: boolean\n /** Puts the control on its own line below the label, for wide controls. */\n stacked?: boolean\n}>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <component\n :is=\"interactive ? 'button' : 'div'\"\n :type=\"interactive ? 'button' : undefined\"\n class=\"flex w-full items-center gap-3 px-4 py-3 text-left\"\n :class=\"[\n interactive ? 'hover:bg-muted/60 transition-colors active:scale-[0.99]' : '',\n stacked ? 'flex-col items-stretch gap-3' : '',\n ]\"\n @click=\"interactive && emit('click')\"\n >\n <div class=\"flex items-center gap-3\">\n <span\n v-if=\"icon\"\n class=\"bg-muted text-ink-soft flex size-9 shrink-0 items-center justify-center rounded-xl\"\n aria-hidden=\"true\"\n >\n <component :is=\"icon\" class=\"size-[18px]\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <p class=\"text-ink text-sm font-medium\">{{ label }}</p>\n <p v-if=\"description\" class=\"text-ink-soft mt-0.5 text-xs leading-snug\">\n {{ description }}\n </p>\n </div>\n\n <div v-if=\"!stacked\" class=\"shrink-0\"><slot /></div>\n\n <ChevronRight v-if=\"interactive\" class=\"text-ink-soft size-4 shrink-0\" aria-hidden=\"true\" />\n </div>\n\n <div v-if=\"stacked\"><slot /></div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { ChevronRight } from 'lucide-vue-next'\nimport type { Component } from 'vue'\n\n/**\n * One line in a settings card.\n *\n * `as` decides the element: a row that navigates has to be a button, and a row\n * that merely holds a control must not be, or the control becomes unreachable.\n */\nconst {\n label,\n description = '',\n icon = undefined,\n interactive = false,\n stacked = false,\n} = defineProps<{\n label: string\n description?: string\n icon?: Component | undefined\n /** Renders the row as a button with a chevron. */\n interactive?: boolean\n /** Puts the control on its own line below the label, for wide controls. */\n stacked?: boolean\n}>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <component\n :is=\"interactive ? 'button' : 'div'\"\n :type=\"interactive ? 'button' : undefined\"\n class=\"flex w-full items-center gap-3 px-4 py-3 text-left\"\n :class=\"[\n interactive ? 'hover:bg-muted/60 transition-colors active:scale-[0.99]' : '',\n stacked ? 'flex-col items-stretch gap-3' : '',\n ]\"\n @click=\"interactive && emit('click')\"\n >\n <div class=\"flex items-center gap-3\">\n <span\n v-if=\"icon\"\n class=\"bg-muted text-ink-soft flex size-9 shrink-0 items-center justify-center rounded-xl\"\n aria-hidden=\"true\"\n >\n <component :is=\"icon\" class=\"size-[18px]\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <p class=\"text-ink text-sm font-medium\">{{ label }}</p>\n <p v-if=\"description\" class=\"text-ink-soft mt-0.5 text-xs leading-snug\">\n {{ description }}\n </p>\n </div>\n\n <div v-if=\"!stacked\" class=\"shrink-0\"><slot /></div>\n\n <ChevronRight v-if=\"interactive\" class=\"text-ink-soft size-4 shrink-0\" aria-hidden=\"true\" />\n </div>\n\n <div v-if=\"stacked\"><slot /></div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nconst {\n rows = 3,\n rowHeight = 'h-14',\n label = 'Loading…',\n} = defineProps<{\n rows?: number\n rowHeight?: string\n label?: string\n}>()\n</script>\n\n<template>\n <div role=\"status\" class=\"flex flex-col gap-1\">\n <span class=\"sr-only\">{{ label }}</span>\n\n <div\n v-for=\"row in rows\"\n :key=\"row\"\n class=\"bg-muted rounded-card animate-pulse\"\n :class=\"rowHeight\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nconst {\n rows = 3,\n rowHeight = 'h-14',\n label = 'Loading…',\n} = defineProps<{\n rows?: number\n rowHeight?: string\n label?: string\n}>()\n</script>\n\n<template>\n <div role=\"status\" class=\"flex flex-col gap-1\">\n <span class=\"sr-only\">{{ label }}</span>\n\n <div\n v-for=\"row in rows\"\n :key=\"row\"\n class=\"bg-muted rounded-card animate-pulse\"\n :class=\"rowHeight\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { ArrowDown, ArrowRight, ArrowUp } from 'lucide-vue-next'\n\nconst {\n value,\n label,\n trend = null,\n} = defineProps<{\n value: string\n label: string\n trend?: 'up' | 'down' | 'flat' | null\n}>()\n\nconst TREND_ICON = { up: ArrowUp, down: ArrowDown, flat: ArrowRight } as const\n</script>\n\n<template>\n <div class=\"border-hair rounded-card flex flex-1 flex-col gap-0.5 border p-3\">\n <div class=\"flex items-baseline gap-1\">\n <span class=\"text-ink text-xl font-semibold tabular-nums\">{{ value }}</span>\n <component :is=\"TREND_ICON[trend]\" v-if=\"trend\" class=\"text-ink-soft size-3\" />\n </div>\n <span class=\"text-ink-soft text-xs\">{{ label }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { ArrowDown, ArrowRight, ArrowUp } from 'lucide-vue-next'\n\nconst {\n value,\n label,\n trend = null,\n} = defineProps<{\n value: string\n label: string\n trend?: 'up' | 'down' | 'flat' | null\n}>()\n\nconst TREND_ICON = { up: ArrowUp, down: ArrowDown, flat: ArrowRight } as const\n</script>\n\n<template>\n <div class=\"border-hair rounded-card flex flex-1 flex-col gap-0.5 border p-3\">\n <div class=\"flex items-baseline gap-1\">\n <span class=\"text-ink text-xl font-semibold tabular-nums\">{{ value }}</span>\n <component :is=\"TREND_ICON[trend]\" v-if=\"trend\" class=\"text-ink-soft size-3\" />\n </div>\n <span class=\"text-ink-soft text-xs\">{{ label }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"L extends string\">\n/**\n * A flat language switcher for screens with no Settings behind them.\n *\n * The list and the labels are props: only the app knows which languages it\n * ships, and endonyms — each language written in itself — are what make the\n * right option legible to someone who cannot read the current interface.\n */\nconst {\n locales,\n labels,\n label = '',\n} = defineProps<{\n locales: readonly L[]\n /** Endonyms, e.g. `{ en: 'English', tr: 'Türkçe' }`. */\n labels: Record<L, string>\n /** Accessible name for the group. */\n label?: string\n}>()\n\n/**\n * Two-way bound rather than taking the runtime's ref as a prop: props are not\n * unwrapped in a template and cannot be assigned to, so the ref would compare\n * against itself and the click handler would not compile.\n */\nconst preference = defineModel<'system' | L>({ required: true })\n</script>\n\n<template>\n <nav class=\"flex flex-wrap items-center justify-center gap-1\" :aria-label=\"label || undefined\">\n <button\n v-for=\"locale in locales\"\n :key=\"locale\"\n type=\"button\"\n :lang=\"locale\"\n class=\"rounded-full px-2.5 py-1.5 text-xs transition-colors\"\n :class=\"\n preference === locale ? 'bg-muted text-ink font-semibold' : 'text-ink-soft hover:text-ink'\n \"\n :aria-pressed=\"preference === locale\"\n @click=\"preference = locale\"\n >\n {{ labels[locale] }}\n </button>\n </nav>\n</template>\n","<script setup lang=\"ts\" generic=\"L extends string\">\n/**\n * A flat language switcher for screens with no Settings behind them.\n *\n * The list and the labels are props: only the app knows which languages it\n * ships, and endonyms — each language written in itself — are what make the\n * right option legible to someone who cannot read the current interface.\n */\nconst {\n locales,\n labels,\n label = '',\n} = defineProps<{\n locales: readonly L[]\n /** Endonyms, e.g. `{ en: 'English', tr: 'Türkçe' }`. */\n labels: Record<L, string>\n /** Accessible name for the group. */\n label?: string\n}>()\n\n/**\n * Two-way bound rather than taking the runtime's ref as a prop: props are not\n * unwrapped in a template and cannot be assigned to, so the ref would compare\n * against itself and the click handler would not compile.\n */\nconst preference = defineModel<'system' | L>({ required: true })\n</script>\n\n<template>\n <nav class=\"flex flex-wrap items-center justify-center gap-1\" :aria-label=\"label || undefined\">\n <button\n v-for=\"locale in locales\"\n :key=\"locale\"\n type=\"button\"\n :lang=\"locale\"\n class=\"rounded-full px-2.5 py-1.5 text-xs transition-colors\"\n :class=\"\n preference === locale ? 'bg-muted text-ink font-semibold' : 'text-ink-soft hover:text-ink'\n \"\n :aria-pressed=\"preference === locale\"\n @click=\"preference = locale\"\n >\n {{ labels[locale] }}\n </button>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nconst { label } = defineProps<{ label: string }>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"border-hair bg-surface text-ink rounded-card hover:bg-muted flex h-11 w-full items-center justify-center gap-2 border text-sm font-medium transition-colors active:scale-95\"\n @click=\"emit('click')\"\n >\n <!-- Google asks for its own mark, so it is inlined rather than themed. -->\n <svg class=\"size-4\" viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <path\n fill=\"#EA4335\"\n d=\"M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.7 30.2.5 24 .5 14.6.5 6.5 5.9 2.6 13.7l7.8 6.1C12.3 13.7 17.7 9.5 24 9.5z\"\n />\n <path\n fill=\"#4285F4\"\n d=\"M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.6-4.9 7.3l7.6 5.9c4.4-4.1 7.1-10.2 7.1-17.7z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M10.4 28.2a14.6 14.6 0 0 1 0-8.4l-7.8-6.1a24 24 0 0 0 0 20.6l7.8-6.1z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M24 47.5c6.2 0 11.5-2 15.4-5.6l-7.6-5.9c-2.1 1.4-4.8 2.3-7.8 2.3-6.3 0-11.7-4.2-13.6-10l-7.8 6.1C6.5 42.1 14.6 47.5 24 47.5z\"\n />\n </svg>\n {{ label }}\n </button>\n</template>\n","<script setup lang=\"ts\">\nconst { label } = defineProps<{ label: string }>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"border-hair bg-surface text-ink rounded-card hover:bg-muted flex h-11 w-full items-center justify-center gap-2 border text-sm font-medium transition-colors active:scale-95\"\n @click=\"emit('click')\"\n >\n <!-- Google asks for its own mark, so it is inlined rather than themed. -->\n <svg class=\"size-4\" viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <path\n fill=\"#EA4335\"\n d=\"M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.7 30.2.5 24 .5 14.6.5 6.5 5.9 2.6 13.7l7.8 6.1C12.3 13.7 17.7 9.5 24 9.5z\"\n />\n <path\n fill=\"#4285F4\"\n d=\"M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.6-4.9 7.3l7.6 5.9c4.4-4.1 7.1-10.2 7.1-17.7z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M10.4 28.2a14.6 14.6 0 0 1 0-8.4l-7.8-6.1a24 24 0 0 0 0 20.6l7.8-6.1z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M24 47.5c6.2 0 11.5-2 15.4-5.6l-7.6-5.9c-2.1 1.4-4.8 2.3-7.8 2.3-6.3 0-11.7-4.2-13.6-10l-7.8 6.1C6.5 42.1 14.6 47.5 24 47.5z\"\n />\n </svg>\n {{ label }}\n </button>\n</template>\n","<script setup lang=\"ts\" generic=\"K extends string\">\nimport { RouterLink } from 'vue-router'\nimport type { Component } from 'vue'\n\nimport { tapFeedback } from '../utils/haptics'\n\nexport interface TabItem<K extends string> {\n /** Identity, compared against `active`. */\n key: K\n /** Router destination. */\n to: string\n /** Text under the icon. Already translated. */\n label: string\n icon: Component\n}\n\n/**\n * The floating bottom bar.\n *\n * Items and the active key are props: the package has no opinion about how an\n * app names its screens, and reading `route.meta` here would force one.\n */\nconst {\n items,\n active,\n label = '',\n} = defineProps<{\n items: readonly TabItem<K>[]\n /** Which item is current. Usually from `route.meta`. */\n active?: K | undefined\n /** Accessible name for the navigation landmark. */\n label?: string\n}>()\n</script>\n\n<template>\n <header class=\"tab-bar\">\n <nav class=\"tab-bar-inner\" :aria-label=\"label || undefined\">\n <RouterLink\n v-for=\"item in items\"\n :key=\"item.key\"\n :to=\"item.to\"\n class=\"tab-link\"\n :class=\"{ 'is-active': item.key === active }\"\n :aria-current=\"item.key === active ? 'page' : undefined\"\n @click=\"tapFeedback()\"\n >\n <span class=\"tab-icon-slot\">\n <component :is=\"item.icon\" class=\"tab-icon\" />\n </span>\n <span class=\"tab-label\">{{ item.label }}</span>\n </RouterLink>\n </nav>\n </header>\n</template>\n\n<style scoped>\n@reference \"../styles/_reference.css\";\n\n/* absolute, not fixed: the bar hangs inside the app shell. Fixed would pin it\n to the browser window, which on a desktop is nowhere near the app. */\n.tab-bar {\n @apply absolute left-1/2 z-40 w-full max-w-[360px] -translate-x-1/2 px-4;\n bottom: calc(1rem + env(safe-area-inset-bottom, 0px));\n}\n\n.tab-bar-inner {\n @apply border-hair bg-surface/85 flex items-center justify-between gap-1 border p-1.5 shadow-lg backdrop-blur-md;\n border-radius: var(--radius-shell);\n}\n\n.tab-link {\n @apply text-ink-soft flex min-h-[52px] flex-1 cursor-pointer flex-col items-center justify-center gap-1 py-1.5;\n border-radius: calc(var(--radius-shell) - 6px);\n /* Only the icon reacts to a press. Scaling the whole link drags the label and\n the pill with it, which reads as the bar wobbling. */\n transition: color 200ms ease;\n}\n\n.tab-link:hover {\n @apply text-ink;\n}\n\n/* The pill sits behind the icon rather than the link, so the active tab grows a\n marker instead of the row changing shape. */\n.tab-icon-slot {\n @apply flex h-7 w-12 items-center justify-center rounded-full transition-all duration-200 ease-out;\n}\n\n.tab-link:active .tab-icon-slot {\n transform: scale(0.88);\n}\n\n.is-active {\n @apply text-primary;\n}\n\n.is-active .tab-icon-slot {\n @apply bg-muted;\n}\n\n.tab-icon {\n @apply size-[18px] stroke-2 transition-transform duration-200;\n}\n\n.is-active .tab-icon {\n @apply scale-110 stroke-[2.5px];\n}\n\n.tab-label {\n @apply text-[10px] leading-none font-medium;\n}\n\n.is-active .tab-label {\n @apply font-semibold;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tab-icon-slot,\n .tab-icon {\n transition: none;\n }\n .tab-link:active .tab-icon-slot {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\" generic=\"K extends string\">\nimport { RouterLink } from 'vue-router'\nimport type { Component } from 'vue'\n\nimport { tapFeedback } from '../utils/haptics'\n\nexport interface TabItem<K extends string> {\n /** Identity, compared against `active`. */\n key: K\n /** Router destination. */\n to: string\n /** Text under the icon. Already translated. */\n label: string\n icon: Component\n}\n\n/**\n * The floating bottom bar.\n *\n * Items and the active key are props: the package has no opinion about how an\n * app names its screens, and reading `route.meta` here would force one.\n */\nconst {\n items,\n active,\n label = '',\n} = defineProps<{\n items: readonly TabItem<K>[]\n /** Which item is current. Usually from `route.meta`. */\n active?: K | undefined\n /** Accessible name for the navigation landmark. */\n label?: string\n}>()\n</script>\n\n<template>\n <header class=\"tab-bar\">\n <nav class=\"tab-bar-inner\" :aria-label=\"label || undefined\">\n <RouterLink\n v-for=\"item in items\"\n :key=\"item.key\"\n :to=\"item.to\"\n class=\"tab-link\"\n :class=\"{ 'is-active': item.key === active }\"\n :aria-current=\"item.key === active ? 'page' : undefined\"\n @click=\"tapFeedback()\"\n >\n <span class=\"tab-icon-slot\">\n <component :is=\"item.icon\" class=\"tab-icon\" />\n </span>\n <span class=\"tab-label\">{{ item.label }}</span>\n </RouterLink>\n </nav>\n </header>\n</template>\n\n<style scoped>\n@reference \"../styles/_reference.css\";\n\n/* absolute, not fixed: the bar hangs inside the app shell. Fixed would pin it\n to the browser window, which on a desktop is nowhere near the app. */\n.tab-bar {\n @apply absolute left-1/2 z-40 w-full max-w-[360px] -translate-x-1/2 px-4;\n bottom: calc(1rem + env(safe-area-inset-bottom, 0px));\n}\n\n.tab-bar-inner {\n @apply border-hair bg-surface/85 flex items-center justify-between gap-1 border p-1.5 shadow-lg backdrop-blur-md;\n border-radius: var(--radius-shell);\n}\n\n.tab-link {\n @apply text-ink-soft flex min-h-[52px] flex-1 cursor-pointer flex-col items-center justify-center gap-1 py-1.5;\n border-radius: calc(var(--radius-shell) - 6px);\n /* Only the icon reacts to a press. Scaling the whole link drags the label and\n the pill with it, which reads as the bar wobbling. */\n transition: color 200ms ease;\n}\n\n.tab-link:hover {\n @apply text-ink;\n}\n\n/* The pill sits behind the icon rather than the link, so the active tab grows a\n marker instead of the row changing shape. */\n.tab-icon-slot {\n @apply flex h-7 w-12 items-center justify-center rounded-full transition-all duration-200 ease-out;\n}\n\n.tab-link:active .tab-icon-slot {\n transform: scale(0.88);\n}\n\n.is-active {\n @apply text-primary;\n}\n\n.is-active .tab-icon-slot {\n @apply bg-muted;\n}\n\n.tab-icon {\n @apply size-[18px] stroke-2 transition-transform duration-200;\n}\n\n.is-active .tab-icon {\n @apply scale-110 stroke-[2.5px];\n}\n\n.tab-label {\n @apply text-[10px] leading-none font-medium;\n}\n\n.is-active .tab-label {\n @apply font-semibold;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tab-icon-slot,\n .tab-icon {\n transition: none;\n }\n .tab-link:active .tab-icon-slot {\n transform: none;\n }\n}\n</style>\n","import { computed, ref, watchEffect } from 'vue'\nimport { createI18n } from 'vue-i18n'\n\nimport { setFormatLocale } from '../utils/format'\n\n/** What the user picked. `system` re-reads the browser on every launch. */\nexport type LocalePreference<L extends string> = 'system' | L\n\nexport interface I18nRuntimeOptions<L extends string, Schema> {\n /** Languages the app ships, in no particular order. */\n locales: readonly L[]\n /** The one that is always loaded, and the fallback when a load fails. */\n fallback: L\n /**\n * BCP 47 tag per locale, for `Intl`.\n *\n * Message lookup only needs the base language, but dates and numbers need a\n * region to be right — `zh` alone would leave the formatter to guess.\n */\n intlTags: Record<L, string>\n /** The fallback's messages, bundled. */\n messages: Schema\n /** The rest, fetched only when they are the one in use. */\n loaders?: Partial<Record<L, () => Promise<{ default: Schema }>>>\n /** Where the choice is stored. Namespace it per app. */\n storageKey?: string\n}\n\n/**\n * Builds an i18n runtime around an app's own catalogue.\n *\n * A factory rather than a module singleton because the schema is the app's:\n * typing every locale as `typeof en` is what makes a missing key a build error,\n * and this package has no `en` of its own to type against.\n *\n * @example\n * ```ts\n * export const { i18n, t, useLocalePreference, loadActiveLocale } =\n * createI18nRuntime({\n * locales: ['en', 'tr'] as const,\n * fallback: 'en',\n * intlTags: { en: 'en-GB', tr: 'tr-TR' },\n * messages: en,\n * loaders: { tr: () => import('./locales/tr') },\n * storageKey: 'myapp-locale',\n * })\n * ```\n */\nexport function createI18nRuntime<L extends string, Schema extends Record<string, unknown>>(\n options: I18nRuntimeOptions<L, Schema>,\n) {\n const { locales, fallback, intlTags, messages, storageKey = 'rei-locale' } = options\n\n // Typed rather than defaulted to `{}`, which erases the locale keys and makes\n // `loaders[locale]` an index into an empty object.\n const loaders: Partial<Record<L, () => Promise<{ default: Schema }>>> = options.loaders ?? {}\n\n function isSupported(value: string): value is L {\n return (locales as readonly string[]).includes(value)\n }\n\n /**\n * First browser language the app can actually speak.\n *\n * `navigator.languages` is ordered by the user's own preference, so the first\n * match is the best one — not simply the first entry.\n */\n function detectSystemLocale(): L {\n for (const tag of navigator.languages ?? [navigator.language]) {\n const base = tag.split('-')[0]?.toLowerCase()\n if (base && isSupported(base)) return base\n }\n\n return fallback\n }\n\n function readStored(): LocalePreference<L> {\n try {\n const stored = localStorage.getItem(storageKey)\n if (stored === 'system' || (stored && isSupported(stored))) return stored\n } catch {\n // Storage blocked; fall through to the system language.\n }\n\n return 'system'\n }\n\n const preference = ref<LocalePreference<L>>(readStored())\n\n const activeLocale = computed<L>(() =>\n preference.value === 'system' ? detectSystemLocale() : (preference.value as L),\n )\n\n const intlLocale = computed(() => intlTags[activeLocale.value])\n\n // Only the fallback at construction; the rest arrive through\n // setLocaleMessage.\n const initial = { [fallback]: messages } as Record<string, Record<string, unknown>>\n\n const i18n = createI18n({\n legacy: false,\n locale: activeLocale.value as string,\n fallbackLocale: fallback as string,\n messages: initial,\n } as unknown as Parameters<typeof createI18n>[0])\n\n /**\n * A narrow view of the instance.\n *\n * vue-i18n infers its own generics from the messages it is handed, which\n * fights a runtime that is generic over the app's schema. Casting once, here,\n * keeps that fight out of every call site — and the surface below is the\n * whole of what this runtime uses.\n */\n const core = i18n.global as unknown as {\n locale: { value: string }\n setLocaleMessage: (locale: string, messages: Schema) => void\n t: (key: string, named?: Record<string, unknown>) => string\n }\n\n const loaded = new Set<L>([fallback])\n\n /**\n * Makes sure a locale's messages are in place before it becomes active.\n *\n * Awaited rather than fired and forgotten: setting the locale first paints one\n * frame of the fallback at every other user, which is the flash a fallback\n * exists to prevent, not cause.\n */\n async function ensureMessages(locale: L): Promise<void> {\n if (loaded.has(locale)) return\n\n const load = loaders[locale]\n if (!load) return\n\n try {\n const module = await load()\n core.setLocaleMessage(locale, module.default)\n loaded.add(locale)\n } catch {\n // Offline, or a stale chunk after a deploy. The fallback is loaded and\n // will carry the UI, which beats a blank screen.\n }\n }\n\n /** Loads whatever the stored preference resolves to. Call before mounting. */\n function loadActiveLocale(): Promise<void> {\n return ensureMessages(activeLocale.value)\n }\n\n // Keeps vue-i18n, `Intl` and the document in step. `lang` matters beyond\n // tidiness: it drives hyphenation, font fallback and screen readers.\n watchEffect(() => {\n core.locale.value = activeLocale.value\n setFormatLocale(intlLocale.value)\n document.documentElement.lang = activeLocale.value\n })\n\n /** Read and write the language preference. */\n function useLocalePreference() {\n return computed<LocalePreference<L>>({\n get: () => preference.value,\n set: (next) => {\n const resolved = next === 'system' ? detectSystemLocale() : (next as L)\n\n // Messages first, then the switch — the other order shows the fallback\n // for a frame on the way to the language the user just picked.\n void ensureMessages(resolved).then(() => {\n preference.value = next\n })\n\n try {\n localStorage.setItem(storageKey, next)\n } catch {\n // Storage blocked; the choice lasts for this session only.\n }\n },\n })\n }\n\n return {\n i18n,\n /** `t` for code outside a component. Tracks the locale inside a computed. */\n t: core.t,\n activeLocale,\n intlLocale,\n ensureMessages,\n loadActiveLocale,\n useLocalePreference,\n }\n}\n","/**\n * rei-kit — the layer every app starts from.\n *\n * Everything here is free of any backend, router or i18n choice. Components\n * take strings rather than calling a translator, and utilities take the clock\n * rather than reading it, so nothing in this package can force a decision on\n * the app that installs it.\n *\n * @see https://github.com/ramazandogna/rei-kit\n */\n\nexport const VERSION = '0.0.0'\n\n// ── Utilities ──────────────────────────────────────────────────────────────\nexport {\n addDays,\n eachDayOfYear,\n fromDateKey,\n lastNDays,\n leadingBlanks,\n startOfWeek,\n toDateKey,\n todayKey,\n} from './utils/date'\nexport type { WeekStart } from './utils/date'\n\nexport { formatDate, setFormatLocale } from './utils/format'\nexport { relativeDayLabel } from './utils/day-label'\nexport type { DayLabels } from './utils/day-label'\n\nexport { downloadJson } from './utils/download'\nexport { safeRedirect } from './utils/redirect'\nexport type { QueryValue } from './utils/redirect'\nexport { tapFeedback } from './utils/haptics'\nexport { isApplePortable, isInstalled, needsIosInstall } from './utils/platform'\n\nexport { AppError, registerErrorMapper, toAppError } from './utils/app-error'\nexport type { AppErrorKind, ErrorMapper } from './utils/app-error'\n\n// ── Composables ────────────────────────────────────────────────────────────\nexport {\n applyTheme,\n isThemePreference,\n readStoredTheme,\n setThemeStorageKey,\n useTheme,\n} from './composables/use-theme'\nexport type { ThemePreference } from './composables/use-theme'\n\nexport { useToday } from './composables/use-today'\nexport { useOnline } from './composables/use-online'\nexport { useDebouncedCallback } from './composables/use-debounced-callback'\nexport { useDragScroll } from './composables/use-drag-scroll'\nexport { useVisualViewport } from './composables/use-visual-viewport'\nexport type { VisualViewportRect } from './composables/use-visual-viewport'\n\n// ── Components ─────────────────────────────────────────────────────────────\nexport { default as BaseButton } from './components/BaseButton.vue'\nexport { default as BaseInput } from './components/BaseInput.vue'\nexport { default as BaseSheet } from './components/BaseSheet.vue'\nexport { default as EmptyState } from './components/EmptyState.vue'\nexport { default as PageHeader } from './components/PageHeader.vue'\nexport { default as SectionHeading } from './components/SectionHeading.vue'\nexport { default as SegmentedControl } from './components/SegmentedControl.vue'\nexport { default as SettingsGroup } from './components/SettingsGroup.vue'\nexport { default as SettingsRow } from './components/SettingsRow.vue'\nexport { default as SkeletonList } from './components/SkeletonList.vue'\nexport { default as StatCard } from './components/StatCard.vue'\nexport { default as ToneDot } from './components/ToneDot.vue'\nexport type { Tone } from './components/SectionHeading.vue'\nexport { default as LocaleLinks } from './components/LocaleLinks.vue'\nexport { default as GoogleButton } from './components/GoogleButton.vue'\nexport { default as TabBar } from './components/TabBar.vue'\nexport type { TabItem } from './components/TabBar.vue'\n\n// ── i18n ───────────────────────────────────────────────────────────────────\nexport { createI18nRuntime } from './i18n/runtime'\nexport type { I18nRuntimeOptions, LocalePreference } from './i18n/runtime'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["$attrs","$slots"],"sources":["../src/utils/date.ts","../src/utils/format.ts","../src/utils/day-label.ts","../src/utils/download.ts","../src/utils/redirect.ts","../src/utils/haptics.ts","../src/utils/platform.ts","../src/composables/use-theme.ts","../src/composables/use-today.ts","../src/composables/use-online.ts","../src/composables/use-debounced-callback.ts","../src/composables/use-drag-scroll.ts","../src/composables/use-visual-viewport.ts","../src/components/BaseButton.vue","../src/components/BaseButton.vue","../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/components/BaseSheet.vue","../src/components/BaseSheet.vue","../src/components/EmptyState.vue","../src/components/EmptyState.vue","../src/components/PageHeader.vue","../src/components/PageHeader.vue","../src/components/ToneDot.vue","../src/components/ToneDot.vue","../src/components/SectionHeading.vue","../src/components/SectionHeading.vue","../src/components/SegmentedControl.vue","../src/components/SegmentedControl.vue","../src/components/SettingsGroup.vue","../src/components/SettingsGroup.vue","../src/components/SettingsRow.vue","../src/components/SettingsRow.vue","../src/components/SkeletonList.vue","../src/components/SkeletonList.vue","../src/components/StatCard.vue","../src/components/StatCard.vue","../src/components/LocaleLinks.vue","../src/components/LocaleLinks.vue","../src/components/GoogleButton.vue","../src/components/GoogleButton.vue","../src/components/TabBar.vue","../src/components/TabBar.vue","../src/i18n/runtime.ts","../src/index.ts"],"sourcesContent":["/**\n * Local calendar-day helpers.\n *\n * Every function is pure and works on `YYYY-MM-DD` keys, the same shape as the\n * `date` columns in Postgres. Nothing here calls `toISOString`: that converts to\n * UTC, so in a UTC+9 timezone every entry made between midnight and 09:00 would\n * be written to the previous day.\n */\n\n/**\n * Formats a `Date` as a local `YYYY-MM-DD` key.\n *\n * @param date - Any `Date`; only its local year, month and day are read.\n * @returns The calendar day in the runtime's own timezone.\n *\n * @example\n * ```ts\n * // 2026-08-23 01:30 in Tokyo\n * toDateKey(new Date()) // '2026-08-23'\n * new Date().toISOString() // '2026-08-22T16:30…' ← the bug\n * ```\n */\nexport function toDateKey(date: Date): string {\n const year = String(date.getFullYear()).padStart(4, '0')\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n\n return `${year}-${month}-${day}`\n}\n\n/** Today's key in the user's own timezone. */\nexport function todayKey(): string {\n return toDateKey(new Date())\n}\n\n/**\n * Parses a `YYYY-MM-DD` key into a `Date` at local midnight.\n *\n * @param key - A key produced by {@link toDateKey}.\n * @returns Local midnight of that calendar day.\n * @throws If the key is not three numeric parts.\n *\n * @example\n * ```ts\n * fromDateKey('2026-08-23') // local midnight, correct\n * new Date('2026-08-23') // UTC midnight — shifts a day in some zones\n * ```\n */\nexport function fromDateKey(key: string): Date {\n const [year, month, day] = key.split('-').map(Number)\n\n if (year === undefined || month === undefined || day === undefined) {\n throw new Error(`Invalid date key: ${key}`)\n }\n\n return new Date(year, month - 1, day)\n}\n\n/**\n * Shifts a date key by whole calendar days.\n *\n * Uses `setDate`, which is calendar-aware: it rolls over month and year ends,\n * and stays correct across daylight-saving transitions. Adding\n * `days * 86_400_000` milliseconds would not — a DST day is 23 or 25 hours long.\n *\n * @param key - Starting `YYYY-MM-DD` key.\n * @param days - Days to add; negative goes back.\n * @returns The resulting key.\n *\n * @example\n * ```ts\n * addDays('2026-01-31', 1) // '2026-02-01'\n * addDays('2026-01-01', -1) // '2025-12-31'\n * addDays('2028-02-28', 1) // '2028-02-29' — leap year\n * ```\n */\nexport function addDays(key: string, days: number): string {\n const date = fromDateKey(key)\n date.setDate(date.getDate() + days)\n\n return toDateKey(date)\n}\n\n/**\n * The last `count` days ending today, oldest first.\n *\n * `today` is a parameter so the function stays pure and testable; call sites\n * normally omit it.\n *\n * @param count - How many days to return, including `today`.\n * @param today - End of the range. Defaults to the real today.\n * @returns Keys in ascending order.\n *\n * @example\n * ```ts\n * lastNDays(3, '2026-08-23') // ['2026-08-21', '2026-08-22', '2026-08-23']\n * ```\n */\nexport function lastNDays(count: number, today: string = todayKey()): string[] {\n const keys: string[] = []\n\n for (let offset = count - 1; offset >= 0; offset -= 1) {\n keys.push(addDays(today, -offset))\n }\n\n return keys\n}\n\n/** 0 = week starts on Sunday, 1 = on Monday. Mirrors `profiles.week_starts_on`. */\nexport type WeekStart = 0 | 1\n\n/**\n * The first day of the week containing `key`.\n *\n * The user's preference is a parameter, not a module-level setting: changing it\n * in Profile has to re-render the week grid and the year heatmap immediately,\n * and a global would make that a hidden dependency.\n *\n * @param key - Any day in the week.\n * @param weekStartsOn - 0 for Sunday, 1 for Monday.\n * @returns Key of that week's first day.\n *\n * @example\n * ```ts\n * // 2026-08-23 is a Sunday\n * startOfWeek('2026-08-23', 1) // '2026-08-17' — previous Monday\n * startOfWeek('2026-08-23', 0) // '2026-08-23' — already Sunday\n * ```\n */\nexport function startOfWeek(key: string, weekStartsOn: WeekStart): string {\n const weekday = fromDateKey(key).getDay()\n const offset = (weekday - weekStartsOn + 7) % 7\n\n return addDays(key, -offset)\n}\n\n/**\n * Every day of a calendar year, in order.\n *\n * Leap years fall out of the loop for free: it walks day by day until the year\n * rolls over, so February 29 is included when it exists.\n *\n * @param year - Four-digit year.\n * @returns 365 or 366 keys, oldest first.\n */\nexport function eachDayOfYear(year: number): string[] {\n const keys: string[] = []\n const date = new Date(year, 0, 1)\n\n while (date.getFullYear() === year) {\n keys.push(toDateKey(date))\n date.setDate(date.getDate() + 1)\n }\n\n return keys\n}\n\n/**\n * Empty cells before a block's first day in a seven-row column grid.\n *\n * The grid fills column by column, so the first column is only partly used\n * unless the block starts exactly on the week's first day. An off-by-one here\n * shifts the whole block by a row, so this is unit tested.\n *\n * @param firstDayKey - First day of the block, e.g. `'2026-02-01'`.\n * @param weekStartsOn - 0 for Sunday, 1 for Monday.\n * @returns 0-6 blank cells.\n *\n * @example\n * ```ts\n * leadingBlanks('2026-01-01', 1) // 3 — a Thursday, Mon-Wed are blank\n * leadingBlanks('2024-01-01', 1) // 0 — a Monday\n * ```\n */\nexport function leadingBlanks(firstDayKey: string, weekStartsOn: WeekStart): number {\n return (fromDateKey(firstDayKey).getDay() - weekStartsOn + 7) % 7\n}\n","import { ref } from 'vue'\n\n/**\n * The locale `Intl` formatting uses.\n *\n * Held here rather than imported from an i18n runtime so the utilities have no\n * i18n dependency at all: an app that never installs vue-i18n still gets dates\n * in the right language. `createI18nRuntime` sets this when it is used.\n */\nconst locale = ref<string>(typeof navigator === 'undefined' ? 'en' : (navigator.language ?? 'en'))\n\n/**\n * Points every formatter at a new locale.\n *\n * @example\n * ```ts\n * setFormatLocale('tr-TR')\n * ```\n */\nexport function setFormatLocale(next: string): void {\n locale.value = next\n}\n\n/**\n * `Intl.DateTimeFormat` is expensive to construct, so instances are cached per\n * locale and option set. The key includes the locale, which is what lets the\n * cache survive a language change instead of returning stale formatters.\n */\nconst cache = new Map<string, Intl.DateTimeFormat>()\n\n/**\n * Formats a date in the active locale.\n *\n * Reading the locale ref here is deliberate: called from a `computed`, the\n * result re-evaluates when the language changes.\n *\n * @param date - Date to format.\n * @param options - Passed straight to `Intl.DateTimeFormat`.\n *\n * @example\n * ```ts\n * formatDate(new Date(), { weekday: 'narrow' }) // 'T'\n * ```\n */\nexport function formatDate(date: Date, options: Intl.DateTimeFormatOptions): string {\n const tag = locale.value\n const key = `${tag}:${JSON.stringify(options)}`\n\n let formatter = cache.get(key)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(tag, options)\n cache.set(key, formatter)\n }\n\n return formatter.format(date)\n}\n","import { addDays, fromDateKey } from './date'\nimport { formatDate } from './format'\n\n/** The two days worth naming rather than numbering. */\nexport interface DayLabels {\n today: string\n yesterday: string\n}\n\n/**\n * A short name for a day, relative to today.\n *\n * \"Today\" and \"Yesterday\" are worth spelling out — they are the two a user\n * actually reaches for. Anything older gets its weekday, which inside a\n * five-day window is unambiguous and stays two or three characters in every\n * language.\n *\n * The two words are arguments rather than translated here: a library that calls\n * `t()` forces every consumer onto one i18n setup.\n *\n * @param dateKey - The day to label (`YYYY-MM-DD`).\n * @param today - Today's key, passed in so the caller controls the clock.\n * @param labels - What to call today and yesterday.\n *\n * @example\n * ```ts\n * relativeDayLabel('2026-08-28', '2026-08-31', { today: 'Today', yesterday: 'Yesterday' })\n * // 'Fri'\n * ```\n */\nexport function relativeDayLabel(dateKey: string, today: string, labels: DayLabels): string {\n if (dateKey === today) return labels.today\n if (dateKey === addDays(today, -1)) return labels.yesterday\n\n return formatDate(fromDateKey(dateKey), { weekday: 'short' })\n}\n","/**\n * Hands the user a file without a server round trip.\n *\n * @param data - Anything `JSON.stringify` can serialise.\n * @param filename - Suggested name, e.g. `hibi-export-2026-08-24.json`.\n */\nexport function downloadJson(data: unknown, filename: string): void {\n const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n\n link.href = url\n link.download = filename\n link.click()\n\n URL.revokeObjectURL(url)\n}\n","/**\n * What a router hands back for one query key.\n *\n * Inlined rather than imported from vue-router: the shape is `string | null`\n * either way, and a helper this small should not drag a router into the\n * package's dependencies.\n */\nexport type QueryValue = string | null\n\n/**\n * Resolves a `?redirect=` query value into a safe in-app path.\n *\n * Only same-origin paths are accepted. Anything else falls back to `/`,\n * so a crafted link cannot bounce a user from the real login page to a\n * phishing clone.\n *\n * Pure: takes the query value instead of reading the router, so it also\n * works inside navigation guards and can be unit tested.\n *\n * @param target - Raw `route.query.redirect` value. May be a string, an\n * array (repeated query key), `null`, or `undefined`.\n * @returns A path starting with a single `/`. Defaults to `/`.\n *\n * @example\n * ```ts\n * // in a view\n * await router.push(safeRedirect(route.query.redirect))\n *\n * // in a guard\n * return safeRedirect(to.query.redirect)\n * ```\n *\n * @example\n * ```ts\n * safeRedirect('/week') // '/week'\n * safeRedirect('https://evil.com') // '/'\n * safeRedirect('//evil.com') // '/' (protocol-relative URL)\n * safeRedirect(['/a', '/b']) // '/'\n * safeRedirect(undefined) // '/'\n * ```\n */\nexport function safeRedirect(target: QueryValue | QueryValue[] | undefined): string {\n if (typeof target === 'string' && target.startsWith('/') && !target.startsWith('//')) {\n return target\n }\n\n return '/'\n}\n","/**\n * A short vibration for a confirmed tap.\n *\n * Optional chaining is not decoration: iOS Safari has no `vibrate` at all, and\n * calling it unguarded would throw on every marked day.\n *\n * @param duration - Milliseconds. Keep it under ~15ms; longer reads as an alert.\n */\nexport function tapFeedback(duration = 10): void {\n navigator.vibrate?.(duration)\n}\n","/**\n * Whether the app is running from the Home Screen rather than a browser tab.\n *\n * Two checks because iOS predates the standard one: `display-mode: standalone`\n * is the modern signal, `navigator.standalone` is Safari's own.\n */\nexport function isInstalled(): boolean {\n if (typeof window === 'undefined') return false\n\n return (\n window.matchMedia('(display-mode: standalone)').matches ||\n (navigator as Navigator & { standalone?: boolean }).standalone === true\n )\n}\n\n/** iPhone and iPad, including iPadOS reporting itself as a Mac. */\nexport function isApplePortable(): boolean {\n if (typeof window === 'undefined') return false\n\n return (\n /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)\n )\n}\n\n/**\n * Whether this device can only receive notifications once the app is installed.\n *\n * Safari on iOS grants notification permission to an installed web app and to\n * nothing else — in a normal tab the request does not even prompt. Telling the\n * user to allow notifications there is asking for something the browser will\n * not offer, so the UI has to say \"add to Home Screen\" instead.\n *\n * @example\n * ```ts\n * if (needsIosInstall()) // show the Home Screen instruction, not the button\n * ```\n */\nexport function needsIosInstall(): boolean {\n return isApplePortable() && !isInstalled()\n}\n","import { ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\n/** What the user asked for; `system` follows the OS. */\nexport type ThemePreference = 'system' | 'light' | 'dark'\n\n/**\n * Namespaced by the app, not by this package.\n *\n * Two rei-kit apps served from the same origin would otherwise share one theme\n * setting — and during development on localhost, they will be.\n */\nlet storageKey = 'rei-theme'\n\nexport function isThemePreference(value: unknown): value is ThemePreference {\n return value === 'system' || value === 'light' || value === 'dark'\n}\n\n/** Reads the stored preference, falling back to `system`. */\nexport function readStoredTheme(): ThemePreference {\n try {\n const stored = localStorage.getItem(storageKey)\n\n return isThemePreference(stored) ? stored : 'system'\n } catch {\n return 'system'\n }\n}\n\nfunction storeTheme(preference: ThemePreference): void {\n try {\n localStorage.setItem(storageKey, preference)\n } catch {\n // Private mode or blocked storage: the choice just will not persist.\n }\n}\n\n/**\n * Adds or removes `.dark` on `<html>`, resolving `system` against the OS.\n *\n * A no-op without a document. There is no OS preference to read on a server and\n * no `<html>` to write to, so a prerender leaves the class off and the app\n * decides the theme before hydration — see the note in the README.\n */\nexport function applyTheme(preference: ThemePreference): void {\n if (typeof document === 'undefined') return\n\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\n const isDark = preference === 'dark' || (preference === 'system' && prefersDark)\n\n document.documentElement.classList.toggle('dark', isDark)\n}\n\n/**\n * The shared preference, created on first use rather than at import.\n *\n * Lazy on purpose: reading storage at import time would lock in the default key\n * before an app had a chance to set its own, leaving the controller reading one\n * key and writing another.\n */\nlet preference: Ref<ThemePreference> | null = null\n\nfunction controller(): Ref<ThemePreference> {\n if (preference) return preference\n\n preference = ref<ThemePreference>(readStoredTheme())\n\n watch(\n preference,\n (next) => {\n storeTheme(next)\n applyTheme(next)\n },\n { immediate: true },\n )\n\n // While on `system`, follow the OS if the user flips it at night.\n if (typeof window !== 'undefined') {\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {\n if (preference?.value === 'system') applyTheme('system')\n })\n }\n\n return preference\n}\n\n/**\n * Sets where the preference is stored.\n *\n * Safe in either order: called before the first `useTheme()` it simply changes\n * the key, and called after it re-reads under the new one, so the controller\n * never reads from one key while writing to another.\n *\n * @example\n * ```ts\n * setThemeStorageKey('hibi-theme') // once, at startup\n * ```\n */\nexport function setThemeStorageKey(key: string): void {\n storageKey = key\n if (preference) preference.value = readStoredTheme()\n}\n\n/** @returns The shared preference ref; assigning to it stores and applies it. */\nexport function useTheme(): Ref<ThemePreference> {\n return controller()\n}\n","import { readonly, ref } from 'vue'\n\nimport { todayKey } from '../utils/date'\n\n/**\n * Today's date key, kept current while the app stays open.\n *\n * `todayKey()` called once in `setup` freezes the date for the lifetime of the\n * component. Nobody notices in a session that lasts minutes, but a phone left\n * on the Today screen overnight would keep marking yesterday, and the Week grid\n * would disable the column that just became today.\n */\nconst current = ref(todayKey())\n\nlet timer: ReturnType<typeof setTimeout> | undefined\nlet watching = false\n\n/** A second past midnight, so a fast timer cannot fire on the old date. */\nfunction msUntilMidnight(): number {\n const now = new Date()\n const next = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 1)\n\n return next.getTime() - now.getTime()\n}\n\nfunction refresh() {\n current.value = todayKey()\n}\n\nfunction schedule() {\n clearTimeout(timer)\n timer = setTimeout(() => {\n refresh()\n schedule()\n }, msUntilMidnight())\n}\n\n/**\n * Starts the clock, once, and only where there is a clock to watch.\n *\n * This used to run at import time, which made the module impossible to load on\n * a server: `document` is not defined there, and a barrel export means one\n * `import { BaseButton } from 'rei-kit'` pulls this file in. Deferring it to\n * the first `useToday()` also means an app that never asks for today never\n * arms a timer.\n */\nfunction watchTheClock() {\n if (watching || typeof document === 'undefined') return\n\n watching = true\n schedule()\n\n // A sleeping phone does not run timers reliably, so the tab also re-checks\n // the moment it comes back — which is when the user would see a stale date.\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return\n\n refresh()\n schedule()\n })\n}\n\n/**\n * @returns Read-only ref holding today's `YYYY-MM-DD` key.\n *\n * Rendered on a server this is the *server's* today, which is a different day\n * from the visitor's either side of midnight. Anything prerendered from it\n * would hydrate to a different value; render it on the client.\n *\n * @example\n * ```ts\n * const today = useToday()\n * const isFuture = computed(() => day > today.value)\n * ```\n */\nexport function useToday() {\n watchTheClock()\n\n return readonly(current)\n}\n","import { onMounted, onUnmounted, readonly, ref } from 'vue'\n\n/**\n * Tracks whether the browser thinks it has a network connection.\n *\n * Note the limit: `navigator.onLine` only reports whether a network interface\n * is up, not whether requests actually succeed. Treat it as a hint for the UI,\n * never as a reason to skip error handling.\n *\n * Listeners are removed on unmount, so the composable is safe to call per view.\n *\n * @returns A readonly ref that flips with the browser's online/offline events.\n *\n * @example\n * ```ts\n * const isOnline = useOnline()\n * // <p v-if=\"!isOnline\">You're offline.</p>\n * ```\n */\nexport function useOnline() {\n const isOnline = ref(true)\n\n function update() {\n isOnline.value = navigator.onLine\n }\n\n onMounted(() => {\n update()\n window.addEventListener('online', update)\n window.addEventListener('offline', update)\n })\n\n onUnmounted(() => {\n window.removeEventListener('online', update)\n window.removeEventListener('offline', update)\n })\n\n return readonly(isOnline)\n}\n","import { onScopeDispose } from 'vue'\n\n/**\n * Delays a callback until the caller stops calling it.\n *\n * Used for note autosave: a request per keystroke would be wasteful, but losing\n * the last keystrokes when the user navigates away would be worse — so the\n * pending call is flushed on dispose, and `flush` is exposed for route guards.\n *\n * @param callback - Runs with the arguments of the most recent call.\n * @param delay - Quiet period in milliseconds.\n * @returns `run` to schedule, `flush` to run now, `cancel` to drop.\n *\n * @example\n * ```ts\n * const save = useDebouncedCallback((body: string) => mutate(body), 800)\n * watch(text, (value) => save.run(value))\n * onBeforeRouteLeave(() => save.flush())\n * ```\n */\nexport function useDebouncedCallback<A extends unknown[]>(\n callback: (...args: A) => void,\n delay = 800,\n) {\n let timer: ReturnType<typeof setTimeout> | null = null\n let pending: A | null = null\n\n /** Runs the pending call right now, if there is one. */\n function flush() {\n if (timer !== null) clearTimeout(timer)\n timer = null\n\n if (pending !== null) {\n const args = pending\n pending = null\n callback(...args)\n }\n }\n\n /** Drops the pending call without running it. */\n function cancel() {\n if (timer !== null) clearTimeout(timer)\n timer = null\n pending = null\n }\n\n function run(...args: A) {\n pending = args\n if (timer !== null) clearTimeout(timer)\n timer = setTimeout(flush, delay)\n }\n\n // A closing sheet or an unmounting view must not eat the last keystrokes.\n onScopeDispose(flush)\n\n return { run, flush, cancel }\n}\n","import { onScopeDispose, watch } from 'vue'\nimport type { Ref } from 'vue'\n\n/** Movement before a press counts as a drag rather than a tap. */\nconst DRAG_THRESHOLD_PX = 6\n\n/**\n * Drag-to-scroll for a horizontally scrolling element.\n *\n * The app puts `touch-action: pan-y` on the page content so the tab-swipe\n * gesture keeps its pointer events — the browser never claims a horizontal\n * drag, which also means it never pans this element natively. Rather than give\n * that up, horizontal scrolling is driven here.\n *\n * @param target - The scroll container.\n * @returns `didDrag`, so a click handler can ignore the press that ended a drag.\n *\n * @example\n * ```ts\n * const scroller = ref<HTMLElement | null>(null)\n * const { didDrag } = useDragScroll(scroller)\n *\n * function onClick() {\n * if (didDrag()) return\n * // …treat as a tap\n * }\n * ```\n */\nexport function useDragScroll(target: Ref<HTMLElement | null>) {\n let pointerId: number | null = null\n let startX = 0\n let startScroll = 0\n let dragged = false\n\n function onPointerDown(event: PointerEvent) {\n const element = target.value\n if (!element || event.pointerType === 'mouse') return\n\n pointerId = event.pointerId\n startX = event.clientX\n startScroll = element.scrollLeft\n dragged = false\n }\n\n function onPointerMove(event: PointerEvent) {\n const element = target.value\n if (!element || event.pointerId !== pointerId) return\n\n const dx = event.clientX - startX\n if (!dragged && Math.abs(dx) < DRAG_THRESHOLD_PX) return\n\n // Capture only once the gesture is clearly horizontal, so a vertical scroll\n // that happens to start here still belongs to the page.\n if (!dragged) {\n dragged = true\n element.setPointerCapture(event.pointerId)\n }\n\n element.scrollLeft = startScroll - dx\n }\n\n function onPointerUp(event: PointerEvent) {\n const element = target.value\n if (element?.hasPointerCapture(event.pointerId)) {\n element.releasePointerCapture(event.pointerId)\n }\n\n pointerId = null\n }\n\n function bind(element: HTMLElement) {\n element.addEventListener('pointerdown', onPointerDown)\n element.addEventListener('pointermove', onPointerMove)\n element.addEventListener('pointerup', onPointerUp)\n element.addEventListener('pointercancel', onPointerUp)\n }\n\n function unbind(element: HTMLElement) {\n element.removeEventListener('pointerdown', onPointerDown)\n element.removeEventListener('pointermove', onPointerMove)\n element.removeEventListener('pointerup', onPointerUp)\n element.removeEventListener('pointercancel', onPointerUp)\n }\n\n watch(\n target,\n (element, previous) => {\n if (previous) unbind(previous)\n if (element) bind(element)\n },\n { immediate: true },\n )\n\n onScopeDispose(() => {\n if (target.value) unbind(target.value)\n })\n\n return { didDrag: () => dragged }\n}\n","import { onScopeDispose, readonly, ref } from 'vue'\n\n/** The visible area, once the on-screen keyboard has taken its share. */\nexport interface VisualViewportRect {\n height: number\n offsetTop: number\n}\n\n/**\n * Tracks the visual viewport.\n *\n * Chrome and Android browsers honour `interactive-widget=resizes-content`, so\n * the layout viewport already shrinks for the keyboard there. Safari on iOS\n * does not implement it: it shrinks only the *visual* viewport, leaving a sheet\n * sized in `dvh` sitting partly underneath the keyboard.\n *\n * `null` means the API is unavailable, which callers should read as \"trust the\n * layout viewport\" rather than as zero. A server has no viewport at all, so it\n * gets that same `null` — this runs during `setup`, and a component using it\n * has to survive being rendered there.\n *\n * @example\n * ```ts\n * const viewport = useVisualViewport()\n * // :style=\"viewport ? { height: `${viewport.height}px` } : undefined\"\n * ```\n */\nexport function useVisualViewport() {\n const rect = ref<VisualViewportRect | null>(null)\n\n const viewport = typeof window === 'undefined' ? undefined : window.visualViewport\n if (!viewport) return readonly(rect)\n\n function read() {\n if (!viewport) return\n\n rect.value = { height: viewport.height, offsetTop: viewport.offsetTop }\n }\n\n read()\n\n // `scroll` matters as much as `resize`: iOS shifts the visual viewport up to\n // keep the focused field visible, without changing its height.\n viewport.addEventListener('resize', read)\n viewport.addEventListener('scroll', read)\n\n onScopeDispose(() => {\n viewport.removeEventListener('resize', read)\n viewport.removeEventListener('scroll', read)\n })\n\n return readonly(rect)\n}\n","<script setup lang=\"ts\">\nconst {\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled = false,\n type = 'button',\n} = defineProps<{\n variant?: 'primary' | 'ghost' | 'danger'\n size?: 'sm' | 'md'\n loading?: boolean\n disabled?: boolean\n type?: 'button' | 'submit'\n}>()\n\nconst VARIANT_CLASS = {\n primary: 'bg-primary text-white hover:bg-primary/90',\n ghost: 'bg-transparent text-ink hover:bg-muted',\n danger: 'bg-negative text-white hover:bg-negative/90',\n} as const\n\nconst SIZE_CLASS = {\n sm: 'h-9 px-3 text-sm',\n md: 'h-11 px-4 text-base',\n} as const\n</script>\n\n<template>\n <button\n :type=\"type\"\n :disabled=\"disabled || loading\"\n :aria-busy=\"loading\"\n class=\"rounded-card focus-visible:outline-primary inline-flex items-center justify-center gap-2 font-medium transition-transform duration-100 select-none focus-visible:outline-2 focus-visible:outline-offset-2 active:scale-95 disabled:pointer-events-none disabled:opacity-50\"\n :class=\"[VARIANT_CLASS[variant], SIZE_CLASS[size]]\"\n >\n <span\n v-if=\"loading\"\n class=\"size-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n aria-hidden=\"true\"\n />\n <slot />\n </button>\n</template>\n","<script setup lang=\"ts\">\nconst {\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled = false,\n type = 'button',\n} = defineProps<{\n variant?: 'primary' | 'ghost' | 'danger'\n size?: 'sm' | 'md'\n loading?: boolean\n disabled?: boolean\n type?: 'button' | 'submit'\n}>()\n\nconst VARIANT_CLASS = {\n primary: 'bg-primary text-white hover:bg-primary/90',\n ghost: 'bg-transparent text-ink hover:bg-muted',\n danger: 'bg-negative text-white hover:bg-negative/90',\n} as const\n\nconst SIZE_CLASS = {\n sm: 'h-9 px-3 text-sm',\n md: 'h-11 px-4 text-base',\n} as const\n</script>\n\n<template>\n <button\n :type=\"type\"\n :disabled=\"disabled || loading\"\n :aria-busy=\"loading\"\n class=\"rounded-card focus-visible:outline-primary inline-flex items-center justify-center gap-2 font-medium transition-transform duration-100 select-none focus-visible:outline-2 focus-visible:outline-offset-2 active:scale-95 disabled:pointer-events-none disabled:opacity-50\"\n :class=\"[VARIANT_CLASS[variant], SIZE_CLASS[size]]\"\n >\n <span\n v-if=\"loading\"\n class=\"size-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n aria-hidden=\"true\"\n />\n <slot />\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst {\n label,\n error = '',\n hint = '',\n type = 'text',\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the input with no accessible name at all.\n */\n labelHidden?: boolean\n type?: 'text' | 'email' | 'password' | 'number'\n}>()\n\nconst model = defineModel<string | undefined>()\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label :for=\"id\" class=\"text-ink text-sm font-medium\" :class=\"labelHidden ? 'sr-only' : ''\">\n {{ label }}\n </label>\n\n <input\n :id=\"id\"\n v-model=\"model\"\n :type=\"type\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n v-bind=\"$attrs\"\n class=\"border-hair bg-surface text-ink rounded-card focus-visible:outline-primary h-11 border px-3 focus-visible:outline-2 focus-visible:outline-offset-1\"\n :class=\"error ? 'border-negative' : ''\"\n />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst {\n label,\n error = '',\n hint = '',\n type = 'text',\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the input with no accessible name at all.\n */\n labelHidden?: boolean\n type?: 'text' | 'email' | 'password' | 'number'\n}>()\n\nconst model = defineModel<string | undefined>()\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label :for=\"id\" class=\"text-ink text-sm font-medium\" :class=\"labelHidden ? 'sr-only' : ''\">\n {{ label }}\n </label>\n\n <input\n :id=\"id\"\n v-model=\"model\"\n :type=\"type\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n v-bind=\"$attrs\"\n class=\"border-hair bg-surface text-ink rounded-card focus-visible:outline-primary h-11 border px-3 focus-visible:outline-2 focus-visible:outline-offset-1\"\n :class=\"error ? 'border-negative' : ''\"\n />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string\n}>()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n lastFocused?.focus()\n lastFocused = null\n setBackgroundInert(false)\n }\n})\n\n/**\n * `inert` takes the whole app out of tab order and pointer events while the\n * sheet is open — a real focus trap without keydown bookkeeping.\n *\n * The sheet itself is teleported to `#sheet-root`, a sibling of `#app`, so it\n * stays interactive.\n */\nfunction setBackgroundInert(isInert: boolean) {\n document.getElementById('app')?.toggleAttribute('inert', isInert)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport to=\"#sheet-root\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel bg-surface relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[28px] shadow-2xl outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <button\n type=\"button\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </button>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity 200ms ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string\n}>()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n lastFocused?.focus()\n lastFocused = null\n setBackgroundInert(false)\n }\n})\n\n/**\n * `inert` takes the whole app out of tab order and pointer events while the\n * sheet is open — a real focus trap without keydown bookkeeping.\n *\n * The sheet itself is teleported to `#sheet-root`, a sibling of `#app`, so it\n * stays interactive.\n */\nfunction setBackgroundInert(isInert: boolean) {\n document.getElementById('app')?.toggleAttribute('inert', isInert)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport to=\"#sheet-root\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel bg-surface relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[28px] shadow-2xl outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <button\n type=\"button\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </button>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity 200ms ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script lang=\"ts\" setup>\nconst { title, description = '' } = defineProps<{\n title: string\n description?: string | undefined\n}>()\n</script>\n\n<template>\n <div class=\"flex flex-col items-center gap-3 px-6 py-10 text-center\">\n <div\n v-if=\"$slots.icon\"\n class=\"bg-muted text-primary rounded-card flex size-12 items-center justify-center\"\n >\n <slot name=\"icon\" />\n </div>\n\n <h3 class=\"text-ink text-base font-semibold\">{{ title }}</h3>\n <p v-if=\"description\" class=\"text-ink-soft max-w-[36ch] text-sm\">\n {{ description }}\n </p>\n\n <div v-if=\"$slots.action\" class=\"mt-2 flex w-full flex-col gap-2\">\n <slot name=\"action\" />\n </div>\n </div>\n</template>\n\n<style></style>\n","<script lang=\"ts\" setup>\nconst { title, description = '' } = defineProps<{\n title: string\n description?: string | undefined\n}>()\n</script>\n\n<template>\n <div class=\"flex flex-col items-center gap-3 px-6 py-10 text-center\">\n <div\n v-if=\"$slots.icon\"\n class=\"bg-muted text-primary rounded-card flex size-12 items-center justify-center\"\n >\n <slot name=\"icon\" />\n </div>\n\n <h3 class=\"text-ink text-base font-semibold\">{{ title }}</h3>\n <p v-if=\"description\" class=\"text-ink-soft max-w-[36ch] text-sm\">\n {{ description }}\n </p>\n\n <div v-if=\"$slots.action\" class=\"mt-2 flex w-full flex-col gap-2\">\n <slot name=\"action\" />\n </div>\n </div>\n</template>\n\n<style></style>\n","<script setup lang=\"ts\">\nconst { title } = defineProps<{ title: string }>()\n</script>\n\n<template>\n <header class=\"grid h-12 shrink-0 grid-cols-[2.5rem_1fr_2.5rem] items-center\">\n <div class=\"justify-self-start\"><slot name=\"left\" /></div>\n\n <h1 class=\"text-ink flex min-w-0 justify-center text-base font-semibold tabular-nums\">\n <slot name=\"title\">\n <span class=\"truncate\">{{ title }}</span>\n </slot>\n </h1>\n\n <div class=\"justify-self-end\"><slot name=\"right\" /></div>\n </header>\n</template>\n","<script setup lang=\"ts\">\nconst { title } = defineProps<{ title: string }>()\n</script>\n\n<template>\n <header class=\"grid h-12 shrink-0 grid-cols-[2.5rem_1fr_2.5rem] items-center\">\n <div class=\"justify-self-start\"><slot name=\"left\" /></div>\n\n <h1 class=\"text-ink flex min-w-0 justify-center text-base font-semibold tabular-nums\">\n <slot name=\"title\">\n <span class=\"truncate\">{{ title }}</span>\n </slot>\n </h1>\n\n <div class=\"justify-self-end\"><slot name=\"right\" /></div>\n </header>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A small coloured dot, optionally labelled.\n *\n * Takes the colour as a class rather than a category, so an app can key it off\n * whatever its own domain calls a category — habit kinds, expense types,\n * priorities — without this component knowing about any of them.\n */\nconst { fill, label = '' } = defineProps<{\n /** Background utility for the dot, e.g. `bg-positive`. */\n fill: string\n /** Optional text after the dot. Omit for a bare marker. */\n label?: string\n}>()\n</script>\n\n<template>\n <span class=\"inline-flex items-center gap-1.5\">\n <span class=\"size-2 rounded-full\" :class=\"fill\" />\n <span v-if=\"label\" class=\"text-ink-soft text-xs font-medium\">{{ label }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A small coloured dot, optionally labelled.\n *\n * Takes the colour as a class rather than a category, so an app can key it off\n * whatever its own domain calls a category — habit kinds, expense types,\n * priorities — without this component knowing about any of them.\n */\nconst { fill, label = '' } = defineProps<{\n /** Background utility for the dot, e.g. `bg-positive`. */\n fill: string\n /** Optional text after the dot. Omit for a bare marker. */\n label?: string\n}>()\n</script>\n\n<template>\n <span class=\"inline-flex items-center gap-1.5\">\n <span class=\"size-2 rounded-full\" :class=\"fill\" />\n <span v-if=\"label\" class=\"text-ink-soft text-xs font-medium\">{{ label }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\nimport ToneDot from './ToneDot.vue'\n\n/** The three classes a category needs to colour a heading. */\nexport interface Tone {\n /** Solid background for the dot, e.g. `bg-positive`. */\n fill: string\n /** Tinted surface for the pill, e.g. `bg-positive/5 border-positive/25`. */\n card: string\n /** Foreground that pairs with the surface, e.g. `text-positive`. */\n text: string\n}\n\n/**\n * A pill heading for a group of things.\n *\n * The tone arrives as three class strings rather than a category name: Tailwind\n * reads source files as plain text, so a class assembled at runtime never\n * reaches the stylesheet — the app has to write them out, and it is the app\n * that knows its own categories anyway.\n */\nconst {\n tone,\n label,\n count = 0,\n} = defineProps<{\n tone: Tone\n label: string\n /** Hidden when zero, so an empty group's heading stays quiet. */\n count?: number\n}>()\n</script>\n\n<template>\n <h2 class=\"flex items-center gap-2 self-start rounded-full border px-3 py-1\" :class=\"tone.card\">\n <ToneDot :fill=\"tone.fill\" />\n <span class=\"text-xs font-semibold tracking-wide uppercase\" :class=\"tone.text\">\n {{ label }}\n </span>\n <span v-if=\"count > 0\" class=\"text-ink-soft text-xs tabular-nums\">{{ count }}</span>\n </h2>\n</template>\n","<script setup lang=\"ts\">\nimport ToneDot from './ToneDot.vue'\n\n/** The three classes a category needs to colour a heading. */\nexport interface Tone {\n /** Solid background for the dot, e.g. `bg-positive`. */\n fill: string\n /** Tinted surface for the pill, e.g. `bg-positive/5 border-positive/25`. */\n card: string\n /** Foreground that pairs with the surface, e.g. `text-positive`. */\n text: string\n}\n\n/**\n * A pill heading for a group of things.\n *\n * The tone arrives as three class strings rather than a category name: Tailwind\n * reads source files as plain text, so a class assembled at runtime never\n * reaches the stylesheet — the app has to write them out, and it is the app\n * that knows its own categories anyway.\n */\nconst {\n tone,\n label,\n count = 0,\n} = defineProps<{\n tone: Tone\n label: string\n /** Hidden when zero, so an empty group's heading stays quiet. */\n count?: number\n}>()\n</script>\n\n<template>\n <h2 class=\"flex items-center gap-2 self-start rounded-full border px-3 py-1\" :class=\"tone.card\">\n <ToneDot :fill=\"tone.fill\" />\n <span class=\"text-xs font-semibold tracking-wide uppercase\" :class=\"tone.text\">\n {{ label }}\n </span>\n <span v-if=\"count > 0\" class=\"text-ink-soft text-xs tabular-nums\">{{ count }}</span>\n </h2>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends string | number\">\nimport { useId } from 'vue'\n\n/**\n * A row of mutually exclusive choices.\n *\n * Radio inputs rather than buttons: it is a single choice out of a small set,\n * so arrow-key navigation and the \"one of N selected\" announcement come free.\n */\nconst { options } = defineProps<{\n options: readonly { value: T; label: string }[]\n}>()\n\nconst model = defineModel<T>({ required: true })\n\nconst name = useId()\n</script>\n\n<template>\n <div class=\"bg-muted rounded-card flex w-full gap-1 p-1\">\n <label v-for=\"option in options\" :key=\"String(option.value)\" class=\"flex-1 cursor-pointer\">\n <input v-model=\"model\" type=\"radio\" :value=\"option.value\" :name=\"name\" class=\"sr-only\" />\n <span\n class=\"flex h-10 items-center justify-center rounded-xl px-2 text-sm font-medium transition-colors select-none\"\n :class=\"model === option.value ? 'bg-surface text-ink shadow-sm' : 'text-ink-soft'\"\n >\n {{ option.label }}\n </span>\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends string | number\">\nimport { useId } from 'vue'\n\n/**\n * A row of mutually exclusive choices.\n *\n * Radio inputs rather than buttons: it is a single choice out of a small set,\n * so arrow-key navigation and the \"one of N selected\" announcement come free.\n */\nconst { options } = defineProps<{\n options: readonly { value: T; label: string }[]\n}>()\n\nconst model = defineModel<T>({ required: true })\n\nconst name = useId()\n</script>\n\n<template>\n <div class=\"bg-muted rounded-card flex w-full gap-1 p-1\">\n <label v-for=\"option in options\" :key=\"String(option.value)\" class=\"flex-1 cursor-pointer\">\n <input v-model=\"model\" type=\"radio\" :value=\"option.value\" :name=\"name\" class=\"sr-only\" />\n <span\n class=\"flex h-10 items-center justify-center rounded-xl px-2 text-sm font-medium transition-colors select-none\"\n :class=\"model === option.value ? 'bg-surface text-ink shadow-sm' : 'text-ink-soft'\"\n >\n {{ option.label }}\n </span>\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\">\ndefineProps<{ title: string }>()\n</script>\n\n<template>\n <section class=\"flex flex-col gap-2\">\n <h2 class=\"text-ink-soft px-1 text-xs font-semibold tracking-wide uppercase\">{{ title }}</h2>\n\n <!-- One card per group, rows divided by hairlines. Loose fields floating on\n the page gave no sense of what belonged with what. -->\n <div class=\"border-hair bg-surface rounded-card divide-hair divide-y overflow-hidden border\">\n <slot />\n </div>\n </section>\n</template>\n","<script setup lang=\"ts\">\ndefineProps<{ title: string }>()\n</script>\n\n<template>\n <section class=\"flex flex-col gap-2\">\n <h2 class=\"text-ink-soft px-1 text-xs font-semibold tracking-wide uppercase\">{{ title }}</h2>\n\n <!-- One card per group, rows divided by hairlines. Loose fields floating on\n the page gave no sense of what belonged with what. -->\n <div class=\"border-hair bg-surface rounded-card divide-hair divide-y overflow-hidden border\">\n <slot />\n </div>\n </section>\n</template>\n","<script setup lang=\"ts\">\nimport { ChevronRight } from 'lucide-vue-next'\nimport type { Component } from 'vue'\n\n/**\n * One line in a settings card.\n *\n * `as` decides the element: a row that navigates has to be a button, and a row\n * that merely holds a control must not be, or the control becomes unreachable.\n */\nconst {\n label,\n description = '',\n icon = undefined,\n interactive = false,\n stacked = false,\n} = defineProps<{\n label: string\n description?: string\n icon?: Component | undefined\n /** Renders the row as a button with a chevron. */\n interactive?: boolean\n /** Puts the control on its own line below the label, for wide controls. */\n stacked?: boolean\n}>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <component\n :is=\"interactive ? 'button' : 'div'\"\n :type=\"interactive ? 'button' : undefined\"\n class=\"flex w-full items-center gap-3 px-4 py-3 text-left\"\n :class=\"[\n interactive ? 'hover:bg-muted/60 transition-colors active:scale-[0.99]' : '',\n stacked ? 'flex-col items-stretch gap-3' : '',\n ]\"\n @click=\"interactive && emit('click')\"\n >\n <div class=\"flex items-center gap-3\">\n <span\n v-if=\"icon\"\n class=\"bg-muted text-ink-soft flex size-9 shrink-0 items-center justify-center rounded-xl\"\n aria-hidden=\"true\"\n >\n <component :is=\"icon\" class=\"size-[18px]\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <p class=\"text-ink text-sm font-medium\">{{ label }}</p>\n <p v-if=\"description\" class=\"text-ink-soft mt-0.5 text-xs leading-snug\">\n {{ description }}\n </p>\n </div>\n\n <div v-if=\"!stacked\" class=\"shrink-0\"><slot /></div>\n\n <ChevronRight v-if=\"interactive\" class=\"text-ink-soft size-4 shrink-0\" aria-hidden=\"true\" />\n </div>\n\n <div v-if=\"stacked\"><slot /></div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { ChevronRight } from 'lucide-vue-next'\nimport type { Component } from 'vue'\n\n/**\n * One line in a settings card.\n *\n * `as` decides the element: a row that navigates has to be a button, and a row\n * that merely holds a control must not be, or the control becomes unreachable.\n */\nconst {\n label,\n description = '',\n icon = undefined,\n interactive = false,\n stacked = false,\n} = defineProps<{\n label: string\n description?: string\n icon?: Component | undefined\n /** Renders the row as a button with a chevron. */\n interactive?: boolean\n /** Puts the control on its own line below the label, for wide controls. */\n stacked?: boolean\n}>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <component\n :is=\"interactive ? 'button' : 'div'\"\n :type=\"interactive ? 'button' : undefined\"\n class=\"flex w-full items-center gap-3 px-4 py-3 text-left\"\n :class=\"[\n interactive ? 'hover:bg-muted/60 transition-colors active:scale-[0.99]' : '',\n stacked ? 'flex-col items-stretch gap-3' : '',\n ]\"\n @click=\"interactive && emit('click')\"\n >\n <div class=\"flex items-center gap-3\">\n <span\n v-if=\"icon\"\n class=\"bg-muted text-ink-soft flex size-9 shrink-0 items-center justify-center rounded-xl\"\n aria-hidden=\"true\"\n >\n <component :is=\"icon\" class=\"size-[18px]\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <p class=\"text-ink text-sm font-medium\">{{ label }}</p>\n <p v-if=\"description\" class=\"text-ink-soft mt-0.5 text-xs leading-snug\">\n {{ description }}\n </p>\n </div>\n\n <div v-if=\"!stacked\" class=\"shrink-0\"><slot /></div>\n\n <ChevronRight v-if=\"interactive\" class=\"text-ink-soft size-4 shrink-0\" aria-hidden=\"true\" />\n </div>\n\n <div v-if=\"stacked\"><slot /></div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nconst {\n rows = 3,\n rowHeight = 'h-14',\n label = 'Loading…',\n} = defineProps<{\n rows?: number\n rowHeight?: string\n label?: string\n}>()\n</script>\n\n<template>\n <div role=\"status\" class=\"flex flex-col gap-1\">\n <span class=\"sr-only\">{{ label }}</span>\n\n <div\n v-for=\"row in rows\"\n :key=\"row\"\n class=\"bg-muted rounded-card animate-pulse\"\n :class=\"rowHeight\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nconst {\n rows = 3,\n rowHeight = 'h-14',\n label = 'Loading…',\n} = defineProps<{\n rows?: number\n rowHeight?: string\n label?: string\n}>()\n</script>\n\n<template>\n <div role=\"status\" class=\"flex flex-col gap-1\">\n <span class=\"sr-only\">{{ label }}</span>\n\n <div\n v-for=\"row in rows\"\n :key=\"row\"\n class=\"bg-muted rounded-card animate-pulse\"\n :class=\"rowHeight\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { ArrowDown, ArrowRight, ArrowUp } from 'lucide-vue-next'\n\nconst {\n value,\n label,\n trend = null,\n} = defineProps<{\n value: string\n label: string\n trend?: 'up' | 'down' | 'flat' | null\n}>()\n\nconst TREND_ICON = { up: ArrowUp, down: ArrowDown, flat: ArrowRight } as const\n</script>\n\n<template>\n <div class=\"border-hair rounded-card flex flex-1 flex-col gap-0.5 border p-3\">\n <div class=\"flex items-baseline gap-1\">\n <span class=\"text-ink text-xl font-semibold tabular-nums\">{{ value }}</span>\n <component :is=\"TREND_ICON[trend]\" v-if=\"trend\" class=\"text-ink-soft size-3\" />\n </div>\n <span class=\"text-ink-soft text-xs\">{{ label }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { ArrowDown, ArrowRight, ArrowUp } from 'lucide-vue-next'\n\nconst {\n value,\n label,\n trend = null,\n} = defineProps<{\n value: string\n label: string\n trend?: 'up' | 'down' | 'flat' | null\n}>()\n\nconst TREND_ICON = { up: ArrowUp, down: ArrowDown, flat: ArrowRight } as const\n</script>\n\n<template>\n <div class=\"border-hair rounded-card flex flex-1 flex-col gap-0.5 border p-3\">\n <div class=\"flex items-baseline gap-1\">\n <span class=\"text-ink text-xl font-semibold tabular-nums\">{{ value }}</span>\n <component :is=\"TREND_ICON[trend]\" v-if=\"trend\" class=\"text-ink-soft size-3\" />\n </div>\n <span class=\"text-ink-soft text-xs\">{{ label }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"L extends string\">\n/**\n * A flat language switcher for screens with no Settings behind them.\n *\n * The list and the labels are props: only the app knows which languages it\n * ships, and endonyms — each language written in itself — are what make the\n * right option legible to someone who cannot read the current interface.\n */\nconst {\n locales,\n labels,\n label = '',\n} = defineProps<{\n locales: readonly L[]\n /** Endonyms, e.g. `{ en: 'English', tr: 'Türkçe' }`. */\n labels: Record<L, string>\n /** Accessible name for the group. */\n label?: string\n}>()\n\n/**\n * Two-way bound rather than taking the runtime's ref as a prop: props are not\n * unwrapped in a template and cannot be assigned to, so the ref would compare\n * against itself and the click handler would not compile.\n */\nconst preference = defineModel<'system' | L>({ required: true })\n</script>\n\n<template>\n <nav class=\"flex flex-wrap items-center justify-center gap-1\" :aria-label=\"label || undefined\">\n <button\n v-for=\"locale in locales\"\n :key=\"locale\"\n type=\"button\"\n :lang=\"locale\"\n class=\"rounded-full px-2.5 py-1.5 text-xs transition-colors\"\n :class=\"\n preference === locale ? 'bg-muted text-ink font-semibold' : 'text-ink-soft hover:text-ink'\n \"\n :aria-pressed=\"preference === locale\"\n @click=\"preference = locale\"\n >\n {{ labels[locale] }}\n </button>\n </nav>\n</template>\n","<script setup lang=\"ts\" generic=\"L extends string\">\n/**\n * A flat language switcher for screens with no Settings behind them.\n *\n * The list and the labels are props: only the app knows which languages it\n * ships, and endonyms — each language written in itself — are what make the\n * right option legible to someone who cannot read the current interface.\n */\nconst {\n locales,\n labels,\n label = '',\n} = defineProps<{\n locales: readonly L[]\n /** Endonyms, e.g. `{ en: 'English', tr: 'Türkçe' }`. */\n labels: Record<L, string>\n /** Accessible name for the group. */\n label?: string\n}>()\n\n/**\n * Two-way bound rather than taking the runtime's ref as a prop: props are not\n * unwrapped in a template and cannot be assigned to, so the ref would compare\n * against itself and the click handler would not compile.\n */\nconst preference = defineModel<'system' | L>({ required: true })\n</script>\n\n<template>\n <nav class=\"flex flex-wrap items-center justify-center gap-1\" :aria-label=\"label || undefined\">\n <button\n v-for=\"locale in locales\"\n :key=\"locale\"\n type=\"button\"\n :lang=\"locale\"\n class=\"rounded-full px-2.5 py-1.5 text-xs transition-colors\"\n :class=\"\n preference === locale ? 'bg-muted text-ink font-semibold' : 'text-ink-soft hover:text-ink'\n \"\n :aria-pressed=\"preference === locale\"\n @click=\"preference = locale\"\n >\n {{ labels[locale] }}\n </button>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nconst { label } = defineProps<{ label: string }>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"border-hair bg-surface text-ink rounded-card hover:bg-muted flex h-11 w-full items-center justify-center gap-2 border text-sm font-medium transition-colors active:scale-95\"\n @click=\"emit('click')\"\n >\n <!-- Google asks for its own mark, so it is inlined rather than themed. -->\n <svg class=\"size-4\" viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <path\n fill=\"#EA4335\"\n d=\"M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.7 30.2.5 24 .5 14.6.5 6.5 5.9 2.6 13.7l7.8 6.1C12.3 13.7 17.7 9.5 24 9.5z\"\n />\n <path\n fill=\"#4285F4\"\n d=\"M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.6-4.9 7.3l7.6 5.9c4.4-4.1 7.1-10.2 7.1-17.7z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M10.4 28.2a14.6 14.6 0 0 1 0-8.4l-7.8-6.1a24 24 0 0 0 0 20.6l7.8-6.1z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M24 47.5c6.2 0 11.5-2 15.4-5.6l-7.6-5.9c-2.1 1.4-4.8 2.3-7.8 2.3-6.3 0-11.7-4.2-13.6-10l-7.8 6.1C6.5 42.1 14.6 47.5 24 47.5z\"\n />\n </svg>\n {{ label }}\n </button>\n</template>\n","<script setup lang=\"ts\">\nconst { label } = defineProps<{ label: string }>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"border-hair bg-surface text-ink rounded-card hover:bg-muted flex h-11 w-full items-center justify-center gap-2 border text-sm font-medium transition-colors active:scale-95\"\n @click=\"emit('click')\"\n >\n <!-- Google asks for its own mark, so it is inlined rather than themed. -->\n <svg class=\"size-4\" viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <path\n fill=\"#EA4335\"\n d=\"M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.7 30.2.5 24 .5 14.6.5 6.5 5.9 2.6 13.7l7.8 6.1C12.3 13.7 17.7 9.5 24 9.5z\"\n />\n <path\n fill=\"#4285F4\"\n d=\"M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.6-4.9 7.3l7.6 5.9c4.4-4.1 7.1-10.2 7.1-17.7z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M10.4 28.2a14.6 14.6 0 0 1 0-8.4l-7.8-6.1a24 24 0 0 0 0 20.6l7.8-6.1z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M24 47.5c6.2 0 11.5-2 15.4-5.6l-7.6-5.9c-2.1 1.4-4.8 2.3-7.8 2.3-6.3 0-11.7-4.2-13.6-10l-7.8 6.1C6.5 42.1 14.6 47.5 24 47.5z\"\n />\n </svg>\n {{ label }}\n </button>\n</template>\n","<script setup lang=\"ts\" generic=\"K extends string\">\nimport { RouterLink } from 'vue-router'\nimport type { Component } from 'vue'\n\nimport { tapFeedback } from '../utils/haptics'\n\nexport interface TabItem<K extends string> {\n /** Identity, compared against `active`. */\n key: K\n /** Router destination. */\n to: string\n /** Text under the icon. Already translated. */\n label: string\n icon: Component\n}\n\n/**\n * The floating bottom bar.\n *\n * Items and the active key are props: the package has no opinion about how an\n * app names its screens, and reading `route.meta` here would force one.\n */\nconst {\n items,\n active,\n label = '',\n} = defineProps<{\n items: readonly TabItem<K>[]\n /** Which item is current. Usually from `route.meta`. */\n active?: K | undefined\n /** Accessible name for the navigation landmark. */\n label?: string\n}>()\n</script>\n\n<template>\n <header class=\"tab-bar\">\n <nav class=\"tab-bar-inner\" :aria-label=\"label || undefined\">\n <RouterLink\n v-for=\"item in items\"\n :key=\"item.key\"\n :to=\"item.to\"\n class=\"tab-link\"\n :class=\"{ 'is-active': item.key === active }\"\n :aria-current=\"item.key === active ? 'page' : undefined\"\n @click=\"tapFeedback()\"\n >\n <span class=\"tab-icon-slot\">\n <component :is=\"item.icon\" class=\"tab-icon\" />\n </span>\n <span class=\"tab-label\">{{ item.label }}</span>\n </RouterLink>\n </nav>\n </header>\n</template>\n\n<style scoped>\n@reference \"../styles/_reference.css\";\n\n/* absolute, not fixed: the bar hangs inside the app shell. Fixed would pin it\n to the browser window, which on a desktop is nowhere near the app. */\n.tab-bar {\n @apply absolute left-1/2 z-40 w-full max-w-[360px] -translate-x-1/2 px-4;\n bottom: calc(1rem + env(safe-area-inset-bottom, 0px));\n}\n\n.tab-bar-inner {\n @apply border-hair bg-surface/85 flex items-center justify-between gap-1 border p-1.5 shadow-lg backdrop-blur-md;\n border-radius: var(--radius-shell);\n}\n\n.tab-link {\n @apply text-ink-soft flex min-h-[52px] flex-1 cursor-pointer flex-col items-center justify-center gap-1 py-1.5;\n border-radius: calc(var(--radius-shell) - 6px);\n /* Only the icon reacts to a press. Scaling the whole link drags the label and\n the pill with it, which reads as the bar wobbling. */\n transition: color 200ms ease;\n}\n\n.tab-link:hover {\n @apply text-ink;\n}\n\n/* The pill sits behind the icon rather than the link, so the active tab grows a\n marker instead of the row changing shape. */\n.tab-icon-slot {\n @apply flex h-7 w-12 items-center justify-center rounded-full transition-all duration-200 ease-out;\n}\n\n.tab-link:active .tab-icon-slot {\n transform: scale(0.88);\n}\n\n.is-active {\n @apply text-primary;\n}\n\n.is-active .tab-icon-slot {\n @apply bg-muted;\n}\n\n.tab-icon {\n @apply size-[18px] stroke-2 transition-transform duration-200;\n}\n\n.is-active .tab-icon {\n @apply scale-110 stroke-[2.5px];\n}\n\n.tab-label {\n @apply text-[10px] leading-none font-medium;\n}\n\n.is-active .tab-label {\n @apply font-semibold;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tab-icon-slot,\n .tab-icon {\n transition: none;\n }\n .tab-link:active .tab-icon-slot {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\" generic=\"K extends string\">\nimport { RouterLink } from 'vue-router'\nimport type { Component } from 'vue'\n\nimport { tapFeedback } from '../utils/haptics'\n\nexport interface TabItem<K extends string> {\n /** Identity, compared against `active`. */\n key: K\n /** Router destination. */\n to: string\n /** Text under the icon. Already translated. */\n label: string\n icon: Component\n}\n\n/**\n * The floating bottom bar.\n *\n * Items and the active key are props: the package has no opinion about how an\n * app names its screens, and reading `route.meta` here would force one.\n */\nconst {\n items,\n active,\n label = '',\n} = defineProps<{\n items: readonly TabItem<K>[]\n /** Which item is current. Usually from `route.meta`. */\n active?: K | undefined\n /** Accessible name for the navigation landmark. */\n label?: string\n}>()\n</script>\n\n<template>\n <header class=\"tab-bar\">\n <nav class=\"tab-bar-inner\" :aria-label=\"label || undefined\">\n <RouterLink\n v-for=\"item in items\"\n :key=\"item.key\"\n :to=\"item.to\"\n class=\"tab-link\"\n :class=\"{ 'is-active': item.key === active }\"\n :aria-current=\"item.key === active ? 'page' : undefined\"\n @click=\"tapFeedback()\"\n >\n <span class=\"tab-icon-slot\">\n <component :is=\"item.icon\" class=\"tab-icon\" />\n </span>\n <span class=\"tab-label\">{{ item.label }}</span>\n </RouterLink>\n </nav>\n </header>\n</template>\n\n<style scoped>\n@reference \"../styles/_reference.css\";\n\n/* absolute, not fixed: the bar hangs inside the app shell. Fixed would pin it\n to the browser window, which on a desktop is nowhere near the app. */\n.tab-bar {\n @apply absolute left-1/2 z-40 w-full max-w-[360px] -translate-x-1/2 px-4;\n bottom: calc(1rem + env(safe-area-inset-bottom, 0px));\n}\n\n.tab-bar-inner {\n @apply border-hair bg-surface/85 flex items-center justify-between gap-1 border p-1.5 shadow-lg backdrop-blur-md;\n border-radius: var(--radius-shell);\n}\n\n.tab-link {\n @apply text-ink-soft flex min-h-[52px] flex-1 cursor-pointer flex-col items-center justify-center gap-1 py-1.5;\n border-radius: calc(var(--radius-shell) - 6px);\n /* Only the icon reacts to a press. Scaling the whole link drags the label and\n the pill with it, which reads as the bar wobbling. */\n transition: color 200ms ease;\n}\n\n.tab-link:hover {\n @apply text-ink;\n}\n\n/* The pill sits behind the icon rather than the link, so the active tab grows a\n marker instead of the row changing shape. */\n.tab-icon-slot {\n @apply flex h-7 w-12 items-center justify-center rounded-full transition-all duration-200 ease-out;\n}\n\n.tab-link:active .tab-icon-slot {\n transform: scale(0.88);\n}\n\n.is-active {\n @apply text-primary;\n}\n\n.is-active .tab-icon-slot {\n @apply bg-muted;\n}\n\n.tab-icon {\n @apply size-[18px] stroke-2 transition-transform duration-200;\n}\n\n.is-active .tab-icon {\n @apply scale-110 stroke-[2.5px];\n}\n\n.tab-label {\n @apply text-[10px] leading-none font-medium;\n}\n\n.is-active .tab-label {\n @apply font-semibold;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tab-icon-slot,\n .tab-icon {\n transition: none;\n }\n .tab-link:active .tab-icon-slot {\n transform: none;\n }\n}\n</style>\n","import { computed, ref, watchEffect } from 'vue'\nimport { createI18n } from 'vue-i18n'\n\nimport { setFormatLocale } from '../utils/format'\n\n/** What the user picked. `system` re-reads the browser on every launch. */\nexport type LocalePreference<L extends string> = 'system' | L\n\nexport interface I18nRuntimeOptions<L extends string, Schema> {\n /** Languages the app ships, in no particular order. */\n locales: readonly L[]\n /** The one that is always loaded, and the fallback when a load fails. */\n fallback: L\n /**\n * BCP 47 tag per locale, for `Intl`.\n *\n * Message lookup only needs the base language, but dates and numbers need a\n * region to be right — `zh` alone would leave the formatter to guess.\n */\n intlTags: Record<L, string>\n /** The fallback's messages, bundled. */\n messages: Schema\n /** The rest, fetched only when they are the one in use. */\n loaders?: Partial<Record<L, () => Promise<{ default: Schema }>>>\n /** Where the choice is stored. Namespace it per app. */\n storageKey?: string\n}\n\n/**\n * Builds an i18n runtime around an app's own catalogue.\n *\n * A factory rather than a module singleton because the schema is the app's:\n * typing every locale as `typeof en` is what makes a missing key a build error,\n * and this package has no `en` of its own to type against.\n *\n * @example\n * ```ts\n * export const { i18n, t, useLocalePreference, loadActiveLocale } =\n * createI18nRuntime({\n * locales: ['en', 'tr'] as const,\n * fallback: 'en',\n * intlTags: { en: 'en-GB', tr: 'tr-TR' },\n * messages: en,\n * loaders: { tr: () => import('./locales/tr') },\n * storageKey: 'myapp-locale',\n * })\n * ```\n */\nexport function createI18nRuntime<L extends string, Schema extends Record<string, unknown>>(\n options: I18nRuntimeOptions<L, Schema>,\n) {\n const { locales, fallback, intlTags, messages, storageKey = 'rei-locale' } = options\n\n // Typed rather than defaulted to `{}`, which erases the locale keys and makes\n // `loaders[locale]` an index into an empty object.\n const loaders: Partial<Record<L, () => Promise<{ default: Schema }>>> = options.loaders ?? {}\n\n function isSupported(value: string): value is L {\n return (locales as readonly string[]).includes(value)\n }\n\n /**\n * First browser language the app can actually speak.\n *\n * `navigator.languages` is ordered by the user's own preference, so the first\n * match is the best one — not simply the first entry.\n */\n function detectSystemLocale(): L {\n // No browser to ask. The fallback is the right answer on a server: it is\n // the locale whose messages are bundled, so it is the only one that could\n // render without a load.\n //\n // The test is `document`, not `navigator`. Node has shipped a global\n // `navigator` since v21, so `typeof navigator === 'undefined'` is false on\n // a server and this would read the *build machine's* language and bake it\n // into every prerendered page. `document` is the only one of the two that\n // still means \"a browser\".\n if (typeof document === 'undefined') return fallback\n\n for (const tag of navigator.languages ?? [navigator.language]) {\n const base = tag.split('-')[0]?.toLowerCase()\n if (base && isSupported(base)) return base\n }\n\n return fallback\n }\n\n function readStored(): LocalePreference<L> {\n try {\n const stored = localStorage.getItem(storageKey)\n if (stored === 'system' || (stored && isSupported(stored))) return stored\n } catch {\n // Storage blocked; fall through to the system language.\n }\n\n return 'system'\n }\n\n const preference = ref<LocalePreference<L>>(readStored())\n\n const activeLocale = computed<L>(() =>\n preference.value === 'system' ? detectSystemLocale() : (preference.value as L),\n )\n\n const intlLocale = computed(() => intlTags[activeLocale.value])\n\n // Only the fallback at construction; the rest arrive through\n // setLocaleMessage.\n const initial = { [fallback]: messages } as Record<string, Record<string, unknown>>\n\n const i18n = createI18n({\n legacy: false,\n locale: activeLocale.value as string,\n fallbackLocale: fallback as string,\n messages: initial,\n } as unknown as Parameters<typeof createI18n>[0])\n\n /**\n * A narrow view of the instance.\n *\n * vue-i18n infers its own generics from the messages it is handed, which\n * fights a runtime that is generic over the app's schema. Casting once, here,\n * keeps that fight out of every call site — and the surface below is the\n * whole of what this runtime uses.\n */\n const core = i18n.global as unknown as {\n locale: { value: string }\n setLocaleMessage: (locale: string, messages: Schema) => void\n t: (key: string, named?: Record<string, unknown>) => string\n }\n\n const loaded = new Set<L>([fallback])\n\n /**\n * Makes sure a locale's messages are in place before it becomes active.\n *\n * Awaited rather than fired and forgotten: setting the locale first paints one\n * frame of the fallback at every other user, which is the flash a fallback\n * exists to prevent, not cause.\n */\n async function ensureMessages(locale: L): Promise<void> {\n if (loaded.has(locale)) return\n\n const load = loaders[locale]\n if (!load) return\n\n try {\n const module = await load()\n core.setLocaleMessage(locale, module.default)\n loaded.add(locale)\n } catch {\n // Offline, or a stale chunk after a deploy. The fallback is loaded and\n // will carry the UI, which beats a blank screen.\n }\n }\n\n /** Loads whatever the stored preference resolves to. Call before mounting. */\n function loadActiveLocale(): Promise<void> {\n return ensureMessages(activeLocale.value)\n }\n\n // Keeps vue-i18n, `Intl` and the document in step. `lang` matters beyond\n // tidiness: it drives hyphenation, font fallback and screen readers.\n watchEffect(() => {\n core.locale.value = activeLocale.value\n setFormatLocale(intlLocale.value)\n\n if (typeof document !== 'undefined') {\n document.documentElement.lang = activeLocale.value\n }\n })\n\n /** Read and write the language preference. */\n function useLocalePreference() {\n return computed<LocalePreference<L>>({\n get: () => preference.value,\n set: (next) => {\n const resolved = next === 'system' ? detectSystemLocale() : (next as L)\n\n // Messages first, then the switch — the other order shows the fallback\n // for a frame on the way to the language the user just picked.\n void ensureMessages(resolved).then(() => {\n preference.value = next\n })\n\n try {\n localStorage.setItem(storageKey, next)\n } catch {\n // Storage blocked; the choice lasts for this session only.\n }\n },\n })\n }\n\n return {\n i18n,\n /** `t` for code outside a component. Tracks the locale inside a computed. */\n t: core.t,\n activeLocale,\n intlLocale,\n ensureMessages,\n loadActiveLocale,\n useLocalePreference,\n }\n}\n","/**\n * rei-kit — the layer every app starts from.\n *\n * Everything here is free of any backend, router or i18n choice. Components\n * take strings rather than calling a translator, and utilities take the clock\n * rather than reading it, so nothing in this package can force a decision on\n * the app that installs it.\n *\n * @see https://github.com/ramazandogna/rei-kit\n */\n\nexport const VERSION = '0.0.0'\n\n// ── Utilities ──────────────────────────────────────────────────────────────\nexport {\n addDays,\n eachDayOfYear,\n fromDateKey,\n lastNDays,\n leadingBlanks,\n startOfWeek,\n toDateKey,\n todayKey,\n} from './utils/date'\nexport type { WeekStart } from './utils/date'\n\nexport { formatDate, setFormatLocale } from './utils/format'\nexport { relativeDayLabel } from './utils/day-label'\nexport type { DayLabels } from './utils/day-label'\n\nexport { downloadJson } from './utils/download'\nexport { safeRedirect } from './utils/redirect'\nexport type { QueryValue } from './utils/redirect'\nexport { tapFeedback } from './utils/haptics'\nexport { isApplePortable, isInstalled, needsIosInstall } from './utils/platform'\n\nexport { AppError, registerErrorMapper, toAppError } from './utils/app-error'\nexport type { AppErrorKind, ErrorMapper } from './utils/app-error'\n\n// ── Composables ────────────────────────────────────────────────────────────\nexport {\n applyTheme,\n isThemePreference,\n readStoredTheme,\n setThemeStorageKey,\n useTheme,\n} from './composables/use-theme'\nexport type { ThemePreference } from './composables/use-theme'\n\nexport { useToday } from './composables/use-today'\nexport { useOnline } from './composables/use-online'\nexport { useDebouncedCallback } from './composables/use-debounced-callback'\nexport { useDragScroll } from './composables/use-drag-scroll'\nexport { useVisualViewport } from './composables/use-visual-viewport'\nexport type { VisualViewportRect } from './composables/use-visual-viewport'\n\n// ── Components ─────────────────────────────────────────────────────────────\nexport { default as BaseButton } from './components/BaseButton.vue'\nexport { default as BaseInput } from './components/BaseInput.vue'\nexport { default as BaseSheet } from './components/BaseSheet.vue'\nexport { default as EmptyState } from './components/EmptyState.vue'\nexport { default as PageHeader } from './components/PageHeader.vue'\nexport { default as SectionHeading } from './components/SectionHeading.vue'\nexport { default as SegmentedControl } from './components/SegmentedControl.vue'\nexport { default as SettingsGroup } from './components/SettingsGroup.vue'\nexport { default as SettingsRow } from './components/SettingsRow.vue'\nexport { default as SkeletonList } from './components/SkeletonList.vue'\nexport { default as StatCard } from './components/StatCard.vue'\nexport { default as ToneDot } from './components/ToneDot.vue'\nexport type { Tone } from './components/SectionHeading.vue'\nexport { default as LocaleLinks } from './components/LocaleLinks.vue'\nexport { default as GoogleButton } from './components/GoogleButton.vue'\nexport { default as TabBar } from './components/TabBar.vue'\nexport type { TabItem } from './components/TabBar.vue'\n\n// ── i18n ───────────────────────────────────────────────────────────────────\nexport { createI18nRuntime } from './i18n/runtime'\nexport type { I18nRuntimeOptions, LocalePreference } from './i18n/runtime'\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "rei-kit",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Vue 3 and Tailwind 4 design system and shared runtime. Extracted from Hibi.",
5
5
  "license": "MIT",
6
- "author": "Ramazan Do\u011fan",
6
+ "author": "Ramazan Doğan",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/ramazandogna/rei-kit.git"