@celestia-island/hikari 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +54 -0
  2. package/package.json +63 -0
  3. package/src/DemoApp.vue +400 -0
  4. package/src/components/HkActionBar.tsx +15 -0
  5. package/src/components/HkAlert.scss +121 -0
  6. package/src/components/HkAlert.tsx +85 -0
  7. package/src/components/HkAvatar.tsx +29 -0
  8. package/src/components/HkBadge.scss +117 -0
  9. package/src/components/HkBadge.tsx +50 -0
  10. package/src/components/HkBreadcrumb.tsx +39 -0
  11. package/src/components/HkButton.scss +128 -0
  12. package/src/components/HkButton.tsx +74 -0
  13. package/src/components/HkCard.scss +49 -0
  14. package/src/components/HkCard.tsx +38 -0
  15. package/src/components/HkCheckbox.scss +102 -0
  16. package/src/components/HkCheckbox.tsx +85 -0
  17. package/src/components/HkCollapse.tsx +48 -0
  18. package/src/components/HkColorPicker.scss +148 -0
  19. package/src/components/HkColorPicker.tsx +204 -0
  20. package/src/components/HkConfirmDialog.scss +12 -0
  21. package/src/components/HkConfirmDialog.tsx +75 -0
  22. package/src/components/HkDateTimePicker.scss +361 -0
  23. package/src/components/HkDateTimePicker.tsx +424 -0
  24. package/src/components/HkDivider.scss +85 -0
  25. package/src/components/HkDivider.tsx +51 -0
  26. package/src/components/HkDraggableGrid.scss +132 -0
  27. package/src/components/HkDraggableGrid.tsx +291 -0
  28. package/src/components/HkDraggableList.scss +110 -0
  29. package/src/components/HkDraggableList.tsx +238 -0
  30. package/src/components/HkDrawer.scss +175 -0
  31. package/src/components/HkDrawer.tsx +171 -0
  32. package/src/components/HkEmptyState.scss +42 -0
  33. package/src/components/HkEmptyState.tsx +41 -0
  34. package/src/components/HkErrorBoundary.scss +64 -0
  35. package/src/components/HkErrorBoundary.tsx +82 -0
  36. package/src/components/HkGaugeRing.scss +4 -0
  37. package/src/components/HkGaugeRing.tsx +130 -0
  38. package/src/components/HkHoverRevealAction.scss +66 -0
  39. package/src/components/HkHoverRevealAction.tsx +77 -0
  40. package/src/components/HkIcon.tsx +43 -0
  41. package/src/components/HkIconButton.tsx +42 -0
  42. package/src/components/HkImageViewer.scss +33 -0
  43. package/src/components/HkImageViewer.tsx +250 -0
  44. package/src/components/HkInput.scss +172 -0
  45. package/src/components/HkInput.tsx +175 -0
  46. package/src/components/HkKbd.tsx +26 -0
  47. package/src/components/HkKeywordSearchModal.scss +148 -0
  48. package/src/components/HkKeywordSearchModal.tsx +308 -0
  49. package/src/components/HkListTransition.scss +131 -0
  50. package/src/components/HkListTransition.tsx +29 -0
  51. package/src/components/HkLocalePickerPopup.tsx +42 -0
  52. package/src/components/HkLogo.scss +36 -0
  53. package/src/components/HkLogo.tsx +93 -0
  54. package/src/components/HkMarkdownRenderer.scss +141 -0
  55. package/src/components/HkMarkdownRenderer.tsx +123 -0
  56. package/src/components/HkMediaControlBar.scss +65 -0
  57. package/src/components/HkMediaControlBar.tsx +115 -0
  58. package/src/components/HkMediaPlayer.scss +29 -0
  59. package/src/components/HkMediaPlayer.tsx +210 -0
  60. package/src/components/HkMediaSlider.scss +54 -0
  61. package/src/components/HkMediaSlider.tsx +81 -0
  62. package/src/components/HkMediaVisualizer.scss +7 -0
  63. package/src/components/HkMediaVisualizer.tsx +119 -0
  64. package/src/components/HkMinimap.scss +76 -0
  65. package/src/components/HkMinimap.tsx +255 -0
  66. package/src/components/HkModal.scss +318 -0
  67. package/src/components/HkModal.tsx +481 -0
  68. package/src/components/HkModalBreadcrumb.scss +55 -0
  69. package/src/components/HkModalBreadcrumb.tsx +113 -0
  70. package/src/components/HkMorphingTabs.scss +141 -0
  71. package/src/components/HkMorphingTabs.tsx +113 -0
  72. package/src/components/HkNavItem.scss +81 -0
  73. package/src/components/HkNavItem.tsx +157 -0
  74. package/src/components/HkNumberInput.scss +154 -0
  75. package/src/components/HkNumberInput.tsx +161 -0
  76. package/src/components/HkPasswordInput.scss +289 -0
  77. package/src/components/HkPasswordInput.tsx +533 -0
  78. package/src/components/HkPhaseTransition.scss +25 -0
  79. package/src/components/HkPhaseTransition.tsx +40 -0
  80. package/src/components/HkPopover.scss +82 -0
  81. package/src/components/HkPopover.tsx +410 -0
  82. package/src/components/HkPopupSelect.scss +136 -0
  83. package/src/components/HkPopupSelect.tsx +199 -0
  84. package/src/components/HkProgressBar.scss +106 -0
  85. package/src/components/HkProgressBar.tsx +61 -0
  86. package/src/components/HkProgressDialog.tsx +67 -0
  87. package/src/components/HkProgressRing.scss +56 -0
  88. package/src/components/HkProgressRing.tsx +82 -0
  89. package/src/components/HkRadio.scss +119 -0
  90. package/src/components/HkRadio.tsx +75 -0
  91. package/src/components/HkRollingNumber.scss +37 -0
  92. package/src/components/HkRollingNumber.tsx +97 -0
  93. package/src/components/HkScrollContainer.scss +101 -0
  94. package/src/components/HkScrollContainer.tsx +362 -0
  95. package/src/components/HkSearchInput.scss +132 -0
  96. package/src/components/HkSearchInput.tsx +117 -0
  97. package/src/components/HkSelect.scss +143 -0
  98. package/src/components/HkSelect.tsx +230 -0
  99. package/src/components/HkSelectionGrid.scss +177 -0
  100. package/src/components/HkSelectionGrid.tsx +99 -0
  101. package/src/components/HkSelectionWaterfall.scss +93 -0
  102. package/src/components/HkSelectionWaterfall.tsx +90 -0
  103. package/src/components/HkSidebar.scss +81 -0
  104. package/src/components/HkSidebar.tsx +45 -0
  105. package/src/components/HkSkeleton.scss +44 -0
  106. package/src/components/HkSkeleton.tsx +30 -0
  107. package/src/components/HkSkeletonList.tsx +35 -0
  108. package/src/components/HkSpinner.scss +67 -0
  109. package/src/components/HkSpinner.tsx +41 -0
  110. package/src/components/HkSplash.scss +60 -0
  111. package/src/components/HkSplash.tsx +40 -0
  112. package/src/components/HkSwitch.scss +157 -0
  113. package/src/components/HkSwitch.tsx +72 -0
  114. package/src/components/HkTable.scss +198 -0
  115. package/src/components/HkTable.tsx +246 -0
  116. package/src/components/HkTabs.scss +109 -0
  117. package/src/components/HkTabs.tsx +111 -0
  118. package/src/components/HkTag.scss +88 -0
  119. package/src/components/HkTag.tsx +44 -0
  120. package/src/components/HkTextarea.scss +72 -0
  121. package/src/components/HkTextarea.tsx +104 -0
  122. package/src/components/HkTimeline.scss +139 -0
  123. package/src/components/HkTimeline.tsx +95 -0
  124. package/src/components/HkToast.scss +168 -0
  125. package/src/components/HkToast.tsx +175 -0
  126. package/src/components/HkTooltip.scss +47 -0
  127. package/src/components/HkTooltip.tsx +108 -0
  128. package/src/components/HkTree.scss +188 -0
  129. package/src/components/HkTree.tsx +217 -0
  130. package/src/components/HkTrendChart.tsx +180 -0
  131. package/src/components/HkWindowedItem.scss +17 -0
  132. package/src/components/HkWindowedItem.tsx +107 -0
  133. package/src/components/HkZoomToolbar.scss +35 -0
  134. package/src/components/HkZoomToolbar.tsx +69 -0
  135. package/src/composables/useBreakpoint.ts +41 -0
  136. package/src/composables/useClipboard.ts +37 -0
  137. package/src/composables/useConfirm.ts +35 -0
  138. package/src/composables/useProgressDialog.ts +52 -0
  139. package/src/composables/useReportedTransition.ts +69 -0
  140. package/src/composables/useScrollWindow.ts +21 -0
  141. package/src/composables/useToast.ts +2 -0
  142. package/src/demo.scss +24 -0
  143. package/src/demo.ts +22 -0
  144. package/src/env.d.ts +6 -0
  145. package/src/i18n/context.ts +89 -0
  146. package/src/i18n/locales/ar/components.json +43 -0
  147. package/src/i18n/locales/ar/components.toml +40 -0
  148. package/src/i18n/locales/de/components.json +43 -0
  149. package/src/i18n/locales/de/components.toml +40 -0
  150. package/src/i18n/locales/en/components.json +43 -0
  151. package/src/i18n/locales/en/components.toml +40 -0
  152. package/src/i18n/locales/es/components.json +43 -0
  153. package/src/i18n/locales/es/components.toml +40 -0
  154. package/src/i18n/locales/fr/components.json +43 -0
  155. package/src/i18n/locales/fr/components.toml +40 -0
  156. package/src/i18n/locales/ja/components.json +43 -0
  157. package/src/i18n/locales/ja/components.toml +40 -0
  158. package/src/i18n/locales/ko/components.json +43 -0
  159. package/src/i18n/locales/ko/components.toml +40 -0
  160. package/src/i18n/locales/pt/components.json +43 -0
  161. package/src/i18n/locales/pt/components.toml +40 -0
  162. package/src/i18n/locales/ru/components.json +43 -0
  163. package/src/i18n/locales/ru/components.toml +40 -0
  164. package/src/i18n/locales/zh-Hans/components.json +43 -0
  165. package/src/i18n/locales/zh-Hans/components.toml +40 -0
  166. package/src/i18n/locales/zh-Hant/components.json +43 -0
  167. package/src/i18n/locales/zh-Hant/components.toml +40 -0
  168. package/src/index.ts +137 -0
  169. package/src/render.ts +18 -0
  170. package/src/runtime/animationBus.ts +278 -0
  171. package/src/runtime/cronBus.ts +25 -0
  172. package/src/runtime/index.ts +12 -0
  173. package/src/runtime/useAsyncData.ts +30 -0
  174. package/src/runtime/useBreakpoint.ts +62 -0
  175. package/src/runtime/useClipboard.ts +37 -0
  176. package/src/runtime/useConfirm.ts +35 -0
  177. package/src/runtime/useOverlay.ts +82 -0
  178. package/src/runtime/usePopupManager.ts +89 -0
  179. package/src/runtime/useToast.ts +140 -0
  180. package/src/styles/index.scss +10 -0
  181. package/src/theme/index.ts +5 -0
  182. package/src/theme/presets.ts +307 -0
  183. package/src/theme/theme.scss +17 -0
  184. package/src/theme/useBackgroundLuminance.ts +145 -0
  185. package/src/theme/useSolarTime.ts +101 -0
  186. package/src/theme/useTheme.ts +141 -0
  187. package/src/tokens.scss +26 -0
