@hraness/design-kit 0.31.1
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/ARTICLE_COPY.md +208 -0
- package/HERO_FIELDS.md +55 -0
- package/ICONS.md +56 -0
- package/LANTERN_MATERIAL.md +60 -0
- package/LICENSE +21 -0
- package/MARKETING_COPY.md +49 -0
- package/MARKETING_PRESET.md +108 -0
- package/PALETTES.md +90 -0
- package/PAPER_THEME.md +128 -0
- package/README.md +745 -0
- package/STATUS_PAGES.md +67 -0
- package/STYLE.md +245 -0
- package/WRITING.md +72 -0
- package/dist/browser/index.js +2345 -0
- package/dist/chunk-44djxk16.js +6872 -0
- package/dist/chunk-5gtx3pza.js +9 -0
- package/dist/chunk-77391vmq.js +454 -0
- package/dist/chunk-7b1586eb.js +1209 -0
- package/dist/chunk-8834fh4n.js +6 -0
- package/dist/chunk-9t8xyqte.js +870 -0
- package/dist/chunk-cejpzyfh.js +446 -0
- package/dist/chunk-eh71jz57.js +429 -0
- package/dist/chunk-evb02bc1.js +516 -0
- package/dist/chunk-h4k7yv6x.js +368 -0
- package/dist/chunk-he8eznb1.js +760 -0
- package/dist/chunk-mr1vdcjq.js +404 -0
- package/dist/chunk-wzvdn8ey.js +116 -0
- package/dist/fonts/nebula-sans/social-fonts.generated.js +53 -0
- package/dist/icons.js +48 -0
- package/dist/index.js +237 -0
- package/dist/launch.js +41 -0
- package/dist/mockups/client.js +422 -0
- package/dist/mockups/index.js +1850 -0
- package/dist/portfolio.js +1549 -0
- package/dist/provider-marks.js +19 -0
- package/dist/react/charts.js +11 -0
- package/dist/react/hero-backdrop.js +8 -0
- package/dist/react/index.js +7252 -0
- package/dist/react/platform-install.js +10 -0
- package/dist/react/server.js +137 -0
- package/dist/stylex-manifest.json +1 -0
- package/dist/stylex.css +5392 -0
- package/dist/syntax-highlighting.js +15 -0
- package/dist/testing.js +250 -0
- package/package.json +377 -0
- package/portfolio-inventory.json +31 -0
- package/scripts/check-lantern-material-snapshot.d.mts +26 -0
- package/scripts/check-lantern-material-snapshot.mjs +126 -0
- package/scripts/check-marketing-snapshot.d.mts +12 -0
- package/scripts/check-marketing-snapshot.mjs +58 -0
- package/scripts/lantern-material-snapshot.ts +101 -0
- package/scripts/marketing-textures.ts +48 -0
- package/scripts/paper-theme-snapshot.ts +97 -0
- package/scripts/product-marketing-snapshot.ts +149 -0
- package/src/appearance-menu.css +266 -0
- package/src/appearance.ts +32 -0
- package/src/article-html.ts +269 -0
- package/src/article.ts +686 -0
- package/src/browser/appearance-menu.ts +604 -0
- package/src/browser/artifact-share.ts +164 -0
- package/src/browser/design-palette.ts +197 -0
- package/src/browser/foil.ts +124 -0
- package/src/browser/hero-light.ts +11 -0
- package/src/browser/index.ts +29 -0
- package/src/browser/status-field.ts +432 -0
- package/src/browser/status-page.ts +100 -0
- package/src/browser/sticky-offset.ts +82 -0
- package/src/browser/theme-color-sync.ts +213 -0
- package/src/charts.css +7 -0
- package/src/compiler-components.css +34 -0
- package/src/compiler-foundation.css +18 -0
- package/src/compiler-palettes.css +7 -0
- package/src/compiler-tokens.css +342 -0
- package/src/components.css +3 -0
- package/src/design-gallery.css +431 -0
- package/src/effects.css +14 -0
- package/src/fonts/geist-mono/GeistMono[wght].woff2 +0 -0
- package/src/fonts/geist-mono/OFL.txt +93 -0
- package/src/fonts/geist-mono/PROVENANCE.md +12 -0
- package/src/fonts/instrument-serif/OFL.txt +93 -0
- package/src/fonts/instrument-serif/UPSTREAM.md +14 -0
- package/src/fonts/instrument-serif/instrument-serif-latin-400.woff2 +0 -0
- package/src/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/fonts/nebula-sans/NebulaSans-Black.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-BlackItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-BoldItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-BookItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Light.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-LightItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Medium.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-MediumItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Semibold.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-SemiboldItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/fonts/nebula-sans/social-fonts.generated.ts +3873 -0
- package/src/fonts.css +106 -0
- package/src/icons/act60/act60.svg +5 -0
- package/src/icons/act60/application.svg +9 -0
- package/src/icons/act60/chapter-compare.svg +3 -0
- package/src/icons/act60/decree-residence.svg +6 -0
- package/src/icons/act60/estimate.svg +7 -0
- package/src/icons/act60/export-services.svg +4 -0
- package/src/icons/act60/investor.svg +13 -0
- package/src/icons/act60/presence.svg +7 -0
- package/src/icons/aicharts/aicharts.svg +6 -0
- package/src/icons/aicharts/cost-compare.svg +4 -0
- package/src/icons/aicharts/task-audio.svg +7 -0
- package/src/icons/aicharts/task-coding.svg +6 -0
- package/src/icons/aicharts/task-images.svg +3 -0
- package/src/icons/aicharts/task-reasoning.svg +9 -0
- package/src/icons/aicharts/task-research.svg +5 -0
- package/src/icons/aicharts/task-video.svg +4 -0
- package/src/icons/manifest.json +1371 -0
- package/src/icons/platonik/conversation.svg +3 -0
- package/src/icons/platonik/creature.svg +7 -0
- package/src/icons/platonik/economy.svg +4 -0
- package/src/icons/platonik/keep-light.svg +6 -0
- package/src/icons/platonik/platonik.svg +7 -0
- package/src/icons/platonik/spark-route.svg +15 -0
- package/src/icons/receipts/act60-retry.receipt.json +888 -0
- package/src/icons/receipts/act60.receipt.json +1366 -0
- package/src/icons/receipts/aicharts.receipt.json +1546 -0
- package/src/icons/receipts/platonik.receipt.json +1011 -0
- package/src/icons/receipts/roughday.receipt.json +770 -0
- package/src/icons/receipts/shared.receipt.json +1296 -0
- package/src/icons/receipts/slopcamera.receipt.json +1495 -0
- package/src/icons/receipts/soundfish-retry.receipt.json +460 -0
- package/src/icons/receipts/soundfish.receipt.json +1709 -0
- package/src/icons/receipts/sponge.receipt.json +1743 -0
- package/src/icons/receipts/stripe-history-retry.receipt.json +145 -0
- package/src/icons/receipts/stripe-history.receipt.json +1202 -0
- package/src/icons/receipts/wordcell.receipt.json +1924 -0
- package/src/icons/roughday/finance.svg +7 -0
- package/src/icons/roughday/roughday.svg +6 -0
- package/src/icons/roughday/technology.svg +4 -0
- package/src/icons/roughday/world.svg +4 -0
- package/src/icons/sets/act60.json +63 -0
- package/src/icons/sets/aicharts.json +63 -0
- package/src/icons/sets/platonik.json +39 -0
- package/src/icons/sets/roughday.json +25 -0
- package/src/icons/sets/shared.json +12 -0
- package/src/icons/sets/slopcamera.json +47 -0
- package/src/icons/sets/soundfish.json +43 -0
- package/src/icons/sets/sponge.json +51 -0
- package/src/icons/sets/stripe-history.json +39 -0
- package/src/icons/sets/wordcell.json +51 -0
- package/src/icons/shared/agent-skill.svg +4 -0
- package/src/icons/shared/cli.svg +5 -0
- package/src/icons/shared/privacy.svg +3 -0
- package/src/icons/shared/research.svg +7 -0
- package/src/icons/shared/sdk.svg +3 -0
- package/src/icons/slopcamera/diagram.svg +3 -0
- package/src/icons/slopcamera/direct-scene.svg +5 -0
- package/src/icons/slopcamera/edit-video.svg +17 -0
- package/src/icons/slopcamera/mcp.svg +3 -0
- package/src/icons/slopcamera/native-world.svg +3 -0
- package/src/icons/slopcamera/slopcamera.svg +11 -0
- package/src/icons/soundfish/agent-ui.svg +7 -0
- package/src/icons/soundfish/collaborate.svg +5 -0
- package/src/icons/soundfish/compose.svg +3 -0
- package/src/icons/soundfish/hear.svg +6 -0
- package/src/icons/soundfish/human-ui.svg +6 -0
- package/src/icons/soundfish/share.svg +3 -0
- package/src/icons/soundfish/soundfish.svg +6 -0
- package/src/icons/sponge/api.svg +3 -0
- package/src/icons/sponge/documents.svg +3 -0
- package/src/icons/sponge/library.svg +3 -0
- package/src/icons/sponge/one-question.svg +5 -0
- package/src/icons/sponge/people-direct.svg +5 -0
- package/src/icons/sponge/read.svg +3 -0
- package/src/icons/sponge/save.svg +4 -0
- package/src/icons/sponge/sponge.svg +7 -0
- package/src/icons/stripe-history/company-history.svg +3 -0
- package/src/icons/stripe-history/evidence.svg +4 -0
- package/src/icons/stripe-history/independence.svg +7 -0
- package/src/icons/stripe-history/publications.svg +11 -0
- package/src/icons/stripe-history/sources.svg +6 -0
- package/src/icons/stripe-history/stripe-history.svg +3 -0
- package/src/icons/wordcell/backlinks.svg +6 -0
- package/src/icons/wordcell/capture.svg +6 -0
- package/src/icons/wordcell/git-provenance.svg +4 -0
- package/src/icons/wordcell/kb.svg +4 -0
- package/src/icons/wordcell/markdown.svg +4 -0
- package/src/icons/wordcell/scopes.svg +3 -0
- package/src/icons/wordcell/search.svg +7 -0
- package/src/icons.generated.ts +734 -0
- package/src/icons.ts +51 -0
- package/src/index.ts +275 -0
- package/src/lantern-material.css +213 -0
- package/src/launch.ts +624 -0
- package/src/marketing-assets/UPSTREAM.md +18 -0
- package/src/marketing-assets/cells.svg +1 -0
- package/src/marketing-assets/grain.svg +1 -0
- package/src/marketing-forced-colors.css +12 -0
- package/src/mockups/client.tsx +415 -0
- package/src/mockups/core.tsx +318 -0
- package/src/mockups/frames.tsx +368 -0
- package/src/mockups/index.ts +14 -0
- package/src/mockups/surfaces.tsx +681 -0
- package/src/mockups.css +2607 -0
- package/src/palette-appearance.ts +45 -0
- package/src/palette-bridge.css +124 -0
- package/src/palette-color.ts +41 -0
- package/src/palette-system.css +721 -0
- package/src/palette-themes.ts +23 -0
- package/src/palette-tokens.stylex.ts +519 -0
- package/src/palettes.css +3 -0
- package/src/palettes.ts +161 -0
- package/src/paper-theme.css +244 -0
- package/src/plain-publication.css +1241 -0
- package/src/plain-site.css +397 -0
- package/src/platforms.ts +151 -0
- package/src/portfolio.generated.json +1595 -0
- package/src/portfolio.generated.ts +1596 -0
- package/src/portfolio.ts +226 -0
- package/src/product-marketing-foundation.css +433 -0
- package/src/product-marketing-preset.css +261 -0
- package/src/product-marketing.css +2186 -0
- package/src/provider-marks.generated.ts +112 -0
- package/src/provider-marks.ts +143 -0
- package/src/react/animated-rail-stage.stylex.ts +15 -0
- package/src/react/animated-rail-stage.tsx +51 -0
- package/src/react/app-shell.stylex.ts +104 -0
- package/src/react/app-shell.tsx +121 -0
- package/src/react/article.tsx +602 -0
- package/src/react/aurora-dots-background.tsx +34 -0
- package/src/react/charts.stylex.ts +563 -0
- package/src/react/charts.tsx +610 -0
- package/src/react/chat.stylex.ts +34 -0
- package/src/react/chat.tsx +155 -0
- package/src/react/design-gallery.tsx +943 -0
- package/src/react/design-palette.stylex.ts +124 -0
- package/src/react/design-palette.tsx +196 -0
- package/src/react/design-theme-context.tsx +41 -0
- package/src/react/effects.stylex.ts +325 -0
- package/src/react/fader.stylex.ts +71 -0
- package/src/react/fader.tsx +151 -0
- package/src/react/foil-card-math.ts +100 -0
- package/src/react/foil-card-surface.stylex.ts +326 -0
- package/src/react/foil-card-surface.tsx +742 -0
- package/src/react/foil-mark.tsx +35 -0
- package/src/react/foil.stylex.ts +163 -0
- package/src/react/haptics.ts +228 -0
- package/src/react/hero-backdrop.tsx +22 -0
- package/src/react/index.ts +38 -0
- package/src/react/keyboard-shortcuts.ts +228 -0
- package/src/react/lantern-material-gallery.tsx +166 -0
- package/src/react/lantern-material.stylex.ts +39 -0
- package/src/react/launch-beats.tsx +73 -0
- package/src/react/navigation-rail.stylex.ts +130 -0
- package/src/react/navigation-rail.tsx +228 -0
- package/src/react/particle-halo.tsx +115 -0
- package/src/react/phaser-dots.tsx +501 -0
- package/src/react/platform-icons.tsx +94 -0
- package/src/react/platform-install.stylex.ts +385 -0
- package/src/react/platform-install.tsx +389 -0
- package/src/react/playback-transport.stylex.ts +14 -0
- package/src/react/playback-transport.tsx +112 -0
- package/src/react/procedural-backdrop.tsx +192 -0
- package/src/react/procedural-recipe.ts +323 -0
- package/src/react/product-marketing.stylex.ts +4469 -0
- package/src/react/product-marketing.tsx +1611 -0
- package/src/react/production-data-preview-notice.stylex.ts +40 -0
- package/src/react/production-data-preview-notice.tsx +34 -0
- package/src/react/provider-mark.stylex.ts +132 -0
- package/src/react/provider-mark.tsx +120 -0
- package/src/react/relative-time.tsx +117 -0
- package/src/react/route-state.stylex.ts +39 -0
- package/src/react/route-state.tsx +350 -0
- package/src/react/server.ts +11 -0
- package/src/react/social-kit-panel.tsx +103 -0
- package/src/react/sticky-offset.tsx +33 -0
- package/src/react/surfaces.stylex.ts +179 -0
- package/src/react/surfaces.tsx +330 -0
- package/src/react/syntax-code.tsx +33 -0
- package/src/react/theme-color-sync.ts +1 -0
- package/src/react/theme-resolution.ts +11 -0
- package/src/react/theme.stylex.ts +276 -0
- package/src/react/theme.tsx +402 -0
- package/src/reading.css +72 -0
- package/src/relative-time.ts +185 -0
- package/src/reset.css +2 -0
- package/src/site-shell.css +16 -0
- package/src/status-page-html.ts +63 -0
- package/src/status-page.css +276 -0
- package/src/status-page.ts +323 -0
- package/src/styles.css +21 -0
- package/src/syntax-code-lexers.ts +303 -0
- package/src/syntax-highlighting.css +78 -0
- package/src/syntax-highlighting.ts +477 -0
- package/src/syntax-tokens.ts +33 -0
- package/src/testing.ts +307 -0
- package/src/tokens.css +3 -0
- package/src/typography.css +86 -0
- package/vendor/evilcharts/LICENSE +21 -0
- package/vendor/evilcharts/UPSTREAM.md +14 -0
- package/vendor/platform-marks/LICENSE +30 -0
- package/vendor/platform-marks/UPSTREAM.md +15 -0
- package/vendor/platform-marks/apple.svg +1 -0
- package/vendor/platform-marks/linux.svg +1 -0
- package/vendor/provider-marks/CRUSH-LICENSE.md +134 -0
- package/vendor/provider-marks/LICENSE +21 -0
- package/vendor/provider-marks/UPSTREAM.md +27 -0
- package/vendor/provider-marks/aider.svg +3 -0
- package/vendor/provider-marks/crush-heartbit.svg +8 -0
- package/vendor/provider-marks/lobehub/alibabacloud-color.svg +1 -0
- package/vendor/provider-marks/lobehub/alibabacloud.svg +1 -0
- package/vendor/provider-marks/lobehub/amp-color.svg +1 -0
- package/vendor/provider-marks/lobehub/amp.svg +1 -0
- package/vendor/provider-marks/lobehub/anthropic.svg +1 -0
- package/vendor/provider-marks/lobehub/claude-color.svg +1 -0
- package/vendor/provider-marks/lobehub/claude.svg +1 -0
- package/vendor/provider-marks/lobehub/claudecode-color.svg +1 -0
- package/vendor/provider-marks/lobehub/claudecode.svg +1 -0
- package/vendor/provider-marks/lobehub/codex-color.svg +1 -0
- package/vendor/provider-marks/lobehub/codex.svg +1 -0
- package/vendor/provider-marks/lobehub/cursor.svg +1 -0
- package/vendor/provider-marks/lobehub/deepseek-color.svg +1 -0
- package/vendor/provider-marks/lobehub/deepseek.svg +1 -0
- package/vendor/provider-marks/lobehub/devin-color.svg +1 -0
- package/vendor/provider-marks/lobehub/devin.svg +1 -0
- package/vendor/provider-marks/lobehub/gemini-color.svg +1 -0
- package/vendor/provider-marks/lobehub/gemini.svg +1 -0
- package/vendor/provider-marks/lobehub/geminicli-color.svg +1 -0
- package/vendor/provider-marks/lobehub/geminicli.svg +1 -0
- package/vendor/provider-marks/lobehub/goose.svg +1 -0
- package/vendor/provider-marks/lobehub/meta-color.svg +1 -0
- package/vendor/provider-marks/lobehub/meta.svg +1 -0
- package/vendor/provider-marks/lobehub/mistral-color.svg +1 -0
- package/vendor/provider-marks/lobehub/mistral.svg +1 -0
- package/vendor/provider-marks/lobehub/moonshot.svg +1 -0
- package/vendor/provider-marks/lobehub/nvidia-color.svg +1 -0
- package/vendor/provider-marks/lobehub/nvidia.svg +1 -0
- package/vendor/provider-marks/lobehub/openai.svg +1 -0
- package/vendor/provider-marks/lobehub/opencode.svg +1 -0
- package/vendor/provider-marks/lobehub/perplexity-color.svg +1 -0
- package/vendor/provider-marks/lobehub/perplexity.svg +1 -0
- package/vendor/provider-marks/lobehub/qwen-color.svg +1 -0
- package/vendor/provider-marks/lobehub/qwen.svg +1 -0
- package/vendor/provider-marks/lobehub/xai.svg +1 -0
- package/vendor/provider-marks/lobehub/zai.svg +1 -0
|
@@ -0,0 +1,2345 @@
|
|
|
1
|
+
// src/appearance.ts
|
|
2
|
+
var designThemes = ["light", "dark", "system"];
|
|
3
|
+
var defaultDesignTheme = "system";
|
|
4
|
+
var designThemeStorageKey = "hraness-design-theme-v1";
|
|
5
|
+
function isDesignTheme(value) {
|
|
6
|
+
return typeof value === "string" && designThemes.some((theme) => theme === value);
|
|
7
|
+
}
|
|
8
|
+
function normalizeDesignTheme(value) {
|
|
9
|
+
return isDesignTheme(value) ? value : defaultDesignTheme;
|
|
10
|
+
}
|
|
11
|
+
function designThemeLabel(theme, labels) {
|
|
12
|
+
return labels?.[theme] ?? `${theme.charAt(0).toUpperCase()}${theme.slice(1)}`;
|
|
13
|
+
}
|
|
14
|
+
function resolveDesignTheme(theme, systemPrefersDark) {
|
|
15
|
+
return theme === "system" ? systemPrefersDark ? "dark" : "light" : theme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/browser/appearance-menu.ts
|
|
19
|
+
import { ComputerIcon, Moon02Icon, Sun03Icon } from "@hugeicons/core-free-icons";
|
|
20
|
+
|
|
21
|
+
// src/browser/theme-color-sync.ts
|
|
22
|
+
var themeColorSyncActiveAttribute = "data-hraness-design-theme-color-sync-active";
|
|
23
|
+
var themeColorSyncDisabledAttribute = "data-hraness-design-theme-color-sync-disabled";
|
|
24
|
+
var managersByDocument = new WeakMap;
|
|
25
|
+
var ownerSequence = 0;
|
|
26
|
+
function exactThemeColorMetas(manager) {
|
|
27
|
+
return Array.from(manager.document.head.querySelectorAll("meta[name]")).filter((meta) => meta.name === manager.metaName);
|
|
28
|
+
}
|
|
29
|
+
function currentRegisteredColor(manager) {
|
|
30
|
+
let color;
|
|
31
|
+
for (const registeredColor of manager.registrations.values())
|
|
32
|
+
color = registeredColor;
|
|
33
|
+
if (color === undefined)
|
|
34
|
+
throw new Error("Theme color synchronization has no active owner.");
|
|
35
|
+
return color;
|
|
36
|
+
}
|
|
37
|
+
function restoreDisabledMeta(meta, original) {
|
|
38
|
+
if (original.media === null)
|
|
39
|
+
meta.removeAttribute("media");
|
|
40
|
+
else
|
|
41
|
+
meta.setAttribute("media", original.media);
|
|
42
|
+
meta.removeAttribute(themeColorSyncDisabledAttribute);
|
|
43
|
+
}
|
|
44
|
+
function createActiveMeta(manager) {
|
|
45
|
+
const meta = manager.document.createElement("meta");
|
|
46
|
+
meta.name = manager.metaName;
|
|
47
|
+
meta.content = currentRegisteredColor(manager);
|
|
48
|
+
meta.setAttribute(themeColorSyncActiveAttribute, manager.owner);
|
|
49
|
+
manager.activeMetas.add(meta);
|
|
50
|
+
const first = exactThemeColorMetas(manager).find((candidate) => candidate.parentElement === manager.document.head);
|
|
51
|
+
manager.document.head.insertBefore(meta, first ?? null);
|
|
52
|
+
return meta;
|
|
53
|
+
}
|
|
54
|
+
function activeMetaIsOwned(manager) {
|
|
55
|
+
const active = manager.activeMeta;
|
|
56
|
+
return active !== null && active.parentElement === manager.document.head && active.name === manager.metaName && !active.hasAttribute("media") && active.getAttribute(themeColorSyncActiveAttribute) === manager.owner;
|
|
57
|
+
}
|
|
58
|
+
function disableCompetingMeta(manager, meta) {
|
|
59
|
+
if (manager.disabledMetas.has(meta)) {
|
|
60
|
+
if (meta.getAttribute(themeColorSyncDisabledAttribute) !== manager.owner) {
|
|
61
|
+
meta.setAttribute(themeColorSyncDisabledAttribute, manager.owner);
|
|
62
|
+
}
|
|
63
|
+
if (meta.getAttribute("media") !== "not all")
|
|
64
|
+
meta.setAttribute("media", "not all");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const ownedBy = meta.getAttribute(themeColorSyncDisabledAttribute);
|
|
68
|
+
if (ownedBy !== null || meta.getAttribute("media")?.trim().toLowerCase() === "not all") {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
manager.disabledMetas.set(meta, {
|
|
72
|
+
media: meta.getAttribute("media")
|
|
73
|
+
});
|
|
74
|
+
meta.setAttribute(themeColorSyncDisabledAttribute, manager.owner);
|
|
75
|
+
meta.setAttribute("media", "not all");
|
|
76
|
+
}
|
|
77
|
+
function reconcileThemeColorMetas(manager) {
|
|
78
|
+
if (manager.registrations.size === 0)
|
|
79
|
+
return;
|
|
80
|
+
for (const [meta, original] of manager.disabledMetas) {
|
|
81
|
+
if (!manager.document.head.contains(meta) || meta.name !== manager.metaName) {
|
|
82
|
+
restoreDisabledMeta(meta, original);
|
|
83
|
+
manager.disabledMetas.delete(meta);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!activeMetaIsOwned(manager))
|
|
87
|
+
manager.activeMeta = createActiveMeta(manager);
|
|
88
|
+
const active = manager.activeMeta;
|
|
89
|
+
if (active === null)
|
|
90
|
+
return;
|
|
91
|
+
const metas = exactThemeColorMetas(manager);
|
|
92
|
+
const first = metas.find((meta) => meta.parentElement === manager.document.head);
|
|
93
|
+
if (first !== undefined && first !== active)
|
|
94
|
+
manager.document.head.insertBefore(active, first);
|
|
95
|
+
const color = currentRegisteredColor(manager);
|
|
96
|
+
if (active.content !== color)
|
|
97
|
+
active.content = color;
|
|
98
|
+
for (const meta of metas) {
|
|
99
|
+
if (meta !== active)
|
|
100
|
+
disableCompetingMeta(manager, meta);
|
|
101
|
+
}
|
|
102
|
+
observeThemeColorMetas(manager);
|
|
103
|
+
}
|
|
104
|
+
function observeThemeColorMetas(manager) {
|
|
105
|
+
const head = manager.document.head;
|
|
106
|
+
if (manager.observer !== null && manager.observedHead === head)
|
|
107
|
+
return;
|
|
108
|
+
manager.observer?.disconnect();
|
|
109
|
+
manager.observedHead = null;
|
|
110
|
+
const Observer = manager.document.defaultView?.MutationObserver;
|
|
111
|
+
if (Observer === undefined)
|
|
112
|
+
return;
|
|
113
|
+
manager.observer ??= new Observer(() => reconcileThemeColorMetas(manager));
|
|
114
|
+
manager.observer.observe(head, {
|
|
115
|
+
attributeFilter: ["content", "media", "name", themeColorSyncActiveAttribute, themeColorSyncDisabledAttribute],
|
|
116
|
+
attributes: true,
|
|
117
|
+
childList: true,
|
|
118
|
+
subtree: true
|
|
119
|
+
});
|
|
120
|
+
manager.observedHead = head;
|
|
121
|
+
}
|
|
122
|
+
function destroyThemeColorManager(manager) {
|
|
123
|
+
manager.observer?.disconnect();
|
|
124
|
+
manager.observer = null;
|
|
125
|
+
manager.observedHead = null;
|
|
126
|
+
for (const meta of manager.activeMetas)
|
|
127
|
+
meta.remove();
|
|
128
|
+
for (const [meta, original] of manager.disabledMetas) {
|
|
129
|
+
restoreDisabledMeta(meta, original);
|
|
130
|
+
}
|
|
131
|
+
manager.activeMetas.clear();
|
|
132
|
+
manager.disabledMetas.clear();
|
|
133
|
+
manager.activeMeta = null;
|
|
134
|
+
const documentManagers = managersByDocument.get(manager.document);
|
|
135
|
+
if (documentManagers?.get(manager.metaName) === manager) {
|
|
136
|
+
documentManagers.delete(manager.metaName);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function acquireThemeColorMeta(document, metaName, registrationId, color) {
|
|
140
|
+
let documentManagers = managersByDocument.get(document);
|
|
141
|
+
if (documentManagers === undefined) {
|
|
142
|
+
documentManagers = new Map;
|
|
143
|
+
managersByDocument.set(document, documentManagers);
|
|
144
|
+
}
|
|
145
|
+
let manager = documentManagers.get(metaName);
|
|
146
|
+
if (manager === undefined) {
|
|
147
|
+
ownerSequence += 1;
|
|
148
|
+
manager = {
|
|
149
|
+
activeMeta: null,
|
|
150
|
+
activeMetas: new Set,
|
|
151
|
+
disabledMetas: new Map,
|
|
152
|
+
document,
|
|
153
|
+
metaName,
|
|
154
|
+
observer: null,
|
|
155
|
+
observedHead: null,
|
|
156
|
+
owner: String(ownerSequence),
|
|
157
|
+
registrations: new Map
|
|
158
|
+
};
|
|
159
|
+
documentManagers.set(metaName, manager);
|
|
160
|
+
}
|
|
161
|
+
manager.registrations.set(registrationId, color);
|
|
162
|
+
reconcileThemeColorMetas(manager);
|
|
163
|
+
let released = false;
|
|
164
|
+
return {
|
|
165
|
+
release: () => {
|
|
166
|
+
if (released)
|
|
167
|
+
return;
|
|
168
|
+
released = true;
|
|
169
|
+
manager.registrations.delete(registrationId);
|
|
170
|
+
if (manager.registrations.size === 0)
|
|
171
|
+
destroyThemeColorManager(manager);
|
|
172
|
+
else
|
|
173
|
+
reconcileThemeColorMetas(manager);
|
|
174
|
+
},
|
|
175
|
+
update: (nextColor) => {
|
|
176
|
+
if (released || !manager.registrations.has(registrationId))
|
|
177
|
+
return;
|
|
178
|
+
manager.registrations.set(registrationId, nextColor);
|
|
179
|
+
reconcileThemeColorMetas(manager);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/browser/appearance-menu.ts
|
|
185
|
+
var menuSelector = "[data-hraness-appearance-menu]";
|
|
186
|
+
var triggerSelector = ".hraness-design-theme-toggle__trigger";
|
|
187
|
+
var popoverSelector = ".hraness-design-theme-toggle__popover";
|
|
188
|
+
var menuElementSelector = ".hraness-design-theme-toggle__menu";
|
|
189
|
+
var itemSelector = ".hraness-design-theme-toggle__item[data-theme-value]";
|
|
190
|
+
var systemDarkQuery = "(prefers-color-scheme: dark)";
|
|
191
|
+
var svgNamespace = "http://www.w3.org/2000/svg";
|
|
192
|
+
var appearanceIcons = {
|
|
193
|
+
dark: Moon02Icon,
|
|
194
|
+
light: Sun03Icon,
|
|
195
|
+
system: ComputerIcon
|
|
196
|
+
};
|
|
197
|
+
var installationByDocument = new WeakMap;
|
|
198
|
+
function requireNonBlank(value, name) {
|
|
199
|
+
if (value.trim().length === 0)
|
|
200
|
+
throw new Error(`${name} must not be blank.`);
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function resolveDocument(candidate) {
|
|
204
|
+
const ownedDocument = candidate ?? (typeof globalThis.document === "undefined" ? undefined : globalThis.document);
|
|
205
|
+
if (ownedDocument === undefined) {
|
|
206
|
+
throw new Error("installAppearanceMenus requires a browser Document.");
|
|
207
|
+
}
|
|
208
|
+
if (ownedDocument.documentElement === null || ownedDocument.head === null) {
|
|
209
|
+
throw new Error("installAppearanceMenus requires a complete HTML document.");
|
|
210
|
+
}
|
|
211
|
+
return ownedDocument;
|
|
212
|
+
}
|
|
213
|
+
function defaultStorage(document) {
|
|
214
|
+
try {
|
|
215
|
+
return document.defaultView?.localStorage ?? null;
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function storedPreference(storage, storageKey) {
|
|
221
|
+
if (storage === null)
|
|
222
|
+
return defaultDesignTheme;
|
|
223
|
+
try {
|
|
224
|
+
const value = storage.getItem(storageKey);
|
|
225
|
+
if (value === null || value === undefined)
|
|
226
|
+
return defaultDesignTheme;
|
|
227
|
+
if (isDesignTheme(value))
|
|
228
|
+
return value;
|
|
229
|
+
storage.setItem(storageKey, defaultDesignTheme);
|
|
230
|
+
} catch {}
|
|
231
|
+
return defaultDesignTheme;
|
|
232
|
+
}
|
|
233
|
+
function persistPreference(storage, storageKey, preference) {
|
|
234
|
+
if (storage === null)
|
|
235
|
+
return;
|
|
236
|
+
try {
|
|
237
|
+
storage.setItem(storageKey, preference);
|
|
238
|
+
} catch {}
|
|
239
|
+
}
|
|
240
|
+
function isElement(value) {
|
|
241
|
+
return value !== null && typeof value === "object" && "nodeType" in value && value.nodeType === 1;
|
|
242
|
+
}
|
|
243
|
+
function attributeName(name) {
|
|
244
|
+
return name.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`);
|
|
245
|
+
}
|
|
246
|
+
function appearanceIcon(document, theme) {
|
|
247
|
+
const svg = document.createElementNS(svgNamespace, "svg");
|
|
248
|
+
svg.setAttribute("aria-hidden", "true");
|
|
249
|
+
svg.setAttribute("class", "hraness-icon");
|
|
250
|
+
svg.setAttribute("color", "currentColor");
|
|
251
|
+
svg.setAttribute("data-slot", "icon");
|
|
252
|
+
svg.setAttribute("fill", "none");
|
|
253
|
+
svg.setAttribute("height", "18");
|
|
254
|
+
svg.setAttribute("stroke", "currentColor");
|
|
255
|
+
svg.setAttribute("stroke-width", "1.5");
|
|
256
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
257
|
+
svg.setAttribute("width", "18");
|
|
258
|
+
for (const [tag, attributes] of appearanceIcons[theme]) {
|
|
259
|
+
const child = document.createElementNS(svgNamespace, tag);
|
|
260
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
261
|
+
if (name !== "key")
|
|
262
|
+
child.setAttribute(attributeName(name), String(value));
|
|
263
|
+
}
|
|
264
|
+
child.setAttribute("stroke", "currentColor");
|
|
265
|
+
child.setAttribute("stroke-width", "1.5");
|
|
266
|
+
svg.append(child);
|
|
267
|
+
}
|
|
268
|
+
return svg;
|
|
269
|
+
}
|
|
270
|
+
function renderIconHost(document, host, theme) {
|
|
271
|
+
host.classList.add("hraness-appearance-icon");
|
|
272
|
+
host.setAttribute("aria-hidden", "true");
|
|
273
|
+
host.setAttribute("data-appearance-icon", theme);
|
|
274
|
+
host.setAttribute("data-slot", "appearance-icon");
|
|
275
|
+
host.replaceChildren(appearanceIcon(document, theme));
|
|
276
|
+
}
|
|
277
|
+
function themeValue(element) {
|
|
278
|
+
const value = element.getAttribute("data-theme-value");
|
|
279
|
+
return isDesignTheme(value) ? value : null;
|
|
280
|
+
}
|
|
281
|
+
function requireMenuElements(root) {
|
|
282
|
+
const triggers = root.querySelectorAll(triggerSelector);
|
|
283
|
+
const popovers = root.querySelectorAll(popoverSelector);
|
|
284
|
+
const menus = root.querySelectorAll(menuElementSelector);
|
|
285
|
+
const trigger = triggers[0];
|
|
286
|
+
const popover = popovers[0];
|
|
287
|
+
const menu = menus[0];
|
|
288
|
+
if (triggers.length !== 1 || popovers.length !== 1 || menus.length !== 1 || trigger?.tagName !== "BUTTON" || popover === undefined || menu === undefined) {
|
|
289
|
+
throw new Error("Appearance menu markup requires one button trigger, popover, and menu.");
|
|
290
|
+
}
|
|
291
|
+
if (menu.id.trim().length === 0 || trigger.getAttribute("aria-controls") !== menu.id || menu.getAttribute("role") !== "menu") {
|
|
292
|
+
throw new Error("Appearance menu trigger and menu ownership is invalid.");
|
|
293
|
+
}
|
|
294
|
+
const items = new Map;
|
|
295
|
+
for (const element of menu.querySelectorAll(itemSelector)) {
|
|
296
|
+
const theme = themeValue(element);
|
|
297
|
+
if (theme === null || items.has(theme) || element.getAttribute("role") !== "menuitemradio") {
|
|
298
|
+
throw new Error("Appearance menu items must be unique Light, Dark, and System radios.");
|
|
299
|
+
}
|
|
300
|
+
items.set(theme, element);
|
|
301
|
+
}
|
|
302
|
+
if (items.size !== designThemes.length || designThemes.some((theme) => !items.has(theme))) {
|
|
303
|
+
throw new Error("Appearance menu must contain Light, Dark, and System exactly once.");
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
items,
|
|
307
|
+
menu,
|
|
308
|
+
popover,
|
|
309
|
+
root,
|
|
310
|
+
trigger,
|
|
311
|
+
typeahead: "",
|
|
312
|
+
typeaheadTimer: null
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function menuItemFromEvent(elements, event) {
|
|
316
|
+
if (!isElement(event.target))
|
|
317
|
+
return null;
|
|
318
|
+
const item = event.target.closest(itemSelector);
|
|
319
|
+
return item !== null && elements.menu.contains(item) ? item : null;
|
|
320
|
+
}
|
|
321
|
+
function focusItem(elements, theme) {
|
|
322
|
+
elements.items.get(theme)?.focus();
|
|
323
|
+
}
|
|
324
|
+
function orderedItems(elements) {
|
|
325
|
+
return designThemes.flatMap((theme) => {
|
|
326
|
+
const item = elements.items.get(theme);
|
|
327
|
+
return item === undefined ? [] : [item];
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function adjacentItem(elements, current, offset) {
|
|
331
|
+
const items = orderedItems(elements);
|
|
332
|
+
if (items.length === 0)
|
|
333
|
+
throw new Error("Appearance menu has no focusable items.");
|
|
334
|
+
const currentIndex = current === null ? -1 : items.indexOf(current);
|
|
335
|
+
const nextIndex = currentIndex < 0 ? offset === 1 ? 0 : items.length - 1 : (currentIndex + offset + items.length) % items.length;
|
|
336
|
+
const next = items[nextIndex];
|
|
337
|
+
if (next === undefined)
|
|
338
|
+
throw new Error("Appearance menu focus order is invalid.");
|
|
339
|
+
return next;
|
|
340
|
+
}
|
|
341
|
+
function storageEventValue(event, storageKey) {
|
|
342
|
+
const candidate = event;
|
|
343
|
+
return candidate.key === storageKey ? candidate.newValue : undefined;
|
|
344
|
+
}
|
|
345
|
+
function installAppearanceMenus(options) {
|
|
346
|
+
const document = resolveDocument(options.document);
|
|
347
|
+
const active = installationByDocument.get(document);
|
|
348
|
+
if (active !== undefined && !active.disposed) {
|
|
349
|
+
throw new Error("Appearance menus are already installed for this document.");
|
|
350
|
+
}
|
|
351
|
+
const darkThemeColor = requireNonBlank(options.darkThemeColor, "darkThemeColor");
|
|
352
|
+
const lightThemeColor = requireNonBlank(options.lightThemeColor, "lightThemeColor");
|
|
353
|
+
const metaName = requireNonBlank(options.metaName ?? "theme-color", "metaName");
|
|
354
|
+
const storageKey = requireNonBlank(options.storageKey ?? designThemeStorageKey, "storageKey");
|
|
355
|
+
const storage = options.storage === undefined ? defaultStorage(document) : options.storage;
|
|
356
|
+
const view = document.defaultView;
|
|
357
|
+
const systemMedia = typeof view?.matchMedia === "function" ? view.matchMedia(systemDarkQuery) : null;
|
|
358
|
+
let preference = storedPreference(storage, storageKey);
|
|
359
|
+
let disposed = false;
|
|
360
|
+
let mounted = false;
|
|
361
|
+
let openMenu = null;
|
|
362
|
+
let metaRegistration = null;
|
|
363
|
+
const menus = new Set;
|
|
364
|
+
const menuIds = new Set;
|
|
365
|
+
const cleanups = [];
|
|
366
|
+
const resolvedTheme = () => resolveDesignTheme(preference, systemMedia?.matches ?? false);
|
|
367
|
+
const currentThemeColor = () => resolvedTheme() === "dark" ? darkThemeColor : lightThemeColor;
|
|
368
|
+
const closeMenu = (elements, returnFocus) => {
|
|
369
|
+
elements.popover.hidden = true;
|
|
370
|
+
elements.root.removeAttribute("data-open");
|
|
371
|
+
elements.trigger.setAttribute("aria-expanded", "false");
|
|
372
|
+
if (openMenu === elements)
|
|
373
|
+
openMenu = null;
|
|
374
|
+
if (returnFocus)
|
|
375
|
+
elements.trigger.focus();
|
|
376
|
+
};
|
|
377
|
+
const syncMenu = (elements) => {
|
|
378
|
+
elements.root.setAttribute("data-theme-value", preference);
|
|
379
|
+
const currentLabel = designThemeLabel(preference);
|
|
380
|
+
const menuLabel = elements.menu.getAttribute("aria-label")?.trim() || "Appearance";
|
|
381
|
+
elements.trigger.setAttribute("aria-label", `${menuLabel}: ${currentLabel}`);
|
|
382
|
+
elements.trigger.setAttribute("title", `${menuLabel}: ${currentLabel}`);
|
|
383
|
+
const currentHost = elements.trigger.querySelector("[data-current-appearance-icon]");
|
|
384
|
+
if (currentHost === null) {
|
|
385
|
+
throw new Error("Appearance menu trigger is missing its current icon host.");
|
|
386
|
+
}
|
|
387
|
+
currentHost.setAttribute("data-current-appearance-icon", preference);
|
|
388
|
+
renderIconHost(document, currentHost, preference);
|
|
389
|
+
for (const theme of designThemes) {
|
|
390
|
+
const item = elements.items.get(theme);
|
|
391
|
+
if (item === undefined)
|
|
392
|
+
continue;
|
|
393
|
+
const selected = theme === preference;
|
|
394
|
+
item.setAttribute("aria-checked", String(selected));
|
|
395
|
+
item.setAttribute("tabindex", "-1");
|
|
396
|
+
item.toggleAttribute("data-selected", selected);
|
|
397
|
+
const iconHost = item.querySelector("[data-appearance-icon]");
|
|
398
|
+
if (iconHost === null) {
|
|
399
|
+
throw new Error(`Appearance menu ${theme} item is missing its icon host.`);
|
|
400
|
+
}
|
|
401
|
+
renderIconHost(document, iconHost, theme);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const syncAppearance = () => {
|
|
405
|
+
document.documentElement.setAttribute("data-theme", resolvedTheme());
|
|
406
|
+
metaRegistration?.update(currentThemeColor());
|
|
407
|
+
for (const menu of menus)
|
|
408
|
+
syncMenu(menu);
|
|
409
|
+
};
|
|
410
|
+
const setPreference = (nextPreference, persist) => {
|
|
411
|
+
preference = nextPreference;
|
|
412
|
+
if (persist)
|
|
413
|
+
persistPreference(storage, storageKey, preference);
|
|
414
|
+
syncAppearance();
|
|
415
|
+
};
|
|
416
|
+
const focusForOpen = (elements, target) => {
|
|
417
|
+
if (target === "selected")
|
|
418
|
+
focusItem(elements, preference);
|
|
419
|
+
else {
|
|
420
|
+
const items = orderedItems(elements);
|
|
421
|
+
const item = target === "first" ? items[0] : items.at(-1);
|
|
422
|
+
item?.focus();
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
const showMenu = (elements, focus) => {
|
|
426
|
+
if (openMenu !== null && openMenu !== elements)
|
|
427
|
+
closeMenu(openMenu, false);
|
|
428
|
+
openMenu = elements;
|
|
429
|
+
elements.popover.hidden = false;
|
|
430
|
+
elements.root.setAttribute("data-open", "true");
|
|
431
|
+
elements.trigger.setAttribute("aria-expanded", "true");
|
|
432
|
+
focusForOpen(elements, focus);
|
|
433
|
+
};
|
|
434
|
+
const selectItem = (elements, item) => {
|
|
435
|
+
const theme = themeValue(item);
|
|
436
|
+
if (theme === null)
|
|
437
|
+
return;
|
|
438
|
+
setPreference(theme, true);
|
|
439
|
+
closeMenu(elements, true);
|
|
440
|
+
};
|
|
441
|
+
const clearTypeahead = (elements) => {
|
|
442
|
+
elements.typeahead = "";
|
|
443
|
+
if (elements.typeaheadTimer !== null)
|
|
444
|
+
clearTimeout(elements.typeaheadTimer);
|
|
445
|
+
elements.typeaheadTimer = null;
|
|
446
|
+
};
|
|
447
|
+
const typeahead = (elements, key) => {
|
|
448
|
+
if (elements.typeaheadTimer !== null)
|
|
449
|
+
clearTimeout(elements.typeaheadTimer);
|
|
450
|
+
elements.typeahead += key.toLocaleLowerCase();
|
|
451
|
+
const find = (query) => orderedItems(elements).find((item) => (item.textContent ?? "").trim().toLocaleLowerCase().startsWith(query));
|
|
452
|
+
const match = find(elements.typeahead) ?? find(key.toLocaleLowerCase());
|
|
453
|
+
match?.focus();
|
|
454
|
+
elements.typeaheadTimer = setTimeout(() => clearTypeahead(elements), 500);
|
|
455
|
+
};
|
|
456
|
+
const mount = () => {
|
|
457
|
+
if (disposed || mounted)
|
|
458
|
+
return;
|
|
459
|
+
mounted = true;
|
|
460
|
+
for (const root of document.querySelectorAll(menuSelector)) {
|
|
461
|
+
const elements = requireMenuElements(root);
|
|
462
|
+
if (menuIds.has(elements.menu.id)) {
|
|
463
|
+
throw new Error("Appearance menu ids must be unique within the document.");
|
|
464
|
+
}
|
|
465
|
+
menuIds.add(elements.menu.id);
|
|
466
|
+
menus.add(elements);
|
|
467
|
+
syncMenu(elements);
|
|
468
|
+
root.setAttribute("data-display", "icons");
|
|
469
|
+
root.setAttribute("data-presentation", "menu");
|
|
470
|
+
root.setAttribute("data-ready", "true");
|
|
471
|
+
root.removeAttribute("aria-busy");
|
|
472
|
+
elements.trigger.disabled = false;
|
|
473
|
+
elements.trigger.setAttribute("aria-expanded", "false");
|
|
474
|
+
elements.trigger.setAttribute("aria-haspopup", "menu");
|
|
475
|
+
elements.popover.hidden = true;
|
|
476
|
+
const onTriggerClick = () => {
|
|
477
|
+
if (openMenu === elements)
|
|
478
|
+
closeMenu(elements, false);
|
|
479
|
+
else
|
|
480
|
+
showMenu(elements, "selected");
|
|
481
|
+
};
|
|
482
|
+
const onTriggerKeyDown = (event) => {
|
|
483
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
484
|
+
event.preventDefault();
|
|
485
|
+
showMenu(elements, event.key === "ArrowDown" ? "first" : "last");
|
|
486
|
+
} else if (event.key === "Escape" && openMenu === elements) {
|
|
487
|
+
event.preventDefault();
|
|
488
|
+
closeMenu(elements, true);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
const onMenuClick = (event) => {
|
|
492
|
+
const item = menuItemFromEvent(elements, event);
|
|
493
|
+
if (item !== null)
|
|
494
|
+
selectItem(elements, item);
|
|
495
|
+
};
|
|
496
|
+
const onMenuKeyDown = (event) => {
|
|
497
|
+
const item = menuItemFromEvent(elements, event);
|
|
498
|
+
if (event.key === "Escape") {
|
|
499
|
+
event.preventDefault();
|
|
500
|
+
event.stopPropagation();
|
|
501
|
+
closeMenu(elements, true);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (event.key === "Tab") {
|
|
505
|
+
closeMenu(elements, false);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
509
|
+
event.preventDefault();
|
|
510
|
+
adjacentItem(elements, item, event.key === "ArrowDown" ? 1 : -1).focus();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (event.key === "Home" || event.key === "End") {
|
|
514
|
+
event.preventDefault();
|
|
515
|
+
const items = orderedItems(elements);
|
|
516
|
+
(event.key === "Home" ? items[0] : items.at(-1))?.focus();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if ((event.key === "Enter" || event.key === " ") && item !== null) {
|
|
520
|
+
event.preventDefault();
|
|
521
|
+
selectItem(elements, item);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
525
|
+
typeahead(elements, event.key);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
elements.trigger.addEventListener("click", onTriggerClick);
|
|
529
|
+
elements.trigger.addEventListener("keydown", onTriggerKeyDown);
|
|
530
|
+
elements.menu.addEventListener("click", onMenuClick);
|
|
531
|
+
elements.menu.addEventListener("keydown", onMenuKeyDown);
|
|
532
|
+
cleanups.push(() => {
|
|
533
|
+
clearTypeahead(elements);
|
|
534
|
+
elements.trigger.removeEventListener("click", onTriggerClick);
|
|
535
|
+
elements.trigger.removeEventListener("keydown", onTriggerKeyDown);
|
|
536
|
+
elements.menu.removeEventListener("click", onMenuClick);
|
|
537
|
+
elements.menu.removeEventListener("keydown", onMenuKeyDown);
|
|
538
|
+
closeMenu(elements, false);
|
|
539
|
+
elements.root.setAttribute("aria-busy", "true");
|
|
540
|
+
elements.root.setAttribute("data-ready", "false");
|
|
541
|
+
elements.trigger.disabled = true;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
const state = {
|
|
546
|
+
disposed: false,
|
|
547
|
+
publicInstallation: {
|
|
548
|
+
dispose: () => {
|
|
549
|
+
if (disposed)
|
|
550
|
+
return;
|
|
551
|
+
disposed = true;
|
|
552
|
+
state.disposed = true;
|
|
553
|
+
for (const cleanup of cleanups.splice(0).reverse())
|
|
554
|
+
cleanup();
|
|
555
|
+
metaRegistration?.release();
|
|
556
|
+
metaRegistration = null;
|
|
557
|
+
menus.clear();
|
|
558
|
+
if (installationByDocument.get(document) === state) {
|
|
559
|
+
installationByDocument.delete(document);
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
preference: () => preference
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const {
|
|
566
|
+
publicInstallation
|
|
567
|
+
} = state;
|
|
568
|
+
installationByDocument.set(document, state);
|
|
569
|
+
try {
|
|
570
|
+
metaRegistration = acquireThemeColorMeta(document, metaName, Symbol("hraness-static-appearance"), currentThemeColor());
|
|
571
|
+
document.documentElement.setAttribute("data-theme", resolvedTheme());
|
|
572
|
+
const onSystemChange = () => {
|
|
573
|
+
if (preference === "system")
|
|
574
|
+
syncAppearance();
|
|
575
|
+
};
|
|
576
|
+
systemMedia?.addEventListener("change", onSystemChange);
|
|
577
|
+
if (systemMedia !== null) {
|
|
578
|
+
cleanups.push(() => systemMedia.removeEventListener("change", onSystemChange));
|
|
579
|
+
}
|
|
580
|
+
const onStorage = (event) => {
|
|
581
|
+
const value = storageEventValue(event, storageKey);
|
|
582
|
+
if (value === undefined)
|
|
583
|
+
return;
|
|
584
|
+
if (isDesignTheme(value)) {
|
|
585
|
+
setPreference(value, false);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
setPreference(defaultDesignTheme, value !== null);
|
|
589
|
+
};
|
|
590
|
+
view?.addEventListener("storage", onStorage);
|
|
591
|
+
if (view !== null) {
|
|
592
|
+
cleanups.push(() => view.removeEventListener("storage", onStorage));
|
|
593
|
+
}
|
|
594
|
+
const closeForOutsideTarget = (target) => {
|
|
595
|
+
if (openMenu !== null && (!isElement(target) || !openMenu.root.contains(target))) {
|
|
596
|
+
closeMenu(openMenu, false);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
const onDocumentPointerDown = (event) => {
|
|
600
|
+
closeForOutsideTarget(event.target);
|
|
601
|
+
};
|
|
602
|
+
const onDocumentFocusIn = (event) => {
|
|
603
|
+
closeForOutsideTarget(event.target);
|
|
604
|
+
};
|
|
605
|
+
document.addEventListener("pointerdown", onDocumentPointerDown, true);
|
|
606
|
+
document.addEventListener("focusin", onDocumentFocusIn);
|
|
607
|
+
cleanups.push(() => {
|
|
608
|
+
document.removeEventListener("pointerdown", onDocumentPointerDown, true);
|
|
609
|
+
document.removeEventListener("focusin", onDocumentFocusIn);
|
|
610
|
+
});
|
|
611
|
+
if (document.readyState === "loading") {
|
|
612
|
+
const mountAfterParse = () => {
|
|
613
|
+
try {
|
|
614
|
+
mount();
|
|
615
|
+
} catch (error) {
|
|
616
|
+
publicInstallation.dispose();
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
document.addEventListener("DOMContentLoaded", mountAfterParse, {
|
|
621
|
+
once: true
|
|
622
|
+
});
|
|
623
|
+
cleanups.push(() => document.removeEventListener("DOMContentLoaded", mountAfterParse));
|
|
624
|
+
} else {
|
|
625
|
+
mount();
|
|
626
|
+
}
|
|
627
|
+
} catch (error) {
|
|
628
|
+
publicInstallation.dispose();
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
return publicInstallation;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/browser/artifact-share.ts
|
|
635
|
+
function nonblank(value, label) {
|
|
636
|
+
if (value.trim().length === 0) {
|
|
637
|
+
throw new RangeError(`${label} must contain a non-whitespace character.`);
|
|
638
|
+
}
|
|
639
|
+
return value;
|
|
640
|
+
}
|
|
641
|
+
function publicWebUrl(value) {
|
|
642
|
+
const normalized = nonblank(value, "An artifact share URL").trim();
|
|
643
|
+
let url;
|
|
644
|
+
try {
|
|
645
|
+
url = new URL(normalized);
|
|
646
|
+
} catch {
|
|
647
|
+
throw new RangeError("An artifact share URL must be an absolute URL.");
|
|
648
|
+
}
|
|
649
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
650
|
+
throw new RangeError("An artifact share URL must use HTTP or HTTPS.");
|
|
651
|
+
}
|
|
652
|
+
return url.href;
|
|
653
|
+
}
|
|
654
|
+
function normalizedIntent(input) {
|
|
655
|
+
return {
|
|
656
|
+
text: nonblank(input.text, "Artifact share text"),
|
|
657
|
+
url: publicWebUrl(input.url)
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function buildXShareIntentUrl(input) {
|
|
661
|
+
const {
|
|
662
|
+
text,
|
|
663
|
+
url
|
|
664
|
+
} = normalizedIntent(input);
|
|
665
|
+
const intent = new URL("https://x.com/intent/post");
|
|
666
|
+
intent.searchParams.set("text", text);
|
|
667
|
+
intent.searchParams.set("url", url);
|
|
668
|
+
return intent.href;
|
|
669
|
+
}
|
|
670
|
+
function buildLinkedInShareIntentUrl(url) {
|
|
671
|
+
const intent = new URL("https://www.linkedin.com/sharing/share-offsite/");
|
|
672
|
+
intent.searchParams.set("url", publicWebUrl(url));
|
|
673
|
+
return intent.href;
|
|
674
|
+
}
|
|
675
|
+
function buildBlueskyShareIntentUrl(input) {
|
|
676
|
+
const {
|
|
677
|
+
text,
|
|
678
|
+
url
|
|
679
|
+
} = normalizedIntent(input);
|
|
680
|
+
const intent = new URL("https://bsky.app/intent/compose");
|
|
681
|
+
intent.searchParams.set("text", `${text}
|
|
682
|
+
${url}`);
|
|
683
|
+
return intent.href;
|
|
684
|
+
}
|
|
685
|
+
async function copyTextToClipboard(text) {
|
|
686
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
687
|
+
if (clipboard === undefined || typeof clipboard.writeText !== "function") {
|
|
688
|
+
throw new Error("Clipboard text writing is unavailable in this environment.");
|
|
689
|
+
}
|
|
690
|
+
await clipboard.writeText(text);
|
|
691
|
+
}
|
|
692
|
+
function downloadBlob(blob, filename) {
|
|
693
|
+
const normalizedFilename = nonblank(filename, "A download filename").trim();
|
|
694
|
+
const document = globalThis.document;
|
|
695
|
+
if (document === undefined || typeof URL.createObjectURL !== "function" || typeof URL.revokeObjectURL !== "function") {
|
|
696
|
+
throw new Error("Blob downloads are unavailable in this environment.");
|
|
697
|
+
}
|
|
698
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
699
|
+
const anchor = document.createElement("a");
|
|
700
|
+
anchor.download = normalizedFilename;
|
|
701
|
+
anchor.hidden = true;
|
|
702
|
+
anchor.href = objectUrl;
|
|
703
|
+
anchor.rel = "noopener";
|
|
704
|
+
document.body.append(anchor);
|
|
705
|
+
try {
|
|
706
|
+
anchor.click();
|
|
707
|
+
} finally {
|
|
708
|
+
anchor.remove();
|
|
709
|
+
globalThis.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function isAbortError(error) {
|
|
713
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
714
|
+
}
|
|
715
|
+
function filesOnlyShareData(file) {
|
|
716
|
+
return {
|
|
717
|
+
files: [file]
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function nativeFileShareNavigator() {
|
|
721
|
+
const shareNavigator = globalThis.navigator;
|
|
722
|
+
if (shareNavigator === undefined || typeof shareNavigator.canShare !== "function" || typeof shareNavigator.share !== "function") {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
return shareNavigator;
|
|
726
|
+
}
|
|
727
|
+
function canShareFileWithNavigator(shareNavigator, file) {
|
|
728
|
+
try {
|
|
729
|
+
return shareNavigator.canShare(filesOnlyShareData(file));
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function canShareFileNatively(file) {
|
|
735
|
+
const shareNavigator = nativeFileShareNavigator();
|
|
736
|
+
return shareNavigator !== undefined && canShareFileWithNavigator(shareNavigator, file);
|
|
737
|
+
}
|
|
738
|
+
async function shareFileNatively(file) {
|
|
739
|
+
const shareNavigator = nativeFileShareNavigator();
|
|
740
|
+
if (shareNavigator === undefined || !canShareFileWithNavigator(shareNavigator, file)) {
|
|
741
|
+
return {
|
|
742
|
+
kind: "unavailable"
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
await shareNavigator.share(filesOnlyShareData(file));
|
|
747
|
+
return {
|
|
748
|
+
kind: "shared"
|
|
749
|
+
};
|
|
750
|
+
} catch (error) {
|
|
751
|
+
if (isAbortError(error))
|
|
752
|
+
return {
|
|
753
|
+
kind: "cancelled"
|
|
754
|
+
};
|
|
755
|
+
return {
|
|
756
|
+
error,
|
|
757
|
+
kind: "failed"
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// src/palette-color.ts
|
|
762
|
+
function channels(hex) {
|
|
763
|
+
if (!/^#[0-9a-f]{6}$/iu.test(hex))
|
|
764
|
+
throw new Error("Palette colors must be six-digit hex values.");
|
|
765
|
+
return [Number.parseInt(hex.slice(1, 3), 16), Number.parseInt(hex.slice(3, 5), 16), Number.parseInt(hex.slice(5, 7), 16)];
|
|
766
|
+
}
|
|
767
|
+
function mixPaletteColor(color, toward, amount) {
|
|
768
|
+
const target = channels(toward);
|
|
769
|
+
return `#${channels(color).map((value, index) => Math.round(value * (1 - amount) + (target[index] ?? 0) * amount).toString(16).padStart(2, "0")).join("")}`;
|
|
770
|
+
}
|
|
771
|
+
function luminance(hex) {
|
|
772
|
+
const linearize = (channel) => {
|
|
773
|
+
const value = channel / 255;
|
|
774
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
775
|
+
};
|
|
776
|
+
const [red, green, blue] = channels(hex);
|
|
777
|
+
return linearize(red) * 0.2126 + linearize(green) * 0.7152 + linearize(blue) * 0.0722;
|
|
778
|
+
}
|
|
779
|
+
function paletteContrast(a, b) {
|
|
780
|
+
const first = luminance(a);
|
|
781
|
+
const second = luminance(b);
|
|
782
|
+
return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05);
|
|
783
|
+
}
|
|
784
|
+
function readablePaletteColor(color, toward, backgrounds, minimum) {
|
|
785
|
+
for (let step = 0;step <= 100; step += 1) {
|
|
786
|
+
const candidate = mixPaletteColor(color, toward, step / 100);
|
|
787
|
+
if (backgrounds.every((background) => paletteContrast(candidate, background) >= minimum))
|
|
788
|
+
return candidate;
|
|
789
|
+
}
|
|
790
|
+
throw new Error("The authored palette cannot meet its contrast contract.");
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/palettes.ts
|
|
794
|
+
var designPalettes = ["catppuccin", "gruvbox", "rose-pine", "tokyo-night", "paper"];
|
|
795
|
+
function isDesignPalette(value) {
|
|
796
|
+
return typeof value === "string" && designPalettes.some((palette) => palette === value);
|
|
797
|
+
}
|
|
798
|
+
var designPaletteSources = {
|
|
799
|
+
paper: {
|
|
800
|
+
light: {
|
|
801
|
+
background: "#f8f7f4",
|
|
802
|
+
surface: "#fffefa",
|
|
803
|
+
raised: "#fffefa",
|
|
804
|
+
hover: "#ebe8e3",
|
|
805
|
+
text: "#1c1917",
|
|
806
|
+
muted: "#6f6962",
|
|
807
|
+
border: "#8b8278",
|
|
808
|
+
primary: "#1f5eea",
|
|
809
|
+
danger: "#b23c34",
|
|
810
|
+
warning: "#a65f00",
|
|
811
|
+
success: "#26814d",
|
|
812
|
+
info: "#1f5eea",
|
|
813
|
+
violet: "#7959a5",
|
|
814
|
+
rose: "#a44b65"
|
|
815
|
+
},
|
|
816
|
+
dark: {
|
|
817
|
+
background: "#12100f",
|
|
818
|
+
surface: "#1d1a18",
|
|
819
|
+
raised: "#211d1a",
|
|
820
|
+
hover: "#292522",
|
|
821
|
+
text: "#f5f2ed",
|
|
822
|
+
muted: "#aaa29a",
|
|
823
|
+
border: "#827a72",
|
|
824
|
+
primary: "#8fb0ff",
|
|
825
|
+
danger: "#ef8c82",
|
|
826
|
+
warning: "#efae55",
|
|
827
|
+
success: "#5fc98b",
|
|
828
|
+
info: "#8fb0ff",
|
|
829
|
+
violet: "#b7a0dc",
|
|
830
|
+
rose: "#db91aa"
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
catppuccin: {
|
|
834
|
+
dark: {
|
|
835
|
+
background: "#1e1e2e",
|
|
836
|
+
surface: "#181825",
|
|
837
|
+
raised: "#313244",
|
|
838
|
+
hover: "#45475a",
|
|
839
|
+
text: "#cdd6f4",
|
|
840
|
+
muted: "#a6adc8",
|
|
841
|
+
border: "#7f849c",
|
|
842
|
+
primary: "#89b4fa",
|
|
843
|
+
danger: "#f38ba8",
|
|
844
|
+
warning: "#f9e2af",
|
|
845
|
+
success: "#a6e3a1",
|
|
846
|
+
info: "#89dceb",
|
|
847
|
+
violet: "#cba6f7",
|
|
848
|
+
rose: "#f5c2e7"
|
|
849
|
+
},
|
|
850
|
+
light: {
|
|
851
|
+
background: "#eff1f5",
|
|
852
|
+
surface: "#e6e9ef",
|
|
853
|
+
raised: "#dce0e8",
|
|
854
|
+
hover: "#ccd0da",
|
|
855
|
+
text: "#4c4f69",
|
|
856
|
+
muted: "#6c6f85",
|
|
857
|
+
border: "#7c7f93",
|
|
858
|
+
primary: "#1e66f5",
|
|
859
|
+
danger: "#d20f39",
|
|
860
|
+
warning: "#df8e1d",
|
|
861
|
+
success: "#40a02b",
|
|
862
|
+
info: "#179299",
|
|
863
|
+
violet: "#8839ef",
|
|
864
|
+
rose: "#ea76cb"
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
gruvbox: {
|
|
868
|
+
dark: {
|
|
869
|
+
background: "#282828",
|
|
870
|
+
surface: "#1d2021",
|
|
871
|
+
raised: "#3c3836",
|
|
872
|
+
hover: "#504945",
|
|
873
|
+
text: "#ebdbb2",
|
|
874
|
+
muted: "#bdae93",
|
|
875
|
+
border: "#928374",
|
|
876
|
+
primary: "#83a598",
|
|
877
|
+
danger: "#fb4934",
|
|
878
|
+
warning: "#fabd2f",
|
|
879
|
+
success: "#b8bb26",
|
|
880
|
+
info: "#8ec07c",
|
|
881
|
+
violet: "#d3869b",
|
|
882
|
+
rose: "#fe8019"
|
|
883
|
+
},
|
|
884
|
+
light: {
|
|
885
|
+
background: "#fbf1c7",
|
|
886
|
+
surface: "#f9f5d7",
|
|
887
|
+
raised: "#ebdbb2",
|
|
888
|
+
hover: "#d5c4a1",
|
|
889
|
+
text: "#3c3836",
|
|
890
|
+
muted: "#665c54",
|
|
891
|
+
border: "#7c6f64",
|
|
892
|
+
primary: "#076678",
|
|
893
|
+
danger: "#9d0006",
|
|
894
|
+
warning: "#b57614",
|
|
895
|
+
success: "#79740e",
|
|
896
|
+
info: "#427b58",
|
|
897
|
+
violet: "#8f3f71",
|
|
898
|
+
rose: "#af3a03"
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
"rose-pine": {
|
|
902
|
+
dark: {
|
|
903
|
+
background: "#191724",
|
|
904
|
+
surface: "#1f1d2e",
|
|
905
|
+
raised: "#26233a",
|
|
906
|
+
hover: "#403d52",
|
|
907
|
+
text: "#e0def4",
|
|
908
|
+
muted: "#908caa",
|
|
909
|
+
border: "#908caa",
|
|
910
|
+
primary: "#c4a7e7",
|
|
911
|
+
danger: "#eb6f92",
|
|
912
|
+
warning: "#f6c177",
|
|
913
|
+
success: "#9ccfd8",
|
|
914
|
+
info: "#ebbcba",
|
|
915
|
+
violet: "#c4a7e7",
|
|
916
|
+
rose: "#ebbcba"
|
|
917
|
+
},
|
|
918
|
+
light: {
|
|
919
|
+
background: "#faf4ed",
|
|
920
|
+
surface: "#fffaf3",
|
|
921
|
+
raised: "#f2e9e1",
|
|
922
|
+
hover: "#dfdad9",
|
|
923
|
+
text: "#575279",
|
|
924
|
+
muted: "#797593",
|
|
925
|
+
border: "#797593",
|
|
926
|
+
primary: "#907aa9",
|
|
927
|
+
danger: "#b4637a",
|
|
928
|
+
warning: "#ea9d34",
|
|
929
|
+
success: "#286983",
|
|
930
|
+
info: "#56949f",
|
|
931
|
+
violet: "#907aa9",
|
|
932
|
+
rose: "#d7827e"
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
"tokyo-night": {
|
|
936
|
+
dark: {
|
|
937
|
+
background: "#1a1b26",
|
|
938
|
+
surface: "#16161e",
|
|
939
|
+
raised: "#24283b",
|
|
940
|
+
hover: "#292e42",
|
|
941
|
+
text: "#c0caf5",
|
|
942
|
+
muted: "#a9b1d6",
|
|
943
|
+
border: "#737aa2",
|
|
944
|
+
primary: "#7aa2f7",
|
|
945
|
+
danger: "#f7768e",
|
|
946
|
+
warning: "#e0af68",
|
|
947
|
+
success: "#9ece6a",
|
|
948
|
+
info: "#7dcfff",
|
|
949
|
+
violet: "#bb9af7",
|
|
950
|
+
rose: "#ff9e64"
|
|
951
|
+
},
|
|
952
|
+
light: {
|
|
953
|
+
background: "#e1e2e7",
|
|
954
|
+
surface: "#d0d5e3",
|
|
955
|
+
raised: "#c4c8da",
|
|
956
|
+
hover: "#b7c1e3",
|
|
957
|
+
text: "#3760bf",
|
|
958
|
+
muted: "#6172b0",
|
|
959
|
+
border: "#6172b0",
|
|
960
|
+
primary: "#2e7de9",
|
|
961
|
+
danger: "#f52a65",
|
|
962
|
+
warning: "#8c6c3e",
|
|
963
|
+
success: "#587539",
|
|
964
|
+
info: "#007197",
|
|
965
|
+
violet: "#9854f1",
|
|
966
|
+
rose: "#b15c00"
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
function createPalette(source, mode) {
|
|
971
|
+
const surfaces = [source.background, source.surface, source.raised, source.hover];
|
|
972
|
+
const endpoint = mode === "dark" ? "#ffffff" : "#000000";
|
|
973
|
+
const foreground = readablePaletteColor(source.text, endpoint, surfaces, 7);
|
|
974
|
+
const muted = readablePaletteColor(source.muted, endpoint, surfaces, 4.6);
|
|
975
|
+
const status = (seed) => {
|
|
976
|
+
const soft = mixPaletteColor(source.background, seed, 0.12);
|
|
977
|
+
const color = readablePaletteColor(seed, endpoint, [...surfaces, soft], 4.6);
|
|
978
|
+
const onColor = paletteContrast(color, source.background) >= 4.5 ? source.background : endpoint === "#ffffff" ? "#000000" : "#ffffff";
|
|
979
|
+
return {
|
|
980
|
+
color,
|
|
981
|
+
foreground: onColor,
|
|
982
|
+
soft
|
|
983
|
+
};
|
|
984
|
+
};
|
|
985
|
+
const primary = status(source.primary);
|
|
986
|
+
const danger = status(source.danger);
|
|
987
|
+
const warning = status(source.warning);
|
|
988
|
+
const success = status(source.success);
|
|
989
|
+
const info = status(source.info);
|
|
990
|
+
return Object.freeze({
|
|
991
|
+
background: source.background,
|
|
992
|
+
foreground,
|
|
993
|
+
muted,
|
|
994
|
+
faint: muted,
|
|
995
|
+
grid: source.raised,
|
|
996
|
+
line: source.hover,
|
|
997
|
+
controlBorder: readablePaletteColor(source.border, endpoint, surfaces, 3.1),
|
|
998
|
+
surface: source.surface,
|
|
999
|
+
surfaceRaised: source.raised,
|
|
1000
|
+
surfaceHover: source.hover,
|
|
1001
|
+
card: source.surface,
|
|
1002
|
+
cardForeground: foreground,
|
|
1003
|
+
popover: source.raised,
|
|
1004
|
+
popoverForeground: foreground,
|
|
1005
|
+
primary: primary.color,
|
|
1006
|
+
primaryForeground: primary.foreground,
|
|
1007
|
+
primarySoft: primary.soft,
|
|
1008
|
+
secondary: source.raised,
|
|
1009
|
+
secondaryForeground: foreground,
|
|
1010
|
+
accent: primary.soft,
|
|
1011
|
+
accentForeground: foreground,
|
|
1012
|
+
focus: primary.color,
|
|
1013
|
+
scrim: mode === "dark" ? "#000000b8" : "#00000070",
|
|
1014
|
+
disabled: source.raised,
|
|
1015
|
+
disabledForeground: muted,
|
|
1016
|
+
inverse: foreground,
|
|
1017
|
+
inverseForeground: source.background,
|
|
1018
|
+
danger: danger.color,
|
|
1019
|
+
dangerForeground: danger.foreground,
|
|
1020
|
+
dangerSoft: danger.soft,
|
|
1021
|
+
warning: warning.color,
|
|
1022
|
+
warningForeground: warning.foreground,
|
|
1023
|
+
warningSoft: warning.soft,
|
|
1024
|
+
success: success.color,
|
|
1025
|
+
successForeground: success.foreground,
|
|
1026
|
+
successSoft: success.soft,
|
|
1027
|
+
info: info.color,
|
|
1028
|
+
infoForeground: info.foreground,
|
|
1029
|
+
infoSoft: info.soft,
|
|
1030
|
+
chart1: readablePaletteColor(source.rose, endpoint, surfaces, 4.6),
|
|
1031
|
+
chart2: success.color,
|
|
1032
|
+
chart3: info.color,
|
|
1033
|
+
chart4: warning.color,
|
|
1034
|
+
chart5: readablePaletteColor(source.violet, endpoint, surfaces, 4.6)
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
var paletteColors = Object.freeze({
|
|
1038
|
+
paper: Object.freeze({
|
|
1039
|
+
light: Object.freeze({
|
|
1040
|
+
...createPalette(designPaletteSources.paper.light, "light"),
|
|
1041
|
+
grid: "#dfdcd6",
|
|
1042
|
+
line: "#b9b3ab",
|
|
1043
|
+
secondary: "#ebe8e3"
|
|
1044
|
+
}),
|
|
1045
|
+
dark: Object.freeze({
|
|
1046
|
+
...createPalette(designPaletteSources.paper.dark, "dark"),
|
|
1047
|
+
grid: "#302b27",
|
|
1048
|
+
line: "#514a44",
|
|
1049
|
+
secondary: "#292522"
|
|
1050
|
+
})
|
|
1051
|
+
}),
|
|
1052
|
+
catppuccin: Object.freeze({
|
|
1053
|
+
light: createPalette(designPaletteSources.catppuccin.light, "light"),
|
|
1054
|
+
dark: createPalette(designPaletteSources.catppuccin.dark, "dark")
|
|
1055
|
+
}),
|
|
1056
|
+
gruvbox: Object.freeze({
|
|
1057
|
+
light: createPalette(designPaletteSources.gruvbox.light, "light"),
|
|
1058
|
+
dark: createPalette(designPaletteSources.gruvbox.dark, "dark")
|
|
1059
|
+
}),
|
|
1060
|
+
"rose-pine": Object.freeze({
|
|
1061
|
+
light: createPalette(designPaletteSources["rose-pine"].light, "light"),
|
|
1062
|
+
dark: createPalette(designPaletteSources["rose-pine"].dark, "dark")
|
|
1063
|
+
}),
|
|
1064
|
+
"tokyo-night": Object.freeze({
|
|
1065
|
+
light: createPalette(designPaletteSources["tokyo-night"].light, "light"),
|
|
1066
|
+
dark: createPalette(designPaletteSources["tokyo-night"].dark, "dark")
|
|
1067
|
+
})
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
// src/palette-appearance.ts
|
|
1071
|
+
var defaultDesignPalettePreference = Object.freeze({
|
|
1072
|
+
palette: "catppuccin",
|
|
1073
|
+
mode: "dark"
|
|
1074
|
+
});
|
|
1075
|
+
var designPaletteStorageKey = "hraness-design-palette-v1";
|
|
1076
|
+
function parseDesignPalettePreference(value) {
|
|
1077
|
+
if (typeof value === "string") {
|
|
1078
|
+
if (value.length > 256)
|
|
1079
|
+
return null;
|
|
1080
|
+
try {
|
|
1081
|
+
value = JSON.parse(value);
|
|
1082
|
+
} catch {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1087
|
+
return null;
|
|
1088
|
+
const record = value;
|
|
1089
|
+
if (!isDesignPalette(record.palette) || !isDesignTheme(record.mode))
|
|
1090
|
+
return null;
|
|
1091
|
+
return Object.freeze({
|
|
1092
|
+
palette: record.palette,
|
|
1093
|
+
mode: record.mode
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
function normalizeDesignPalettePreference(value, fallback = defaultDesignPalettePreference) {
|
|
1097
|
+
return parseDesignPalettePreference(value) ?? parseDesignPalettePreference(fallback) ?? defaultDesignPalettePreference;
|
|
1098
|
+
}
|
|
1099
|
+
function resolveDesignPalettePreference(preference, systemPrefersDark) {
|
|
1100
|
+
return {
|
|
1101
|
+
palette: preference.palette,
|
|
1102
|
+
mode: resolveDesignTheme(preference.mode, systemPrefersDark)
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/palette-themes.ts
|
|
1107
|
+
import * as stylex from "@stylexjs/stylex";
|
|
1108
|
+
|
|
1109
|
+
// src/palette-tokens.stylex.ts
|
|
1110
|
+
var catppuccinLight = {
|
|
1111
|
+
x18acsur: "xadus2s x18acsur",
|
|
1112
|
+
$$css: true
|
|
1113
|
+
};
|
|
1114
|
+
var catppuccinDark = {
|
|
1115
|
+
x18acsur: "x18wthyl x18acsur",
|
|
1116
|
+
$$css: true
|
|
1117
|
+
};
|
|
1118
|
+
var gruvboxLight = {
|
|
1119
|
+
x18acsur: "xdktuxt x18acsur",
|
|
1120
|
+
$$css: true
|
|
1121
|
+
};
|
|
1122
|
+
var gruvboxDark = {
|
|
1123
|
+
x18acsur: "x1fsd05x x18acsur",
|
|
1124
|
+
$$css: true
|
|
1125
|
+
};
|
|
1126
|
+
var rosePineLight = {
|
|
1127
|
+
x18acsur: "x131glkb x18acsur",
|
|
1128
|
+
$$css: true
|
|
1129
|
+
};
|
|
1130
|
+
var rosePineDark = {
|
|
1131
|
+
x18acsur: "xivpxii x18acsur",
|
|
1132
|
+
$$css: true
|
|
1133
|
+
};
|
|
1134
|
+
var tokyoNightLight = {
|
|
1135
|
+
x18acsur: "x11phk89 x18acsur",
|
|
1136
|
+
$$css: true
|
|
1137
|
+
};
|
|
1138
|
+
var tokyoNightDark = {
|
|
1139
|
+
x18acsur: "xgp1gpt x18acsur",
|
|
1140
|
+
$$css: true
|
|
1141
|
+
};
|
|
1142
|
+
var paperLight = {
|
|
1143
|
+
x18acsur: "xjfxk3y x18acsur",
|
|
1144
|
+
$$css: true
|
|
1145
|
+
};
|
|
1146
|
+
var paperDark = {
|
|
1147
|
+
x18acsur: "x1lresar x18acsur",
|
|
1148
|
+
$$css: true
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
// src/palette-themes.ts
|
|
1152
|
+
var classes = {
|
|
1153
|
+
paper: {
|
|
1154
|
+
light: stylex.props(paperLight).className,
|
|
1155
|
+
dark: stylex.props(paperDark).className
|
|
1156
|
+
},
|
|
1157
|
+
catppuccin: {
|
|
1158
|
+
light: stylex.props(catppuccinLight).className,
|
|
1159
|
+
dark: stylex.props(catppuccinDark).className
|
|
1160
|
+
},
|
|
1161
|
+
gruvbox: {
|
|
1162
|
+
light: stylex.props(gruvboxLight).className,
|
|
1163
|
+
dark: stylex.props(gruvboxDark).className
|
|
1164
|
+
},
|
|
1165
|
+
"rose-pine": {
|
|
1166
|
+
light: stylex.props(rosePineLight).className,
|
|
1167
|
+
dark: stylex.props(rosePineDark).className
|
|
1168
|
+
},
|
|
1169
|
+
"tokyo-night": {
|
|
1170
|
+
light: stylex.props(tokyoNightLight).className,
|
|
1171
|
+
dark: stylex.props(tokyoNightDark).className
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
function getDesignPaletteTheme(palette, mode) {
|
|
1175
|
+
return {
|
|
1176
|
+
className: `hraness-palette ${classes[palette][mode]}`,
|
|
1177
|
+
background: paletteColors[palette][mode].background
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/browser/design-palette.ts
|
|
1182
|
+
var installationKey = Symbol.for("hraness.design-palette.controller.v1");
|
|
1183
|
+
var darkQuery = "(prefers-color-scheme: dark)";
|
|
1184
|
+
function designPaletteSnapshot(preference, systemPrefersDark, isForced = false) {
|
|
1185
|
+
const resolved = resolveDesignPalettePreference(preference, systemPrefersDark);
|
|
1186
|
+
const theme = getDesignPaletteTheme(resolved.palette, resolved.mode);
|
|
1187
|
+
return Object.freeze({
|
|
1188
|
+
preference: Object.freeze({
|
|
1189
|
+
...preference
|
|
1190
|
+
}),
|
|
1191
|
+
resolvedMode: resolved.mode,
|
|
1192
|
+
className: theme.className,
|
|
1193
|
+
background: theme.background,
|
|
1194
|
+
isForced
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
function readStorage(storage, key) {
|
|
1198
|
+
try {
|
|
1199
|
+
return storage?.getItem(key);
|
|
1200
|
+
} catch {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function writeStorage(storage, key, preference) {
|
|
1205
|
+
try {
|
|
1206
|
+
storage?.setItem(key, JSON.stringify(preference));
|
|
1207
|
+
} catch {}
|
|
1208
|
+
}
|
|
1209
|
+
function initDesignPalette(options = {}) {
|
|
1210
|
+
const document = options.document ?? globalThis.document;
|
|
1211
|
+
if (document === undefined || document.documentElement === null || document.head === null) {
|
|
1212
|
+
throw new Error("initDesignPalette requires an HTML document with a head.");
|
|
1213
|
+
}
|
|
1214
|
+
const view = document.defaultView;
|
|
1215
|
+
const storageKey = options.storageKey ?? designPaletteStorageKey;
|
|
1216
|
+
const legacyStorageKey = options.legacyStorageKey === undefined ? designThemeStorageKey : options.legacyStorageKey;
|
|
1217
|
+
if (storageKey.trim() === "" || legacyStorageKey?.trim() === "")
|
|
1218
|
+
throw new Error("Appearance storage keys must not be blank.");
|
|
1219
|
+
const fallback = normalizeDesignPalettePreference(options.defaultPreference);
|
|
1220
|
+
const forced = options.forcedPreference === undefined ? null : parseDesignPalettePreference(options.forcedPreference);
|
|
1221
|
+
if (options.forcedPreference !== undefined && (forced === null || forced.mode === "system")) {
|
|
1222
|
+
throw new Error("A forced palette requires a valid palette and a light or dark mode.");
|
|
1223
|
+
}
|
|
1224
|
+
const configuration = JSON.stringify({
|
|
1225
|
+
storageKey,
|
|
1226
|
+
legacyStorageKey,
|
|
1227
|
+
fallback,
|
|
1228
|
+
forced
|
|
1229
|
+
});
|
|
1230
|
+
const ownerDocument = document;
|
|
1231
|
+
const previous = ownerDocument[installationKey];
|
|
1232
|
+
if (previous !== undefined) {
|
|
1233
|
+
if (previous.version !== 1 || previous.configuration !== configuration) {
|
|
1234
|
+
throw new Error("This document already has a different palette configuration.");
|
|
1235
|
+
}
|
|
1236
|
+
return previous.acquire();
|
|
1237
|
+
}
|
|
1238
|
+
let storage = options.storage ?? null;
|
|
1239
|
+
const usesDefaultStorage = options.storage === undefined;
|
|
1240
|
+
if (usesDefaultStorage && forced === null) {
|
|
1241
|
+
try {
|
|
1242
|
+
storage = view?.localStorage ?? null;
|
|
1243
|
+
} catch {}
|
|
1244
|
+
}
|
|
1245
|
+
let media = null;
|
|
1246
|
+
try {
|
|
1247
|
+
media = view?.matchMedia?.(darkQuery) ?? null;
|
|
1248
|
+
} catch {}
|
|
1249
|
+
const readPreference = () => {
|
|
1250
|
+
if (forced !== null)
|
|
1251
|
+
return forced;
|
|
1252
|
+
const raw = readStorage(storage, storageKey);
|
|
1253
|
+
const saved = parseDesignPalettePreference(raw);
|
|
1254
|
+
if (saved !== null)
|
|
1255
|
+
return saved;
|
|
1256
|
+
if ((raw === null || raw === undefined) && legacyStorageKey !== null) {
|
|
1257
|
+
const legacy = readStorage(storage, legacyStorageKey);
|
|
1258
|
+
if (isDesignTheme(legacy))
|
|
1259
|
+
return Object.freeze({
|
|
1260
|
+
palette: fallback.palette,
|
|
1261
|
+
mode: legacy
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
return fallback;
|
|
1265
|
+
};
|
|
1266
|
+
let snapshot = designPaletteSnapshot(readPreference(), media?.matches ?? false, forced !== null);
|
|
1267
|
+
const listeners = new Set;
|
|
1268
|
+
const ownedClasses = new Set(designPalettes.flatMap((palette) => ["light", "dark"].flatMap((mode) => getDesignPaletteTheme(palette, mode).className.split(/\s+/u))));
|
|
1269
|
+
const themeColor = acquireThemeColorMeta(document, "theme-color", Symbol("palette-theme-color"), snapshot.background);
|
|
1270
|
+
const apply = () => {
|
|
1271
|
+
const root = document.documentElement;
|
|
1272
|
+
const nextClasses = new Set(snapshot.className.split(/\s+/u).filter(Boolean));
|
|
1273
|
+
for (const token of ownedClasses)
|
|
1274
|
+
if (token !== "" && !nextClasses.has(token))
|
|
1275
|
+
root.classList.remove(token);
|
|
1276
|
+
for (const token of nextClasses)
|
|
1277
|
+
root.classList.add(token);
|
|
1278
|
+
root.setAttribute("data-palette", snapshot.preference.palette);
|
|
1279
|
+
root.setAttribute("data-theme", snapshot.resolvedMode);
|
|
1280
|
+
themeColor.update(snapshot.background);
|
|
1281
|
+
};
|
|
1282
|
+
const update = (preference) => {
|
|
1283
|
+
const next = designPaletteSnapshot(preference, media?.matches ?? false, forced !== null);
|
|
1284
|
+
if (next.preference.palette === snapshot.preference.palette && next.preference.mode === snapshot.preference.mode && next.resolvedMode === snapshot.resolvedMode)
|
|
1285
|
+
return false;
|
|
1286
|
+
snapshot = next;
|
|
1287
|
+
apply();
|
|
1288
|
+
for (const listener of listeners)
|
|
1289
|
+
listener();
|
|
1290
|
+
return true;
|
|
1291
|
+
};
|
|
1292
|
+
const onMedia = () => {
|
|
1293
|
+
update(snapshot.preference);
|
|
1294
|
+
};
|
|
1295
|
+
const onStorage = (event) => {
|
|
1296
|
+
if (forced !== null || event.key !== storageKey && event.key !== null)
|
|
1297
|
+
return;
|
|
1298
|
+
if (usesDefaultStorage && event.storageArea !== null && event.storageArea !== undefined && event.storageArea !== storage)
|
|
1299
|
+
return;
|
|
1300
|
+
update(readPreference());
|
|
1301
|
+
};
|
|
1302
|
+
if (forced === null) {
|
|
1303
|
+
writeStorage(storage, storageKey, snapshot.preference);
|
|
1304
|
+
view?.addEventListener("storage", onStorage);
|
|
1305
|
+
media?.addEventListener("change", onMedia);
|
|
1306
|
+
}
|
|
1307
|
+
let owners = 0;
|
|
1308
|
+
const installation = {
|
|
1309
|
+
version: 1,
|
|
1310
|
+
configuration,
|
|
1311
|
+
acquire: () => {
|
|
1312
|
+
apply();
|
|
1313
|
+
owners += 1;
|
|
1314
|
+
let disposed = false;
|
|
1315
|
+
const subscriptions = new Set;
|
|
1316
|
+
return {
|
|
1317
|
+
getSnapshot: () => snapshot,
|
|
1318
|
+
subscribe: (listener) => {
|
|
1319
|
+
if (disposed)
|
|
1320
|
+
return () => {
|
|
1321
|
+
return;
|
|
1322
|
+
};
|
|
1323
|
+
const subscription = () => {
|
|
1324
|
+
listener();
|
|
1325
|
+
};
|
|
1326
|
+
subscriptions.add(subscription);
|
|
1327
|
+
listeners.add(subscription);
|
|
1328
|
+
return () => {
|
|
1329
|
+
subscriptions.delete(subscription);
|
|
1330
|
+
listeners.delete(subscription);
|
|
1331
|
+
};
|
|
1332
|
+
},
|
|
1333
|
+
setPreference: (value) => {
|
|
1334
|
+
if (disposed || forced !== null)
|
|
1335
|
+
return false;
|
|
1336
|
+
const preference = parseDesignPalettePreference(value);
|
|
1337
|
+
if (preference === null)
|
|
1338
|
+
throw new Error("Choose a supported palette and appearance mode.");
|
|
1339
|
+
writeStorage(storage, storageKey, preference);
|
|
1340
|
+
return update(preference);
|
|
1341
|
+
},
|
|
1342
|
+
dispose: () => {
|
|
1343
|
+
if (disposed)
|
|
1344
|
+
return;
|
|
1345
|
+
disposed = true;
|
|
1346
|
+
for (const listener of subscriptions)
|
|
1347
|
+
listeners.delete(listener);
|
|
1348
|
+
subscriptions.clear();
|
|
1349
|
+
owners -= 1;
|
|
1350
|
+
if (owners !== 0)
|
|
1351
|
+
return;
|
|
1352
|
+
view?.removeEventListener("storage", onStorage);
|
|
1353
|
+
media?.removeEventListener("change", onMedia);
|
|
1354
|
+
themeColor.release();
|
|
1355
|
+
if (ownerDocument[installationKey] === installation)
|
|
1356
|
+
Reflect.deleteProperty(ownerDocument, installationKey);
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
ownerDocument[installationKey] = installation;
|
|
1362
|
+
return installation.acquire();
|
|
1363
|
+
}
|
|
1364
|
+
// src/browser/foil.ts
|
|
1365
|
+
var REST = 50;
|
|
1366
|
+
var SETTLE = 0.05;
|
|
1367
|
+
var RESPONSE_MS = 85;
|
|
1368
|
+
var MIN_LIGHT = 8;
|
|
1369
|
+
var MAX_LIGHT = 92;
|
|
1370
|
+
var INPUTS = ["--hraness-foil-x", "--hraness-foil-y"];
|
|
1371
|
+
var clampLight = (value) => Math.max(MIN_LIGHT, Math.min(MAX_LIGHT, value));
|
|
1372
|
+
function attachFoil(root) {
|
|
1373
|
+
const document = root.ownerDocument;
|
|
1374
|
+
const view = document.defaultView;
|
|
1375
|
+
if (!view || typeof view.matchMedia !== "function" || typeof view.requestAnimationFrame !== "function" || typeof view.cancelAnimationFrame !== "function")
|
|
1376
|
+
return () => {};
|
|
1377
|
+
const preference = view.matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) and (forced-colors: none)");
|
|
1378
|
+
const states = new Map;
|
|
1379
|
+
let targets = [];
|
|
1380
|
+
let needsTargets = true;
|
|
1381
|
+
let pointer;
|
|
1382
|
+
let frame = null;
|
|
1383
|
+
let previousTime;
|
|
1384
|
+
let detached = false;
|
|
1385
|
+
const restore = (target) => {
|
|
1386
|
+
for (const input of INPUTS)
|
|
1387
|
+
target.style.removeProperty(input);
|
|
1388
|
+
};
|
|
1389
|
+
const reset = () => {
|
|
1390
|
+
if (frame !== null)
|
|
1391
|
+
view.cancelAnimationFrame(frame);
|
|
1392
|
+
frame = null;
|
|
1393
|
+
previousTime = undefined;
|
|
1394
|
+
pointer = undefined;
|
|
1395
|
+
for (const target of states.keys())
|
|
1396
|
+
restore(target);
|
|
1397
|
+
states.clear();
|
|
1398
|
+
targets = [];
|
|
1399
|
+
needsTargets = true;
|
|
1400
|
+
};
|
|
1401
|
+
const refreshTargets = () => {
|
|
1402
|
+
targets = [...root.matches("[data-foil]") ? [root] : [], ...root.querySelectorAll("[data-foil]")].filter((target) => {
|
|
1403
|
+
const ancestor = target.parentElement?.closest("[data-foil]");
|
|
1404
|
+
return !ancestor || !root.contains(ancestor);
|
|
1405
|
+
});
|
|
1406
|
+
const live = new Set(targets);
|
|
1407
|
+
for (const target of states.keys()) {
|
|
1408
|
+
if (!live.has(target)) {
|
|
1409
|
+
restore(target);
|
|
1410
|
+
states.delete(target);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
needsTargets = false;
|
|
1414
|
+
};
|
|
1415
|
+
const paint = (time) => {
|
|
1416
|
+
frame = null;
|
|
1417
|
+
if (detached || !preference.matches || document.hidden || !pointer) {
|
|
1418
|
+
reset();
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (needsTargets)
|
|
1422
|
+
refreshTargets();
|
|
1423
|
+
const elapsed = previousTime === undefined ? 1000 / 60 : Math.max(0, Math.min(64, time - previousTime));
|
|
1424
|
+
previousTime = time;
|
|
1425
|
+
const ease = 1 - Math.exp(-elapsed / RESPONSE_MS);
|
|
1426
|
+
const measured = targets.map((target) => ({
|
|
1427
|
+
target,
|
|
1428
|
+
bounds: target.getBoundingClientRect()
|
|
1429
|
+
}));
|
|
1430
|
+
let moving = false;
|
|
1431
|
+
for (const {
|
|
1432
|
+
target,
|
|
1433
|
+
bounds
|
|
1434
|
+
} of measured) {
|
|
1435
|
+
if (!target.isConnected || !root.contains(target) || bounds.width <= 0 || bounds.height <= 0 || bounds.bottom <= 0 || bounds.right <= 0 || bounds.top >= view.innerHeight || bounds.left >= view.innerWidth) {
|
|
1436
|
+
if (states.has(target)) {
|
|
1437
|
+
restore(target);
|
|
1438
|
+
states.delete(target);
|
|
1439
|
+
}
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
const goal = {
|
|
1443
|
+
x: clampLight(REST + (pointer.x - bounds.left - bounds.width / 2) / Math.max(160, bounds.width) * 60),
|
|
1444
|
+
y: clampLight(REST + (pointer.y - bounds.top - bounds.height / 2) / Math.max(120, bounds.height) * 50)
|
|
1445
|
+
};
|
|
1446
|
+
const light = states.get(target) ?? {
|
|
1447
|
+
x: REST,
|
|
1448
|
+
y: REST
|
|
1449
|
+
};
|
|
1450
|
+
for (const axis of ["x", "y"]) {
|
|
1451
|
+
const distance = goal[axis] - light[axis];
|
|
1452
|
+
if (Math.abs(distance) > SETTLE) {
|
|
1453
|
+
light[axis] += distance * ease;
|
|
1454
|
+
moving = true;
|
|
1455
|
+
} else
|
|
1456
|
+
light[axis] = goal[axis];
|
|
1457
|
+
}
|
|
1458
|
+
states.set(target, light);
|
|
1459
|
+
for (const axis of ["x", "y"]) {
|
|
1460
|
+
const input = `--hraness-foil-${axis}`;
|
|
1461
|
+
const value = `${light[axis].toFixed(2)}%`;
|
|
1462
|
+
if (target.style.getPropertyValue(input) !== value)
|
|
1463
|
+
target.style.setProperty(input, value);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (moving)
|
|
1467
|
+
frame = view.requestAnimationFrame(paint);
|
|
1468
|
+
else
|
|
1469
|
+
previousTime = undefined;
|
|
1470
|
+
};
|
|
1471
|
+
const move = (event) => {
|
|
1472
|
+
if (!preference.matches || document.hidden || event.pointerType === "touch") {
|
|
1473
|
+
reset();
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY))
|
|
1477
|
+
return;
|
|
1478
|
+
pointer = {
|
|
1479
|
+
x: event.clientX,
|
|
1480
|
+
y: event.clientY
|
|
1481
|
+
};
|
|
1482
|
+
needsTargets = true;
|
|
1483
|
+
if (frame === null)
|
|
1484
|
+
frame = view.requestAnimationFrame(paint);
|
|
1485
|
+
};
|
|
1486
|
+
const leave = (event) => {
|
|
1487
|
+
if (event.relatedTarget === null)
|
|
1488
|
+
reset();
|
|
1489
|
+
};
|
|
1490
|
+
const visibility = () => {
|
|
1491
|
+
if (document.hidden)
|
|
1492
|
+
reset();
|
|
1493
|
+
};
|
|
1494
|
+
view.addEventListener("pointermove", move, {
|
|
1495
|
+
passive: true
|
|
1496
|
+
});
|
|
1497
|
+
view.addEventListener("pointerdown", move, {
|
|
1498
|
+
passive: true
|
|
1499
|
+
});
|
|
1500
|
+
view.addEventListener("pointerout", leave, {
|
|
1501
|
+
passive: true
|
|
1502
|
+
});
|
|
1503
|
+
view.addEventListener("pointercancel", reset);
|
|
1504
|
+
view.addEventListener("blur", reset);
|
|
1505
|
+
view.addEventListener("scroll", reset, {
|
|
1506
|
+
passive: true,
|
|
1507
|
+
capture: true
|
|
1508
|
+
});
|
|
1509
|
+
view.addEventListener("resize", reset, {
|
|
1510
|
+
passive: true
|
|
1511
|
+
});
|
|
1512
|
+
document.addEventListener("visibilitychange", visibility);
|
|
1513
|
+
preference.addEventListener("change", reset);
|
|
1514
|
+
return () => {
|
|
1515
|
+
detached = true;
|
|
1516
|
+
reset();
|
|
1517
|
+
view.removeEventListener("pointermove", move);
|
|
1518
|
+
view.removeEventListener("pointerdown", move);
|
|
1519
|
+
view.removeEventListener("pointerout", leave);
|
|
1520
|
+
view.removeEventListener("pointercancel", reset);
|
|
1521
|
+
view.removeEventListener("blur", reset);
|
|
1522
|
+
view.removeEventListener("scroll", reset, true);
|
|
1523
|
+
view.removeEventListener("resize", reset);
|
|
1524
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
1525
|
+
preference.removeEventListener("change", reset);
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
// src/browser/hero-light.ts
|
|
1529
|
+
function attachHeroLight(root) {
|
|
1530
|
+
return () => {};
|
|
1531
|
+
}
|
|
1532
|
+
// src/status-page.ts
|
|
1533
|
+
var STATUS_PAGE_MAX_ROUTES = 2000;
|
|
1534
|
+
function isSitePath(href) {
|
|
1535
|
+
return /^\/(?![/\\])/u.test(href);
|
|
1536
|
+
}
|
|
1537
|
+
function parseStatusPageRoutes(value) {
|
|
1538
|
+
if (!value)
|
|
1539
|
+
return [];
|
|
1540
|
+
let parsed;
|
|
1541
|
+
try {
|
|
1542
|
+
parsed = JSON.parse(value);
|
|
1543
|
+
} catch {
|
|
1544
|
+
return [];
|
|
1545
|
+
}
|
|
1546
|
+
if (!Array.isArray(parsed))
|
|
1547
|
+
return [];
|
|
1548
|
+
const routes = [];
|
|
1549
|
+
for (const entry of parsed.slice(0, STATUS_PAGE_MAX_ROUTES)) {
|
|
1550
|
+
if (!Array.isArray(entry))
|
|
1551
|
+
continue;
|
|
1552
|
+
const [href, label] = entry;
|
|
1553
|
+
if (typeof href !== "string" || typeof label !== "string")
|
|
1554
|
+
continue;
|
|
1555
|
+
if (!isSitePath(href) || label.trim() === "")
|
|
1556
|
+
continue;
|
|
1557
|
+
routes.push({
|
|
1558
|
+
href,
|
|
1559
|
+
label
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
return routes;
|
|
1563
|
+
}
|
|
1564
|
+
var MAX_PATH = 160;
|
|
1565
|
+
function normalizeStatusPath(value) {
|
|
1566
|
+
let path = value.slice(0, MAX_PATH * 4);
|
|
1567
|
+
const end = path.search(/[?#]/u);
|
|
1568
|
+
if (end !== -1)
|
|
1569
|
+
path = path.slice(0, end);
|
|
1570
|
+
try {
|
|
1571
|
+
path = decodeURIComponent(path);
|
|
1572
|
+
} catch {}
|
|
1573
|
+
path = path.toLowerCase().split("/").filter((part, index) => index === 0 || part !== "").join("/");
|
|
1574
|
+
path = path.replace(/(?:\/index)?\.html?$/u, "");
|
|
1575
|
+
while (path.length > 1 && path.endsWith("/"))
|
|
1576
|
+
path = path.slice(0, -1);
|
|
1577
|
+
if (!path.startsWith("/"))
|
|
1578
|
+
path = `/${path}`;
|
|
1579
|
+
return path.slice(0, MAX_PATH);
|
|
1580
|
+
}
|
|
1581
|
+
var MAX_EDITS = 3;
|
|
1582
|
+
var BEYOND = MAX_EDITS + 1;
|
|
1583
|
+
function editDistance(a, b, limit) {
|
|
1584
|
+
if (Math.abs(a.length - b.length) > limit)
|
|
1585
|
+
return limit + 1;
|
|
1586
|
+
const columns = b.length + 1;
|
|
1587
|
+
let before = new Int32Array(columns).fill(BEYOND);
|
|
1588
|
+
let previous = Int32Array.from({
|
|
1589
|
+
length: columns
|
|
1590
|
+
}, (_, index) => Math.min(index, BEYOND));
|
|
1591
|
+
let current = new Int32Array(columns).fill(BEYOND);
|
|
1592
|
+
for (let i = 1;i <= a.length; i++) {
|
|
1593
|
+
const low = Math.max(1, i - limit);
|
|
1594
|
+
const high = Math.min(b.length, i + limit);
|
|
1595
|
+
current[low - 1] = low === 1 ? Math.min(i, BEYOND) : BEYOND;
|
|
1596
|
+
if (high < b.length)
|
|
1597
|
+
current[high + 1] = BEYOND;
|
|
1598
|
+
let rowMin = BEYOND;
|
|
1599
|
+
for (let j = low;j <= high; j++) {
|
|
1600
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1601
|
+
let value = Math.min((previous[j] ?? BEYOND) + 1, (current[j - 1] ?? BEYOND) + 1, (previous[j - 1] ?? BEYOND) + cost);
|
|
1602
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
1603
|
+
value = Math.min(value, (before[j - 2] ?? BEYOND) + 1);
|
|
1604
|
+
}
|
|
1605
|
+
current[j] = Math.min(value, BEYOND);
|
|
1606
|
+
rowMin = Math.min(rowMin, value);
|
|
1607
|
+
}
|
|
1608
|
+
if (rowMin > limit)
|
|
1609
|
+
return limit + 1;
|
|
1610
|
+
[before, previous, current] = [previous, current, before];
|
|
1611
|
+
}
|
|
1612
|
+
return Math.min(previous[b.length] ?? BEYOND, limit + 1);
|
|
1613
|
+
}
|
|
1614
|
+
function words(path) {
|
|
1615
|
+
return path.split(/[/\-_.]+/u).filter((word) => word.length > 0);
|
|
1616
|
+
}
|
|
1617
|
+
function wordAllowance(word) {
|
|
1618
|
+
if (word.length < 4)
|
|
1619
|
+
return 0;
|
|
1620
|
+
return word.length < 8 ? 1 : 2;
|
|
1621
|
+
}
|
|
1622
|
+
function wordsMatch(missing, candidate) {
|
|
1623
|
+
return missing.every((word) => {
|
|
1624
|
+
const allowance = wordAllowance(word);
|
|
1625
|
+
return candidate.some((other) => other === word || allowance > 0 && editDistance(word, other, allowance) <= allowance);
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
function candidate(path) {
|
|
1629
|
+
const parts = words(path);
|
|
1630
|
+
return {
|
|
1631
|
+
path,
|
|
1632
|
+
words: parts,
|
|
1633
|
+
joined: parts.join(""),
|
|
1634
|
+
last: path.slice(path.lastIndexOf("/") + 1)
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function score(missing, page) {
|
|
1638
|
+
if (page.path === missing.path)
|
|
1639
|
+
return 0;
|
|
1640
|
+
if (missing.joined.length >= 6 && missing.joined === page.joined)
|
|
1641
|
+
return 0.1;
|
|
1642
|
+
const longest = Math.max(missing.path.length, page.path.length);
|
|
1643
|
+
const limit = Math.min(MAX_EDITS, Math.floor(longest / 4));
|
|
1644
|
+
if (limit > 0 && wordsMatch(missing.words, page.words)) {
|
|
1645
|
+
const distance = editDistance(missing.path, page.path, limit);
|
|
1646
|
+
if (distance <= limit)
|
|
1647
|
+
return distance / longest;
|
|
1648
|
+
}
|
|
1649
|
+
if (missing.last.length >= 6 && missing.last === page.last)
|
|
1650
|
+
return 0.5;
|
|
1651
|
+
const longer = missing.words.filter((word) => word.length >= 3);
|
|
1652
|
+
if (longer.length >= 2 && longer.length === missing.words.length && longer.every((word) => page.words.includes(word)))
|
|
1653
|
+
return 0.6;
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
function suggestStatusRoute(pathname, routes) {
|
|
1657
|
+
const normalized = normalizeStatusPath(pathname);
|
|
1658
|
+
if (normalized === "/")
|
|
1659
|
+
return;
|
|
1660
|
+
const exactOnly = normalized.length < 5;
|
|
1661
|
+
const missing = candidate(normalized);
|
|
1662
|
+
let best;
|
|
1663
|
+
for (const route of routes.slice(0, STATUS_PAGE_MAX_ROUTES)) {
|
|
1664
|
+
if (!isSitePath(route.href))
|
|
1665
|
+
continue;
|
|
1666
|
+
const path = normalizeStatusPath(route.href);
|
|
1667
|
+
if (path === "/" || exactOnly && path !== normalized)
|
|
1668
|
+
continue;
|
|
1669
|
+
const value = score(missing, candidate(path));
|
|
1670
|
+
if (value === undefined)
|
|
1671
|
+
continue;
|
|
1672
|
+
if (best === undefined || value < best.score || value === best.score && (path.length < best.path.length || path.length === best.path.length && path < best.path)) {
|
|
1673
|
+
best = {
|
|
1674
|
+
route,
|
|
1675
|
+
score: value,
|
|
1676
|
+
path
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return best?.route;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/browser/status-field.ts
|
|
1684
|
+
var MAX_DOTS = 5200;
|
|
1685
|
+
var STRIDE = 4;
|
|
1686
|
+
var SPRING = 0.034;
|
|
1687
|
+
var DAMPING = 0.86;
|
|
1688
|
+
var PUSH = 5.5;
|
|
1689
|
+
var BURST = 24;
|
|
1690
|
+
var REST_DISTANCE = 0.08;
|
|
1691
|
+
var ACCENT_SHARE = 0.14;
|
|
1692
|
+
var VERTEX = `
|
|
1693
|
+
attribute vec2 a_position;
|
|
1694
|
+
attribute float a_heat;
|
|
1695
|
+
attribute float a_tone;
|
|
1696
|
+
uniform vec2 u_size;
|
|
1697
|
+
uniform float u_point;
|
|
1698
|
+
varying float v_mix;
|
|
1699
|
+
void main() {
|
|
1700
|
+
vec2 clip = a_position / u_size * 2.0 - 1.0;
|
|
1701
|
+
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
|
|
1702
|
+
gl_PointSize = u_point * (1.0 + a_heat * 0.55);
|
|
1703
|
+
v_mix = clamp(max(a_tone * 0.85, a_heat), 0.0, 1.0);
|
|
1704
|
+
}`;
|
|
1705
|
+
var FRAGMENT = `
|
|
1706
|
+
precision mediump float;
|
|
1707
|
+
uniform vec3 u_ink;
|
|
1708
|
+
uniform vec3 u_accent;
|
|
1709
|
+
uniform float u_alpha;
|
|
1710
|
+
varying float v_mix;
|
|
1711
|
+
void main() {
|
|
1712
|
+
float d = length(gl_PointCoord - 0.5);
|
|
1713
|
+
float a = smoothstep(0.5, 0.18, d) * u_alpha;
|
|
1714
|
+
gl_FragColor = vec4(mix(u_ink, u_accent, v_mix) * a, a);
|
|
1715
|
+
}`;
|
|
1716
|
+
function random(seed) {
|
|
1717
|
+
let state = seed >>> 0 || 2654435769;
|
|
1718
|
+
return () => {
|
|
1719
|
+
state ^= state << 13;
|
|
1720
|
+
state >>>= 0;
|
|
1721
|
+
state ^= state >>> 17;
|
|
1722
|
+
state ^= state << 5;
|
|
1723
|
+
state >>>= 0;
|
|
1724
|
+
return state / 4294967296;
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function compile(gl, type, source) {
|
|
1728
|
+
const shader = gl.createShader(type);
|
|
1729
|
+
if (!shader)
|
|
1730
|
+
return null;
|
|
1731
|
+
gl.shaderSource(shader, source);
|
|
1732
|
+
gl.compileShader(shader);
|
|
1733
|
+
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS))
|
|
1734
|
+
return shader;
|
|
1735
|
+
gl.deleteShader(shader);
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
function attachStatusField(code, options = {}) {
|
|
1739
|
+
const document = code.ownerDocument;
|
|
1740
|
+
const view = document.defaultView;
|
|
1741
|
+
const canvas = code.querySelector("canvas");
|
|
1742
|
+
const glyph = code.querySelector(".hraness-status-page__glyph");
|
|
1743
|
+
if (!view || !canvas || !glyph || typeof view.matchMedia !== "function" || typeof view.requestAnimationFrame !== "function" || typeof view.ResizeObserver !== "function")
|
|
1744
|
+
return () => {};
|
|
1745
|
+
if (view.matchMedia("(forced-colors: active)").matches)
|
|
1746
|
+
return () => {};
|
|
1747
|
+
const motion = view.matchMedia("(prefers-reduced-motion: reduce)");
|
|
1748
|
+
let gl = null;
|
|
1749
|
+
try {
|
|
1750
|
+
gl = canvas.getContext("webgl", {
|
|
1751
|
+
alpha: true,
|
|
1752
|
+
antialias: false,
|
|
1753
|
+
depth: false,
|
|
1754
|
+
premultipliedAlpha: true,
|
|
1755
|
+
preserveDrawingBuffer: false,
|
|
1756
|
+
stencil: false
|
|
1757
|
+
});
|
|
1758
|
+
} catch {
|
|
1759
|
+
gl = null;
|
|
1760
|
+
}
|
|
1761
|
+
if (!gl || gl.isContextLost())
|
|
1762
|
+
return () => {};
|
|
1763
|
+
const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);
|
|
1764
|
+
const fragment = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);
|
|
1765
|
+
const program = gl.createProgram();
|
|
1766
|
+
if (!vertex || !fragment || !program)
|
|
1767
|
+
return () => {};
|
|
1768
|
+
gl.attachShader(program, vertex);
|
|
1769
|
+
gl.attachShader(program, fragment);
|
|
1770
|
+
gl.linkProgram(program);
|
|
1771
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
|
|
1772
|
+
return () => {};
|
|
1773
|
+
gl.useProgram(program);
|
|
1774
|
+
const buffer = gl.createBuffer();
|
|
1775
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1776
|
+
const attribute = (name, size, offset) => {
|
|
1777
|
+
const location = gl.getAttribLocation(program, name);
|
|
1778
|
+
gl.enableVertexAttribArray(location);
|
|
1779
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, STRIDE * 4, offset * 4);
|
|
1780
|
+
};
|
|
1781
|
+
attribute("a_position", 2, 0);
|
|
1782
|
+
attribute("a_heat", 1, 2);
|
|
1783
|
+
attribute("a_tone", 1, 3);
|
|
1784
|
+
const uniform = (name) => gl.getUniformLocation(program, name);
|
|
1785
|
+
const uSize = uniform("u_size");
|
|
1786
|
+
const uPoint = uniform("u_point");
|
|
1787
|
+
const uInk = uniform("u_ink");
|
|
1788
|
+
const uAccent = uniform("u_accent");
|
|
1789
|
+
const uAlpha = uniform("u_alpha");
|
|
1790
|
+
gl.enable(gl.BLEND);
|
|
1791
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1792
|
+
const next = random(options.seed ?? 404);
|
|
1793
|
+
let homes = new Float32Array(0);
|
|
1794
|
+
let velocity = new Float32Array(0);
|
|
1795
|
+
let vertices = new Float32Array(0);
|
|
1796
|
+
let count = 0;
|
|
1797
|
+
let width = 0;
|
|
1798
|
+
let height = 0;
|
|
1799
|
+
let pointSize = 2;
|
|
1800
|
+
let pointer;
|
|
1801
|
+
let frame;
|
|
1802
|
+
let previous;
|
|
1803
|
+
let introStart;
|
|
1804
|
+
let delays = new Float32Array(0);
|
|
1805
|
+
let live = false;
|
|
1806
|
+
let disposed = false;
|
|
1807
|
+
let lost = false;
|
|
1808
|
+
const probe = document.createElement("canvas");
|
|
1809
|
+
probe.width = 1;
|
|
1810
|
+
probe.height = 1;
|
|
1811
|
+
const probeContext = probe.getContext("2d", {
|
|
1812
|
+
willReadFrequently: true
|
|
1813
|
+
});
|
|
1814
|
+
const rgba = (value) => {
|
|
1815
|
+
if (!probeContext)
|
|
1816
|
+
return [0.5, 0.5, 0.5, 1];
|
|
1817
|
+
probeContext.clearRect(0, 0, 1, 1);
|
|
1818
|
+
probeContext.fillStyle = "#808080";
|
|
1819
|
+
probeContext.fillStyle = value;
|
|
1820
|
+
probeContext.fillRect(0, 0, 1, 1);
|
|
1821
|
+
const [r = 128, g = 128, b = 128, a = 255] = probeContext.getImageData(0, 0, 1, 1).data;
|
|
1822
|
+
return [r / 255, g / 255, b / 255, a / 255];
|
|
1823
|
+
};
|
|
1824
|
+
const readColors = () => {
|
|
1825
|
+
const ink = rgba(view.getComputedStyle(glyph).color);
|
|
1826
|
+
const accent = rgba(view.getComputedStyle(canvas).color);
|
|
1827
|
+
gl.uniform3f(uInk, ink[0], ink[1], ink[2]);
|
|
1828
|
+
gl.uniform3f(uAccent, accent[0], accent[1], accent[2]);
|
|
1829
|
+
gl.uniform1f(uAlpha, Math.max(0.35, ink[3]));
|
|
1830
|
+
};
|
|
1831
|
+
const sample = () => {
|
|
1832
|
+
const bounds = canvas.getBoundingClientRect();
|
|
1833
|
+
const box = code.getBoundingClientRect();
|
|
1834
|
+
const centerX = box.left - bounds.left + box.width / 2;
|
|
1835
|
+
const centerY = box.top - bounds.top + box.height / 2;
|
|
1836
|
+
width = Math.round(bounds.width);
|
|
1837
|
+
height = Math.round(bounds.height);
|
|
1838
|
+
if (width < 8 || height < 8)
|
|
1839
|
+
return false;
|
|
1840
|
+
const ratio = Math.min(2, view.devicePixelRatio || 1);
|
|
1841
|
+
canvas.width = Math.round(width * ratio);
|
|
1842
|
+
canvas.height = Math.round(height * ratio);
|
|
1843
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
1844
|
+
gl.uniform2f(uSize, width, height);
|
|
1845
|
+
const style = view.getComputedStyle(glyph);
|
|
1846
|
+
const fontSize = Number.parseFloat(style.fontSize) || 96;
|
|
1847
|
+
const mask = document.createElement("canvas");
|
|
1848
|
+
mask.width = width;
|
|
1849
|
+
mask.height = height;
|
|
1850
|
+
const context = mask.getContext("2d", {
|
|
1851
|
+
willReadFrequently: true
|
|
1852
|
+
});
|
|
1853
|
+
if (!context)
|
|
1854
|
+
return false;
|
|
1855
|
+
context.font = `${style.fontStyle} ${style.fontWeight} ${fontSize}px ${style.fontFamily}`;
|
|
1856
|
+
context.textAlign = "center";
|
|
1857
|
+
context.textBaseline = "alphabetic";
|
|
1858
|
+
const text = glyph.textContent ?? "";
|
|
1859
|
+
const metrics = context.measureText(text);
|
|
1860
|
+
const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.7;
|
|
1861
|
+
const descent = metrics.actualBoundingBoxDescent || 0;
|
|
1862
|
+
context.fillStyle = "#000";
|
|
1863
|
+
context.fillText(text, centerX, centerY + (ascent - descent) / 2);
|
|
1864
|
+
const pixels = context.getImageData(0, 0, width, height).data;
|
|
1865
|
+
let gap = Math.max(3, Math.min(8, fontSize / 30));
|
|
1866
|
+
let points = [];
|
|
1867
|
+
for (let attempt = 0;attempt < 6; attempt++) {
|
|
1868
|
+
points = [];
|
|
1869
|
+
const rowHeight = gap * 0.866;
|
|
1870
|
+
for (let row = 0, y = rowHeight / 2;y < height; row++, y += rowHeight) {
|
|
1871
|
+
for (let x = row % 2 ? gap : gap / 2;x < width; x += gap) {
|
|
1872
|
+
if ((pixels[(Math.floor(y) * width + Math.floor(x)) * 4 + 3] ?? 0) >= 128)
|
|
1873
|
+
points.push(x, y);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
if (points.length / 2 <= MAX_DOTS)
|
|
1877
|
+
break;
|
|
1878
|
+
gap *= 1.2;
|
|
1879
|
+
}
|
|
1880
|
+
const nextCount = points.length / 2;
|
|
1881
|
+
const nextHomes = Float32Array.from(points);
|
|
1882
|
+
const nextVertices = new Float32Array(nextCount * STRIDE);
|
|
1883
|
+
const nextVelocity = new Float32Array(nextCount * 2);
|
|
1884
|
+
const nextDelays = new Float32Array(nextCount);
|
|
1885
|
+
const intro = count === 0 && !motion.matches;
|
|
1886
|
+
for (let index = 0;index < nextCount; index++) {
|
|
1887
|
+
const base = index * STRIDE;
|
|
1888
|
+
const hx = nextHomes[index * 2] ?? 0;
|
|
1889
|
+
const hy = nextHomes[index * 2 + 1] ?? 0;
|
|
1890
|
+
if (index < count) {
|
|
1891
|
+
nextVertices[base] = vertices[index * STRIDE] ?? 0;
|
|
1892
|
+
nextVertices[base + 1] = vertices[index * STRIDE + 1] ?? 0;
|
|
1893
|
+
nextVertices[base + 3] = vertices[index * STRIDE + 3] ?? 0;
|
|
1894
|
+
} else {
|
|
1895
|
+
const angle = next() * Math.PI * 2;
|
|
1896
|
+
const reach = intro ? (0.25 + next() * 0.6) * Math.max(width, height) * 0.5 : 0;
|
|
1897
|
+
nextVertices[base] = hx + Math.cos(angle) * reach;
|
|
1898
|
+
nextVertices[base + 1] = hy + Math.sin(angle) * reach * 0.6;
|
|
1899
|
+
nextVertices[base + 3] = next() < ACCENT_SHARE ? 1 : 0;
|
|
1900
|
+
nextDelays[index] = intro ? next() * 420 : 0;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
homes = nextHomes;
|
|
1904
|
+
vertices = nextVertices;
|
|
1905
|
+
velocity = nextVelocity;
|
|
1906
|
+
delays = nextDelays;
|
|
1907
|
+
count = nextCount;
|
|
1908
|
+
pointSize = gap * 0.6 * ratio;
|
|
1909
|
+
gl.uniform1f(uPoint, pointSize);
|
|
1910
|
+
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
|
|
1911
|
+
if (intro)
|
|
1912
|
+
introStart = undefined;
|
|
1913
|
+
return count > 0;
|
|
1914
|
+
};
|
|
1915
|
+
const draw = () => {
|
|
1916
|
+
if (disposed || lost)
|
|
1917
|
+
return;
|
|
1918
|
+
gl.clearColor(0, 0, 0, 0);
|
|
1919
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
1920
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
|
|
1921
|
+
gl.drawArrays(gl.POINTS, 0, count);
|
|
1922
|
+
if (!live) {
|
|
1923
|
+
live = true;
|
|
1924
|
+
options.onLive?.(true);
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
const settle = () => {
|
|
1928
|
+
for (let index = 0;index < count; index++) {
|
|
1929
|
+
vertices[index * STRIDE] = homes[index * 2] ?? 0;
|
|
1930
|
+
vertices[index * STRIDE + 1] = homes[index * 2 + 1] ?? 0;
|
|
1931
|
+
vertices[index * STRIDE + 2] = 0;
|
|
1932
|
+
velocity[index * 2] = 0;
|
|
1933
|
+
velocity[index * 2 + 1] = 0;
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
const step = (time) => {
|
|
1937
|
+
frame = undefined;
|
|
1938
|
+
if (disposed || lost)
|
|
1939
|
+
return;
|
|
1940
|
+
if (document.hidden) {
|
|
1941
|
+
previous = undefined;
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
introStart ??= time;
|
|
1945
|
+
const dt = previous === undefined ? 1 : Math.max(0.25, Math.min(2, (time - previous) / 16.67));
|
|
1946
|
+
previous = time;
|
|
1947
|
+
const since = time - introStart;
|
|
1948
|
+
const radius = Math.max(72, width * 0.16);
|
|
1949
|
+
const damping = DAMPING ** dt;
|
|
1950
|
+
let moving = false;
|
|
1951
|
+
for (let index = 0;index < count; index++) {
|
|
1952
|
+
const base = index * STRIDE;
|
|
1953
|
+
if (since < (delays[index] ?? 0)) {
|
|
1954
|
+
moving = true;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
let x = vertices[base] ?? 0;
|
|
1958
|
+
let y = vertices[base + 1] ?? 0;
|
|
1959
|
+
let vx = velocity[index * 2] ?? 0;
|
|
1960
|
+
let vy = velocity[index * 2 + 1] ?? 0;
|
|
1961
|
+
let ax = ((homes[index * 2] ?? 0) - x) * SPRING;
|
|
1962
|
+
let ay = ((homes[index * 2 + 1] ?? 0) - y) * SPRING;
|
|
1963
|
+
if (pointer) {
|
|
1964
|
+
const dx = x - pointer.x;
|
|
1965
|
+
const dy = y - pointer.y;
|
|
1966
|
+
const distance = Math.hypot(dx, dy);
|
|
1967
|
+
if (distance < radius) {
|
|
1968
|
+
const force = (1 - distance / radius) ** 2 * PUSH / Math.max(distance, 0.5);
|
|
1969
|
+
ax += dx * force;
|
|
1970
|
+
ay += dy * force;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
vx = (vx + ax * dt) * damping;
|
|
1974
|
+
vy = (vy + ay * dt) * damping;
|
|
1975
|
+
x += vx * dt;
|
|
1976
|
+
y += vy * dt;
|
|
1977
|
+
vertices[base] = x;
|
|
1978
|
+
vertices[base + 1] = y;
|
|
1979
|
+
velocity[index * 2] = vx;
|
|
1980
|
+
velocity[index * 2 + 1] = vy;
|
|
1981
|
+
const speed = Math.hypot(vx, vy);
|
|
1982
|
+
vertices[base + 2] = Math.max((vertices[base + 2] ?? 0) * 0.93 ** dt, Math.min(1, speed / 2));
|
|
1983
|
+
if (speed > REST_DISTANCE * 0.5 || (vertices[base + 2] ?? 0) > 0.02 || !pointer && Math.abs((homes[index * 2] ?? 0) - x) + Math.abs((homes[index * 2 + 1] ?? 0) - y) > REST_DISTANCE)
|
|
1984
|
+
moving = true;
|
|
1985
|
+
}
|
|
1986
|
+
if (!moving && !pointer)
|
|
1987
|
+
settle();
|
|
1988
|
+
draw();
|
|
1989
|
+
if (moving)
|
|
1990
|
+
frame = view.requestAnimationFrame(step);
|
|
1991
|
+
else
|
|
1992
|
+
previous = undefined;
|
|
1993
|
+
};
|
|
1994
|
+
const wake = () => {
|
|
1995
|
+
if (disposed || lost || motion.matches || frame !== undefined)
|
|
1996
|
+
return;
|
|
1997
|
+
frame = view.requestAnimationFrame(step);
|
|
1998
|
+
};
|
|
1999
|
+
const paintStill = () => {
|
|
2000
|
+
if (disposed || lost)
|
|
2001
|
+
return;
|
|
2002
|
+
if (frame !== undefined)
|
|
2003
|
+
view.cancelAnimationFrame(frame);
|
|
2004
|
+
frame = undefined;
|
|
2005
|
+
settle();
|
|
2006
|
+
draw();
|
|
2007
|
+
};
|
|
2008
|
+
const rebuild = () => {
|
|
2009
|
+
if (disposed || lost)
|
|
2010
|
+
return;
|
|
2011
|
+
if (!sample())
|
|
2012
|
+
return;
|
|
2013
|
+
readColors();
|
|
2014
|
+
if (motion.matches)
|
|
2015
|
+
paintStill();
|
|
2016
|
+
else {
|
|
2017
|
+
draw();
|
|
2018
|
+
wake();
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
const local = (event) => {
|
|
2022
|
+
const bounds = canvas.getBoundingClientRect();
|
|
2023
|
+
return {
|
|
2024
|
+
x: event.clientX - bounds.left,
|
|
2025
|
+
y: event.clientY - bounds.top
|
|
2026
|
+
};
|
|
2027
|
+
};
|
|
2028
|
+
const onMove = (event) => {
|
|
2029
|
+
if (motion.matches)
|
|
2030
|
+
return;
|
|
2031
|
+
pointer = local(event);
|
|
2032
|
+
wake();
|
|
2033
|
+
};
|
|
2034
|
+
const onLeave = () => {
|
|
2035
|
+
pointer = undefined;
|
|
2036
|
+
wake();
|
|
2037
|
+
};
|
|
2038
|
+
const onDown = (event) => {
|
|
2039
|
+
if (motion.matches)
|
|
2040
|
+
return;
|
|
2041
|
+
const origin = local(event);
|
|
2042
|
+
const reach = Math.max(120, width * 0.34);
|
|
2043
|
+
for (let index = 0;index < count; index++) {
|
|
2044
|
+
const dx = (vertices[index * STRIDE] ?? 0) - origin.x;
|
|
2045
|
+
const dy = (vertices[index * STRIDE + 1] ?? 0) - origin.y;
|
|
2046
|
+
const distance = Math.hypot(dx, dy);
|
|
2047
|
+
if (distance >= reach)
|
|
2048
|
+
continue;
|
|
2049
|
+
const force = (1 - distance / reach) * BURST / Math.max(distance, 0.5);
|
|
2050
|
+
velocity[index * 2] = (velocity[index * 2] ?? 0) + dx * force;
|
|
2051
|
+
velocity[index * 2 + 1] = (velocity[index * 2 + 1] ?? 0) + dy * force;
|
|
2052
|
+
}
|
|
2053
|
+
if (event.pointerType !== "mouse")
|
|
2054
|
+
pointer = undefined;
|
|
2055
|
+
wake();
|
|
2056
|
+
};
|
|
2057
|
+
const area = code.closest(".hraness-status-page") ?? code;
|
|
2058
|
+
area.addEventListener("pointermove", onMove, {
|
|
2059
|
+
passive: true
|
|
2060
|
+
});
|
|
2061
|
+
area.addEventListener("pointerleave", onLeave, {
|
|
2062
|
+
passive: true
|
|
2063
|
+
});
|
|
2064
|
+
area.addEventListener("pointerdown", onDown, {
|
|
2065
|
+
passive: true
|
|
2066
|
+
});
|
|
2067
|
+
const onVisibility = () => {
|
|
2068
|
+
if (!document.hidden)
|
|
2069
|
+
wake();
|
|
2070
|
+
};
|
|
2071
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
2072
|
+
const onMotion = () => {
|
|
2073
|
+
if (motion.matches)
|
|
2074
|
+
paintStill();
|
|
2075
|
+
else
|
|
2076
|
+
wake();
|
|
2077
|
+
};
|
|
2078
|
+
motion.addEventListener?.("change", onMotion);
|
|
2079
|
+
const scheme = view.matchMedia("(prefers-color-scheme: dark)");
|
|
2080
|
+
const onScheme = () => {
|
|
2081
|
+
if (disposed || lost)
|
|
2082
|
+
return;
|
|
2083
|
+
readColors();
|
|
2084
|
+
if (frame === undefined)
|
|
2085
|
+
draw();
|
|
2086
|
+
};
|
|
2087
|
+
scheme.addEventListener?.("change", onScheme);
|
|
2088
|
+
const themeObserver = typeof view.MutationObserver === "function" ? new view.MutationObserver(onScheme) : undefined;
|
|
2089
|
+
themeObserver?.observe(document.documentElement, {
|
|
2090
|
+
attributeFilter: ["class", "data-theme", "data-palette", "style"],
|
|
2091
|
+
attributes: true
|
|
2092
|
+
});
|
|
2093
|
+
let resizeFrame;
|
|
2094
|
+
const resizeObserver = new view.ResizeObserver(() => {
|
|
2095
|
+
if (resizeFrame !== undefined)
|
|
2096
|
+
view.cancelAnimationFrame(resizeFrame);
|
|
2097
|
+
resizeFrame = view.requestAnimationFrame(() => {
|
|
2098
|
+
resizeFrame = undefined;
|
|
2099
|
+
rebuild();
|
|
2100
|
+
});
|
|
2101
|
+
});
|
|
2102
|
+
const onLost = () => {
|
|
2103
|
+
lost = true;
|
|
2104
|
+
if (frame !== undefined)
|
|
2105
|
+
view.cancelAnimationFrame(frame);
|
|
2106
|
+
frame = undefined;
|
|
2107
|
+
if (live) {
|
|
2108
|
+
live = false;
|
|
2109
|
+
options.onLive?.(false);
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
canvas.addEventListener("webglcontextlost", onLost);
|
|
2113
|
+
const fonts = document.fonts;
|
|
2114
|
+
const onFonts = () => rebuild();
|
|
2115
|
+
let started = false;
|
|
2116
|
+
const start = () => {
|
|
2117
|
+
if (started || disposed)
|
|
2118
|
+
return;
|
|
2119
|
+
started = true;
|
|
2120
|
+
resizeObserver.observe(code);
|
|
2121
|
+
rebuild();
|
|
2122
|
+
};
|
|
2123
|
+
if (fonts?.ready) {
|
|
2124
|
+
fonts.ready.then(start, start);
|
|
2125
|
+
fonts.addEventListener?.("loadingdone", onFonts);
|
|
2126
|
+
} else
|
|
2127
|
+
start();
|
|
2128
|
+
return () => {
|
|
2129
|
+
if (disposed)
|
|
2130
|
+
return;
|
|
2131
|
+
disposed = true;
|
|
2132
|
+
if (frame !== undefined)
|
|
2133
|
+
view.cancelAnimationFrame(frame);
|
|
2134
|
+
if (resizeFrame !== undefined)
|
|
2135
|
+
view.cancelAnimationFrame(resizeFrame);
|
|
2136
|
+
area.removeEventListener("pointermove", onMove);
|
|
2137
|
+
area.removeEventListener("pointerleave", onLeave);
|
|
2138
|
+
area.removeEventListener("pointerdown", onDown);
|
|
2139
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
2140
|
+
motion.removeEventListener?.("change", onMotion);
|
|
2141
|
+
scheme.removeEventListener?.("change", onScheme);
|
|
2142
|
+
fonts?.removeEventListener?.("loadingdone", onFonts);
|
|
2143
|
+
themeObserver?.disconnect();
|
|
2144
|
+
resizeObserver.disconnect();
|
|
2145
|
+
canvas.removeEventListener("webglcontextlost", onLost);
|
|
2146
|
+
gl.deleteBuffer(buffer);
|
|
2147
|
+
gl.deleteProgram(program);
|
|
2148
|
+
gl.deleteShader(vertex);
|
|
2149
|
+
gl.deleteShader(fragment);
|
|
2150
|
+
if (live)
|
|
2151
|
+
options.onLive?.(false);
|
|
2152
|
+
const release = () => {
|
|
2153
|
+
if (!canvas.isConnected && !gl.isContextLost())
|
|
2154
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
2155
|
+
};
|
|
2156
|
+
release();
|
|
2157
|
+
view.setTimeout(release, 0);
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
// src/browser/status-page.ts
|
|
2162
|
+
function parseUrl(value) {
|
|
2163
|
+
if (!value)
|
|
2164
|
+
return;
|
|
2165
|
+
try {
|
|
2166
|
+
return new URL(value);
|
|
2167
|
+
} catch {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
function previousPage(view, document) {
|
|
2172
|
+
const navigation = view.navigation;
|
|
2173
|
+
const index = navigation?.currentEntry?.index;
|
|
2174
|
+
if (navigation?.entries !== undefined && typeof index === "number") {
|
|
2175
|
+
return index < 1 ? undefined : parseUrl(navigation.entries()[index - 1]?.url);
|
|
2176
|
+
}
|
|
2177
|
+
return view.history.length > 1 ? parseUrl(document.referrer) : undefined;
|
|
2178
|
+
}
|
|
2179
|
+
function attachStatusPage(root) {
|
|
2180
|
+
const document = root.ownerDocument;
|
|
2181
|
+
const view = document.defaultView;
|
|
2182
|
+
if (!view)
|
|
2183
|
+
return () => {};
|
|
2184
|
+
const cleanups = [];
|
|
2185
|
+
const hint = root.querySelector(".hraness-status-page__hint");
|
|
2186
|
+
const hintLink = hint?.querySelector("a");
|
|
2187
|
+
if (hint && hintLink && root.dataset.kind === "not-found") {
|
|
2188
|
+
const match = suggestStatusRoute(view.location.pathname, parseStatusPageRoutes(root.getAttribute("data-hraness-status-routes")));
|
|
2189
|
+
const target = match === undefined ? undefined : new URL(match.href, view.location.href);
|
|
2190
|
+
if (match && target && target.origin === view.location.origin) {
|
|
2191
|
+
hintLink.href = target.pathname + target.search + target.hash;
|
|
2192
|
+
hintLink.textContent = match.label;
|
|
2193
|
+
hint.hidden = false;
|
|
2194
|
+
root.dataset.hranessStatusSuggestion = match.href;
|
|
2195
|
+
cleanups.push(() => {
|
|
2196
|
+
hint.hidden = true;
|
|
2197
|
+
hintLink.setAttribute("href", "/");
|
|
2198
|
+
hintLink.textContent = "";
|
|
2199
|
+
delete root.dataset.hranessStatusSuggestion;
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const back = root.querySelector(".hraness-status-page__back");
|
|
2204
|
+
if (back) {
|
|
2205
|
+
const previous = previousPage(view, document);
|
|
2206
|
+
if (previous && previous.origin === view.location.origin && previous.pathname !== view.location.pathname) {
|
|
2207
|
+
back.href = previous.href;
|
|
2208
|
+
back.hidden = false;
|
|
2209
|
+
const onBack = (event) => {
|
|
2210
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
2211
|
+
return;
|
|
2212
|
+
event.preventDefault();
|
|
2213
|
+
view.history.back();
|
|
2214
|
+
};
|
|
2215
|
+
back.addEventListener("click", onBack);
|
|
2216
|
+
cleanups.push(() => {
|
|
2217
|
+
back.removeEventListener("click", onBack);
|
|
2218
|
+
back.hidden = true;
|
|
2219
|
+
back.setAttribute("href", "/");
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
const code = root.querySelector(".hraness-status-page__code");
|
|
2224
|
+
if (code) {
|
|
2225
|
+
cleanups.push(attachStatusField(code, {
|
|
2226
|
+
onLive: (live) => {
|
|
2227
|
+
if (live)
|
|
2228
|
+
root.dataset.hranessStatusField = "live";
|
|
2229
|
+
else
|
|
2230
|
+
delete root.dataset.hranessStatusField;
|
|
2231
|
+
}
|
|
2232
|
+
}));
|
|
2233
|
+
}
|
|
2234
|
+
cleanups.push(attachFoil(root));
|
|
2235
|
+
return () => {
|
|
2236
|
+
for (const cleanup of cleanups.splice(0).reverse())
|
|
2237
|
+
cleanup();
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
// src/browser/sticky-offset.ts
|
|
2241
|
+
var stickyOffsetCustomProperty = "--hraness-sticky-offset";
|
|
2242
|
+
var stickyOffsetHeaderSelector = [".hraness-marketing-header:not([data-position='static'])", ".hraness-marketing-header-surface:not([data-position='static'])"].join(", ");
|
|
2243
|
+
function canQuery(root) {
|
|
2244
|
+
return typeof root.querySelector === "function";
|
|
2245
|
+
}
|
|
2246
|
+
function ownerDocumentOf(node) {
|
|
2247
|
+
if (node === undefined)
|
|
2248
|
+
return globalThis.document;
|
|
2249
|
+
if (node.nodeType === 9)
|
|
2250
|
+
return node;
|
|
2251
|
+
const owner = node.ownerDocument;
|
|
2252
|
+
return owner ?? undefined;
|
|
2253
|
+
}
|
|
2254
|
+
function resolveHeader(root, header) {
|
|
2255
|
+
if (typeof header === "object")
|
|
2256
|
+
return header;
|
|
2257
|
+
if (!canQuery(root))
|
|
2258
|
+
return null;
|
|
2259
|
+
if (typeof header === "string")
|
|
2260
|
+
return root.querySelector(header);
|
|
2261
|
+
return root.querySelector(stickyOffsetHeaderSelector);
|
|
2262
|
+
}
|
|
2263
|
+
function resolveTarget(header, target) {
|
|
2264
|
+
if (target !== undefined)
|
|
2265
|
+
return target;
|
|
2266
|
+
const page = header.closest(".hraness-marketing-page");
|
|
2267
|
+
return page ?? header.ownerDocument.documentElement;
|
|
2268
|
+
}
|
|
2269
|
+
function measureStickyOffset(header) {
|
|
2270
|
+
return `${header.getBoundingClientRect().height}px`;
|
|
2271
|
+
}
|
|
2272
|
+
function publishStickyOffset(header, target) {
|
|
2273
|
+
const value = measureStickyOffset(header);
|
|
2274
|
+
resolveTarget(header, target).style.setProperty(stickyOffsetCustomProperty, value);
|
|
2275
|
+
return value;
|
|
2276
|
+
}
|
|
2277
|
+
function syncStickyOffset(options = {}) {
|
|
2278
|
+
const document = typeof options.header === "object" ? options.header.ownerDocument : ownerDocumentOf(options.root);
|
|
2279
|
+
if (document === undefined)
|
|
2280
|
+
return () => {};
|
|
2281
|
+
const root = options.root ?? document;
|
|
2282
|
+
const header = resolveHeader(root, options.header);
|
|
2283
|
+
if (header === null)
|
|
2284
|
+
return () => {};
|
|
2285
|
+
const target = resolveTarget(header, options.target);
|
|
2286
|
+
const publish = () => {
|
|
2287
|
+
publishStickyOffset(header, target);
|
|
2288
|
+
};
|
|
2289
|
+
publish();
|
|
2290
|
+
const view = document.defaultView;
|
|
2291
|
+
if (view === null || typeof view.ResizeObserver !== "function") {
|
|
2292
|
+
return () => {
|
|
2293
|
+
target.style.removeProperty(stickyOffsetCustomProperty);
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
const observer = new view.ResizeObserver(publish);
|
|
2297
|
+
observer.observe(header);
|
|
2298
|
+
return () => {
|
|
2299
|
+
observer.disconnect();
|
|
2300
|
+
target.style.removeProperty(stickyOffsetCustomProperty);
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// src/browser/index.ts
|
|
2305
|
+
var defaultDesignTheme2 = defaultDesignTheme;
|
|
2306
|
+
var designThemeLabel2 = designThemeLabel;
|
|
2307
|
+
var designThemes2 = designThemes;
|
|
2308
|
+
var designThemeStorageKey2 = designThemeStorageKey;
|
|
2309
|
+
var installAppearanceMenus2 = installAppearanceMenus;
|
|
2310
|
+
var isDesignTheme2 = isDesignTheme;
|
|
2311
|
+
var normalizeDesignTheme2 = normalizeDesignTheme;
|
|
2312
|
+
var resolveDesignTheme2 = resolveDesignTheme;
|
|
2313
|
+
export {
|
|
2314
|
+
syncStickyOffset,
|
|
2315
|
+
stickyOffsetHeaderSelector,
|
|
2316
|
+
stickyOffsetCustomProperty,
|
|
2317
|
+
shareFileNatively,
|
|
2318
|
+
resolveDesignTheme2 as resolveDesignTheme,
|
|
2319
|
+
resolveDesignPalettePreference,
|
|
2320
|
+
publishStickyOffset,
|
|
2321
|
+
parseDesignPalettePreference,
|
|
2322
|
+
normalizeDesignTheme2 as normalizeDesignTheme,
|
|
2323
|
+
normalizeDesignPalettePreference,
|
|
2324
|
+
measureStickyOffset,
|
|
2325
|
+
isDesignTheme2 as isDesignTheme,
|
|
2326
|
+
installAppearanceMenus2 as installAppearanceMenus,
|
|
2327
|
+
initDesignPalette,
|
|
2328
|
+
downloadBlob,
|
|
2329
|
+
designThemes2 as designThemes,
|
|
2330
|
+
designThemeStorageKey2 as designThemeStorageKey,
|
|
2331
|
+
designThemeLabel2 as designThemeLabel,
|
|
2332
|
+
designPaletteStorageKey,
|
|
2333
|
+
designPaletteSnapshot,
|
|
2334
|
+
defaultDesignTheme2 as defaultDesignTheme,
|
|
2335
|
+
defaultDesignPalettePreference,
|
|
2336
|
+
copyTextToClipboard,
|
|
2337
|
+
canShareFileNatively,
|
|
2338
|
+
buildXShareIntentUrl,
|
|
2339
|
+
buildLinkedInShareIntentUrl,
|
|
2340
|
+
buildBlueskyShareIntentUrl,
|
|
2341
|
+
attachStatusPage,
|
|
2342
|
+
attachStatusField,
|
|
2343
|
+
attachHeroLight,
|
|
2344
|
+
attachFoil
|
|
2345
|
+
};
|