@@ -0,0 +1,140 @@
1
+ import { reactive } from "vue";
2
+
3
+ import { scheduleCronAfter, type CronHandle } from "./cronBus";
4
+
5
+ export type ToastType = "error" | "success" | "warning" | "info" | "loading";
6
+
7
+ export const TOAST_DURATION: Record<ToastType, number> = {
8
+ error: 30_000,
9
+ warning: 30_000,
10
+ success: 3_000,
11
+ info: 3_000,
12
+ loading: 0,
13
+ };
14
+
15
+ export function isTransient(type: ToastType): boolean {
16
+ return type === "success" || type === "info";
17
+ }
18
+
19
+ export function defaultCopyable(type: ToastType): boolean {
20
+ return type === "error" || type === "warning";
21
+ }
22
+
23
+ export interface ToastMessage {
24
+ id: number;
25
+ text: string;
26
+ }
27
+
28
+ export interface ToastItem {
29
+ id: number;
30
+ type: ToastType;
31
+ messages: ToastMessage[];
32
+ duration?: number;
33
+ copyable?: boolean;
34
+ }
35
+
36
+ const state = reactive<{ toasts: ToastItem[] }>({ toasts: [] });
37
+
38
+ let nextSlotId = 0;
39
+ let nextMsgId = 0;
40
+
41
+ const timers = new Map<number, CronHandle>();
42
+
43
+ function clearTimer(slotId: number) {
44
+ const t = timers.get(slotId);
45
+ if (t !== undefined) {
46
+ t.disconnect();
47
+ timers.delete(slotId);
48
+ }
49
+ }
50
+
51
+ function removeSlot(slotId: number) {
52
+ const idx = state.toasts.findIndex((t) => t.id === slotId);
53
+ if (idx !== -1) state.toasts.splice(idx, 1);
54
+ clearTimer(slotId);
55
+ }
56
+
57
+ function scheduleAutoDismiss(slot: ToastItem) {
58
+ const duration = slot.duration ?? 0;
59
+ if (duration <= 0) return;
60
+ clearTimer(slot.id);
61
+ timers.set(
62
+ slot.id,
63
+ scheduleCronAfter(() => removeSlot(slot.id), duration),
64
+ );
65
+ }
66
+
67
+ function findSlot(type: ToastType): ToastItem | undefined {
68
+ return state.toasts.find((t) => t.type === type);
69
+ }
70
+
71
+ function push(
72
+ type: ToastType,
73
+ text: string,
74
+ options: { duration?: number; copyable?: boolean } = {},
75
+ ): number {
76
+ let slot = findSlot(type);
77
+ if (!slot) {
78
+ slot = {
79
+ id: ++nextSlotId,
80
+ type,
81
+ messages: [],
82
+ duration: options.duration ?? TOAST_DURATION[type],
83
+ copyable: options.copyable ?? defaultCopyable(type),
84
+ };
85
+ state.toasts.push(slot);
86
+ } else if (options.copyable === true && !slot.copyable) {
87
+ slot.copyable = true;
88
+ }
89
+ const msgId = ++nextMsgId;
90
+ slot.messages.push({ id: msgId, text });
91
+ scheduleAutoDismiss(slot);
92
+ return msgId;
93
+ }
94
+
95
+ export function useToast() {
96
+ function show(
97
+ message: string,
98
+ options: Partial<Omit<ToastItem, "id" | "messages">> = {},
99
+ ) {
100
+ const type = options.type || "info";
101
+ return push(type, message, {
102
+ duration: options.duration,
103
+ copyable: options.copyable,
104
+ });
105
+ }
106
+
107
+ function error(message: string, copyable = true) {
108
+ return push("error", message, { copyable });
109
+ }
110
+ function success(message: string) {
111
+ return push("success", message);
112
+ }
113
+ function warning(message: string) {
114
+ return push("warning", message);
115
+ }
116
+ function info(message: string) {
117
+ return push("info", message);
118
+ }
119
+ function loading(message: string, maxDuration = 0) {
120
+ const opts: { duration?: number } = {};
121
+ if (maxDuration > 0) opts.duration = maxDuration;
122
+ return push("loading", message, opts);
123
+ }
124
+
125
+ function remove(id: number) {
126
+ const slotById = state.toasts.find((t) => t.id === id);
127
+ if (slotById) {
128
+ removeSlot(slotById.id);
129
+ return;
130
+ }
131
+ for (const slot of state.toasts) {
132
+ if (slot.messages.some((m) => m.id === id)) {
133
+ removeSlot(slot.id);
134
+ return;
135
+ }
136
+ }
137
+ }
138
+
139
+ return { toasts: state.toasts, show, error, success, warning, info, loading, remove };
140
+ }
@@ -0,0 +1,10 @@
1
+ // Re-export shared hikari theme and component styles.
2
+ // NOTE: The `../../../theme/...` and `../../../components/...` imports below
3
+ // resolve relative to the monorepo root and work locally. When published to
4
+ // npm, consumers who need the full hikari SCSS stack should import this file
5
+ // from a monorepo checkout or bring their own SCSS load-path resolution.
6
+ @use "../../../theme/styles/variables.scss";
7
+ @use "../../../theme/styles/mixins.scss";
8
+ @use "../../../theme/styles/foundation.scss";
9
+ @use "../../../theme/styles/themes.scss";
10
+ @use "../../../components/src/styles/index.scss";
@@ -0,0 +1,5 @@
1
+ export { initTheme, useTheme } from "./useTheme";
2
+ export { themePresets, tokensToCSSVars, getThemeTokens, loadCustomThemes, saveCustomThemes, addCustomTheme, removeCustomTheme } from "./presets";
3
+ export type { ThemeTokenRGB, ThemeSchemeTokens, ThemePreset, CustomThemePreset, ThemeId, ThemeMode } from "./presets";
4
+ export { getTimePeriod, getGeolocation, solarAltitude, DEFAULT_GEO_LOCATION } from "./useSolarTime";
5
+ export type { TimePeriod } from "./useSolarTime";
@@ -0,0 +1,307 @@
1
+ export interface ThemeTokenRGB {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ }
6
+
7
+ export interface ThemeSchemeTokens {
8
+ primary: ThemeTokenRGB;
9
+ secondary: ThemeTokenRGB;
10
+ accent: ThemeTokenRGB;
11
+ text: ThemeTokenRGB;
12
+ muted: ThemeTokenRGB;
13
+ border: ThemeTokenRGB;
14
+ focusedBorder: ThemeTokenRGB;
15
+ background: ThemeTokenRGB;
16
+ surface: ThemeTokenRGB;
17
+ selectedBackground: ThemeTokenRGB;
18
+ selectedText: ThemeTokenRGB;
19
+ statusBarBackground: ThemeTokenRGB;
20
+ success: ThemeTokenRGB;
21
+ error: ThemeTokenRGB;
22
+ warning: ThemeTokenRGB;
23
+ info: ThemeTokenRGB;
24
+ }
25
+
26
+ export interface ThemePreset {
27
+ id: string;
28
+ name: string;
29
+ dark: ThemeSchemeTokens;
30
+ light: ThemeSchemeTokens;
31
+ }
32
+
33
+ export type ThemeId = string;
34
+ export type ThemeMode = "system" | "light" | "dark";
35
+
36
+ export interface CustomThemePreset {
37
+ id: string;
38
+ name: string;
39
+ dark: ThemeSchemeTokens;
40
+ light: ThemeSchemeTokens;
41
+ }
42
+
43
+ const CUSTOM_STORAGE_KEY = "hikari-custom-themes";
44
+
45
+ export function loadCustomThemes(): CustomThemePreset[] {
46
+ try {
47
+ const raw = localStorage.getItem(CUSTOM_STORAGE_KEY);
48
+ return raw ? JSON.parse(raw) : [];
49
+ } catch {
50
+ return [];
51
+ }
52
+ }
53
+
54
+ export function saveCustomThemes(list: CustomThemePreset[]) {
55
+ localStorage.setItem(CUSTOM_STORAGE_KEY, JSON.stringify(list));
56
+ }
57
+
58
+ export function addCustomTheme(theme: CustomThemePreset) {
59
+ const list = loadCustomThemes();
60
+ const idx = list.findIndex((t) => t.id === theme.id);
61
+ if (idx >= 0) {
62
+ list[idx] = theme;
63
+ } else {
64
+ list.push(theme);
65
+ }
66
+ saveCustomThemes(list);
67
+ }
68
+
69
+ export function removeCustomTheme(id: string) {
70
+ const list = loadCustomThemes().filter((t) => t.id !== id);
71
+ saveCustomThemes(list);
72
+ }
73
+
74
+ const rgb = (r: number, g: number, b: number): ThemeTokenRGB => ({ r, g, b });
75
+
76
+ export const themePresets: Record<ThemeId, ThemePreset> = {
77
+ synthwave84: {
78
+ id: "synthwave84",
79
+ name: "Synthwave '84",
80
+ dark: {
81
+ primary: rgb(255, 107, 157),
82
+ secondary: rgb(199, 146, 234),
83
+ accent: rgb(253, 235, 139),
84
+ text: rgb(228, 228, 231),
85
+ muted: rgb(180, 180, 180),
86
+ border: rgb(255, 255, 255),
87
+ focusedBorder: rgb(255, 107, 157),
88
+ background: rgb(14, 14, 30),
89
+ surface: rgb(24, 24, 42),
90
+ selectedBackground: rgb(70, 70, 85),
91
+ selectedText: rgb(240, 240, 240),
92
+ statusBarBackground: rgb(24, 24, 42),
93
+ success: rgb(114, 241, 184),
94
+ error: rgb(255, 107, 107),
95
+ warning: rgb(253, 235, 139),
96
+ info: rgb(110, 231, 239),
97
+ },
98
+ light: {
99
+ primary: rgb(214, 51, 132),
100
+ secondary: rgb(156, 106, 222),
101
+ accent: rgb(230, 167, 0),
102
+ text: rgb(30, 30, 30),
103
+ muted: rgb(80, 80, 80),
104
+ border: rgb(0, 0, 0),
105
+ focusedBorder: rgb(214, 51, 132),
106
+ background: rgb(245, 245, 240),
107
+ surface: rgb(255, 255, 255),
108
+ selectedBackground: rgb(200, 200, 205),
109
+ selectedText: rgb(40, 40, 45),
110
+ statusBarBackground: rgb(230, 230, 225),
111
+ success: rgb(16, 185, 129),
112
+ error: rgb(239, 68, 68),
113
+ warning: rgb(245, 158, 11),
114
+ info: rgb(6, 182, 212),
115
+ },
116
+ },
117
+ nord: {
118
+ id: "nord",
119
+ name: "Nord",
120
+ dark: {
121
+ primary: rgb(136, 192, 208),
122
+ secondary: rgb(143, 188, 187),
123
+ accent: rgb(94, 129, 172),
124
+ text: rgb(236, 239, 244),
125
+ muted: rgb(180, 180, 180),
126
+ border: rgb(255, 255, 255),
127
+ focusedBorder: rgb(136, 192, 208),
128
+ background: rgb(22, 27, 38),
129
+ surface: rgb(34, 40, 54),
130
+ selectedBackground: rgb(70, 70, 85),
131
+ selectedText: rgb(240, 240, 240),
132
+ statusBarBackground: rgb(34, 40, 54),
133
+ success: rgb(163, 190, 140),
134
+ error: rgb(191, 97, 106),
135
+ warning: rgb(208, 135, 112),
136
+ info: rgb(136, 192, 208),
137
+ },
138
+ light: {
139
+ primary: rgb(94, 129, 172),
140
+ secondary: rgb(136, 192, 208),
141
+ accent: rgb(143, 188, 187),
142
+ text: rgb(30, 30, 30),
143
+ muted: rgb(80, 80, 80),
144
+ border: rgb(0, 0, 0),
145
+ focusedBorder: rgb(94, 129, 172),
146
+ background: rgb(236, 239, 244),
147
+ surface: rgb(229, 233, 240),
148
+ selectedBackground: rgb(200, 200, 205),
149
+ selectedText: rgb(40, 40, 45),
150
+ statusBarBackground: rgb(220, 224, 232),
151
+ success: rgb(163, 190, 140),
152
+ error: rgb(191, 97, 106),
153
+ warning: rgb(208, 135, 112),
154
+ info: rgb(136, 192, 208),
155
+ },
156
+ },
157
+ gruvbox: {
158
+ id: "gruvbox",
159
+ name: "Gruvbox",
160
+ dark: {
161
+ primary: rgb(251, 189, 84),
162
+ secondary: rgb(177, 161, 134),
163
+ accent: rgb(214, 160, 115),
164
+ text: rgb(235, 219, 178),
165
+ muted: rgb(180, 180, 180),
166
+ border: rgb(255, 255, 255),
167
+ focusedBorder: rgb(251, 189, 84),
168
+ background: rgb(20, 20, 20),
169
+ surface: rgb(34, 32, 30),
170
+ selectedBackground: rgb(70, 70, 85),
171
+ selectedText: rgb(240, 240, 240),
172
+ statusBarBackground: rgb(34, 32, 30),
173
+ success: rgb(169, 195, 85),
174
+ error: rgb(251, 118, 118),
175
+ warning: rgb(251, 189, 84),
176
+ info: rgb(131, 191, 152),
177
+ },
178
+ light: {
179
+ primary: rgb(204, 128, 49),
180
+ secondary: rgb(168, 135, 81),
181
+ accent: rgb(186, 128, 82),
182
+ text: rgb(30, 30, 30),
183
+ muted: rgb(80, 80, 80),
184
+ border: rgb(0, 0, 0),
185
+ focusedBorder: rgb(204, 128, 49),
186
+ background: rgb(251, 241, 199),
187
+ surface: rgb(242, 229, 188),
188
+ selectedBackground: rgb(200, 200, 205),
189
+ selectedText: rgb(40, 40, 45),
190
+ statusBarBackground: rgb(235, 225, 185),
191
+ success: rgb(121, 153, 50),
192
+ error: rgb(204, 69, 57),
193
+ warning: rgb(204, 128, 49),
194
+ info: rgb(70, 120, 104),
195
+ },
196
+ },
197
+ tokyonight: {
198
+ id: "tokyonight",
199
+ name: "Tokyo Night",
200
+ dark: {
201
+ primary: rgb(122, 162, 247),
202
+ secondary: rgb(125, 207, 255),
203
+ accent: rgb(247, 118, 142),
204
+ text: rgb(192, 202, 245),
205
+ muted: rgb(180, 180, 180),
206
+ border: rgb(255, 255, 255),
207
+ focusedBorder: rgb(122, 162, 247),
208
+ background: rgb(14, 15, 24),
209
+ surface: rgb(22, 24, 36),
210
+ selectedBackground: rgb(70, 70, 85),
211
+ selectedText: rgb(240, 240, 240),
212
+ statusBarBackground: rgb(22, 24, 36),
213
+ success: rgb(158, 206, 106),
214
+ error: rgb(247, 118, 142),
215
+ warning: rgb(255, 183, 87),
216
+ info: rgb(125, 207, 255),
217
+ },
218
+ light: {
219
+ primary: rgb(52, 96, 189),
220
+ secondary: rgb(56, 157, 192),
221
+ accent: rgb(225, 72, 96),
222
+ text: rgb(30, 30, 30),
223
+ muted: rgb(80, 80, 80),
224
+ border: rgb(0, 0, 0),
225
+ focusedBorder: rgb(52, 96, 189),
226
+ background: rgb(231, 233, 241),
227
+ surface: rgb(221, 223, 231),
228
+ selectedBackground: rgb(200, 200, 205),
229
+ selectedText: rgb(40, 40, 45),
230
+ statusBarBackground: rgb(215, 217, 227),
231
+ success: rgb(86, 159, 86),
232
+ error: rgb(225, 72, 96),
233
+ warning: rgb(206, 145, 60),
234
+ info: rgb(56, 157, 192),
235
+ },
236
+ },
237
+ };
238
+
239
+ export type ThemeTokens = ThemeSchemeTokens;
240
+
241
+ export function getThemeTokens(
242
+ name: string,
243
+ mode: string,
244
+ ): ThemeSchemeTokens | null {
245
+ const preset = themePresets[name as keyof typeof themePresets];
246
+ if (preset) return mode === "dark" ? preset.dark : preset.light;
247
+ return null;
248
+ }
249
+
250
+ function contrastColor({ r, g, b }: ThemeTokenRGB): ThemeTokenRGB {
251
+ const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
252
+ return luminance > 0.55 ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 };
253
+ }
254
+
255
+ function mixToken(a: ThemeTokenRGB, b: ThemeTokenRGB, t: number): ThemeTokenRGB {
256
+ return {
257
+ r: Math.round(a.r + (b.r - a.r) * t),
258
+ g: Math.round(a.g + (b.g - a.g) * t),
259
+ b: Math.round(a.b + (b.b - a.b) * t),
260
+ };
261
+ }
262
+
263
+ export function tokensToCSSVars(
264
+ tokens: ThemeSchemeTokens,
265
+ ): Record<string, string> {
266
+ const onPrimary = contrastColor(tokens.primary);
267
+ const textSecondary = mixToken(tokens.text, tokens.muted, 0.25);
268
+ return {
269
+ "--color-primary": `${tokens.primary.r} ${tokens.primary.g} ${tokens.primary.b}`,
270
+ "--color-on-primary": `${onPrimary.r} ${onPrimary.g} ${onPrimary.b}`,
271
+ "--color-on-solid": "255 255 255",
272
+ "--color-secondary": `${tokens.secondary.r} ${tokens.secondary.g} ${tokens.secondary.b}`,
273
+ "--color-accent": `${tokens.accent.r} ${tokens.accent.g} ${tokens.accent.b}`,
274
+ "--color-text": `${tokens.text.r} ${tokens.text.g} ${tokens.text.b}`,
275
+ "--color-text-secondary": `${textSecondary.r} ${textSecondary.g} ${textSecondary.b}`,
276
+ "--color-muted": `${tokens.muted.r} ${tokens.muted.g} ${tokens.muted.b}`,
277
+ "--color-border": `${tokens.border.r} ${tokens.border.g} ${tokens.border.b}`,
278
+ "--color-focused-border": `${tokens.focusedBorder.r} ${tokens.focusedBorder.g} ${tokens.focusedBorder.b}`,
279
+ "--color-background": `${tokens.background.r} ${tokens.background.g} ${tokens.background.b}`,
280
+ "--color-surface": `${tokens.surface.r} ${tokens.surface.g} ${tokens.surface.b}`,
281
+ "--color-selected-bg": `${tokens.selectedBackground.r} ${tokens.selectedBackground.g} ${tokens.selectedBackground.b}`,
282
+ "--color-selected-text": `${tokens.selectedText.r} ${tokens.selectedText.g} ${tokens.selectedText.b}`,
283
+ "--color-status-bar-bg": `${tokens.statusBarBackground.r} ${tokens.statusBarBackground.g} ${tokens.statusBarBackground.b}`,
284
+ "--color-success": `${tokens.success.r} ${tokens.success.g} ${tokens.success.b}`,
285
+ "--color-error": `${tokens.error.r} ${tokens.error.g} ${tokens.error.b}`,
286
+ "--color-warning": `${tokens.warning.r} ${tokens.warning.g} ${tokens.warning.b}`,
287
+ "--color-info": `${tokens.info.r} ${tokens.info.g} ${tokens.info.b}`,
288
+ "--hi-color-primary": "rgb(var(--color-primary))",
289
+ "--hi-color-secondary": "rgb(var(--color-secondary))",
290
+ "--hi-color-surface": "rgb(var(--color-surface))",
291
+ "--hi-color-background": "rgb(var(--color-background))",
292
+ "--hi-color-border": "rgb(var(--color-border))",
293
+ "--hi-color-text-primary": "rgb(var(--color-text))",
294
+ "--hi-color-text-secondary": "rgb(var(--color-text-secondary))",
295
+ "--hi-color-muted": "rgb(var(--color-muted))",
296
+ "--hi-color-on-primary": "rgb(var(--color-on-primary))",
297
+ "--hi-color-text-on-primary": "rgb(var(--color-on-primary))",
298
+ "--hi-color-success": "rgb(var(--color-success))",
299
+ "--hi-color-error": "rgb(var(--color-error))",
300
+ "--hi-color-warning": "rgb(var(--color-warning))",
301
+ "--hi-color-info": "rgb(var(--color-info))",
302
+ "--hi-color-bg-subtle": "rgb(var(--color-surface) / 0.5)",
303
+ "--hi-color-bg-elevated": "rgb(var(--color-surface))",
304
+ "--hi-color-bg-canvas": "rgb(var(--color-background))",
305
+ "--hi-secondary-bg": "rgb(var(--color-surface) / 0.5)",
306
+ };
307
+ }
@@ -0,0 +1,17 @@
1
+ // Essential theme CSS variables for hikari-vue components
2
+ // Color/radius/space/text tokens are NOT defined here —
3
+ // they come from the consumer's theme bridge.
4
+ // Component SCSS has fallback values in every var() call.
5
+
6
+ :root {
7
+ --hi-z-above: 1;
8
+ --hi-z-tooltip: 10000;
9
+ }
10
+
11
+ [data-mode="dark"] body {
12
+ color-scheme: dark;
13
+ }
14
+
15
+ [data-mode="light"] body {
16
+ color-scheme: light;
17
+ }
@@ -0,0 +1,145 @@
1
+ import { onceFrame, onFrame, type AnimationHandle } from "../runtime/animationBus";
2
+
3
+ const CVAR = "--float-text-color";
4
+ const CVAR_BADGE = "--float-badge-color";
5
+
6
+ let handle: AnimationHandle | null = null;
7
+ let cachedText: string | null = null;
8
+ let cachedPrimary: string | null = null;
9
+ let pendingPBO: { buffer: WebGLBuffer; gl: WebGL2RenderingContext } | null = null;
10
+
11
+ function luminance(r: number, g: number, b: number): number {
12
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
13
+ }
14
+
15
+ function applyLuminance(lum: number) {
16
+ const el = document.documentElement;
17
+ if (lum < 0.45) {
18
+ el.style.setProperty(CVAR, "255, 255, 255");
19
+ el.style.setProperty(CVAR_BADGE, "255, 255, 255");
20
+ } else {
21
+ if (cachedText === null || cachedPrimary === null) {
22
+ const cs = getComputedStyle(el);
23
+ cachedText = cs.getPropertyValue("--color-text").trim();
24
+ cachedPrimary = cs.getPropertyValue("--color-primary").trim();
25
+ }
26
+ el.style.setProperty(CVAR, cachedText);
27
+ el.style.setProperty(CVAR_BADGE, cachedPrimary);
28
+ }
29
+ }
30
+
31
+ function sampleShader(): boolean {
32
+ const canvas = document.getElementById("s-wallpaper-canvas") as HTMLCanvasElement | null;
33
+ if (!canvas) return false;
34
+ const gl = canvas.getContext("webgl2");
35
+ if (!gl) return false;
36
+
37
+ let applied = false;
38
+
39
+ if (pendingPBO && pendingPBO.gl === gl) {
40
+ try {
41
+ const data = new Uint8Array(4);
42
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pendingPBO.buffer);
43
+ gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data, 0, 4);
44
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
45
+ gl.deleteBuffer(pendingPBO.buffer);
46
+ applyLuminance(luminance(data[0], data[1], data[2]));
47
+ applied = true;
48
+ } catch {
49
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
50
+ gl.deleteBuffer(pendingPBO.buffer);
51
+ }
52
+ }
53
+ pendingPBO = null;
54
+
55
+ const pbo = gl.createBuffer();
56
+ if (pbo) {
57
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
58
+ gl.bufferData(gl.PIXEL_PACK_BUFFER, 4, gl.STREAM_READ);
59
+ const cx = Math.floor(canvas.width / 2);
60
+ const cy = Math.floor(canvas.height / 2);
61
+ gl.readPixels(cx, cy, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, 0);
62
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
63
+ pendingPBO = { buffer: pbo, gl };
64
+ }
65
+
66
+ return applied;
67
+ }
68
+
69
+ function sampleMedia(el: HTMLImageElement | HTMLVideoElement): boolean {
70
+ try {
71
+ let w: number, h: number;
72
+ if (el instanceof HTMLImageElement) {
73
+ w = el.naturalWidth || 1;
74
+ h = el.naturalHeight || 1;
75
+ } else {
76
+ w = el.videoWidth || 1;
77
+ h = el.videoHeight || 1;
78
+ }
79
+ w = Math.min(w, 64);
80
+ h = Math.min(h, 64);
81
+ const c = document.createElement("canvas");
82
+ c.width = 1;
83
+ c.height = 1;
84
+ const ctx = c.getContext("2d");
85
+ if (!ctx) return false;
86
+ ctx.drawImage(el, w / 2, h / 2, 1, 1, 0, 0, 1, 1);
87
+ const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
88
+ applyLuminance(luminance(r, g, b));
89
+ return true;
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ function sampleFromBodyBg(): boolean {
96
+ const raw = getComputedStyle(document.body).backgroundColor;
97
+ const m = raw.match(/\d+/g);
98
+ if (!m || m.length < 3) return false;
99
+ applyLuminance(luminance(+m[0], +m[1], +m[2]));
100
+ return true;
101
+ }
102
+
103
+ function sampleFrame() {
104
+ if (sampleShader()) return;
105
+ const video = document.getElementById("s-wallpaper-video") as HTMLVideoElement | null;
106
+ if (video && video.readyState >= 2 && sampleMedia(video)) return;
107
+ const bgImg = getComputedStyle(document.body).backgroundImage;
108
+ if (bgImg && bgImg !== "none") {
109
+ const urlMatch = bgImg.match(/url\(["']?(.*?)["']?\)/);
110
+ if (urlMatch) {
111
+ const img = document.createElement("img");
112
+ img.crossOrigin = "anonymous";
113
+ img.src = urlMatch[1];
114
+ img.onload = () => { sampleMedia(img); };
115
+ return;
116
+ }
117
+ }
118
+ sampleFromBodyBg();
119
+ }
120
+
121
+ export function startLuminanceSampler() {
122
+ stopLuminanceSampler();
123
+ sampleFrame();
124
+ handle = onFrame(() => sampleFrame(), "idle");
125
+ }
126
+
127
+ export function stopLuminanceSampler() {
128
+ if (handle) {
129
+ handle.disconnect();
130
+ handle = null;
131
+ }
132
+ if (pendingPBO) {
133
+ try { pendingPBO.gl.deleteBuffer(pendingPBO.buffer); } catch {}
134
+ pendingPBO = null;
135
+ }
136
+ }
137
+
138
+ export function sampleLuminanceNow() {
139
+ onceFrame(() => sampleFrame());
140
+ }
141
+
142
+ export function invalidateLuminanceCache() {
143
+ cachedText = null;
144
+ cachedPrimary = null;
145
+ }