@pyreon/zero 0.12.4 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/lib/favicon.js +151 -6
- package/lib/favicon.js.map +1 -1
- package/lib/index.js +40 -3
- package/lib/index.js.map +1 -1
- package/lib/theme.js +5 -2
- package/lib/theme.js.map +1 -1
- package/lib/types/favicon.d.ts +17 -0
- package/lib/types/favicon.d.ts.map +1 -1
- package/lib/types/index.d.ts +18 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/server.d.ts +1 -1
- package/lib/types/theme.d.ts +1 -1
- package/lib/types/theme.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/favicon.ts +163 -33
- package/src/index.ts +28 -0
- package/src/theme.tsx +10 -3
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/utils/use-intersection-observer.ts","../../../core/core/lib/jsx-runtime.js","../src/image.tsx","../src/link.tsx","../src/script.tsx","../src/i18n-routing.ts","../src/meta.tsx","../src/theme.tsx"],"sourcesContent":["import { onMount, onUnmount } from '@pyreon/core'\n\n/**\n * Observes an element and calls `onIntersect` once it enters the viewport.\n * Automatically disconnects after the first intersection.\n *\n * @param getElement - Getter for the target element (may be undefined before mount).\n * @param onIntersect - Callback fired when the element becomes visible.\n * @param rootMargin - IntersectionObserver rootMargin. Default: \"200px\".\n */\nexport function useIntersectionObserver(\n getElement: () => HTMLElement | undefined,\n onIntersect: () => void,\n rootMargin = '200px',\n) {\n onMount(() => {\n const el = getElement()\n if (!el) return undefined\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n onIntersect()\n observer.disconnect()\n }\n }\n },\n { rootMargin },\n )\n\n observer.observe(el)\n onUnmount(() => observer.disconnect())\n return undefined\n })\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { createRef } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { FormatSource } from './image-plugin'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Image optimization component ───────────────────────────────────────────\n//\n// <Image> provides:\n// - Lazy loading via IntersectionObserver (loads when near viewport)\n// - Automatic width/height to prevent CLS (Cumulative Layout Shift)\n// - Responsive srcset generation from width descriptors\n// - Multi-format support via <picture> (WebP/AVIF with fallback)\n// - Blur-up placeholder while loading\n// - Priority loading for above-the-fold images\n\nexport interface ImageProps {\n /** Image source URL. */\n src: string\n /** Alt text (required for accessibility). */\n alt: string\n /** Intrinsic width of the image. */\n width: number\n /** Intrinsic height of the image. */\n height: number\n /** Responsive sizes attribute. Default: \"100vw\" */\n sizes?: string\n /** Responsive srcset string or source array. */\n srcset?: string | ImageSource[]\n /** Per-format source sets for <picture>. Provided automatically by imagePlugin. */\n formats?: FormatSource[]\n /** Loading strategy. \"lazy\" uses IntersectionObserver, \"eager\" loads immediately. Default: \"lazy\" */\n loading?: 'lazy' | 'eager'\n /** Mark as priority (LCP image). Disables lazy loading, adds fetchPriority=\"high\". */\n priority?: boolean\n /** Low-quality placeholder image URL or base64 data URI for blur-up effect. */\n placeholder?: string\n /** CSS class name. */\n class?: string\n /** Inline styles. */\n style?: string\n /** CSS object-fit. Default: \"cover\" */\n fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n /** Decode async. Default: true */\n decoding?: 'sync' | 'async' | 'auto'\n}\n\nexport interface ImageSource {\n src: string\n width: number\n}\n\n/**\n * Optimized image component with lazy loading, responsive images,\n * multi-format <picture> support, and blur-up placeholders.\n *\n * @example\n * // With imagePlugin — spread the import directly\n * import hero from \"./hero.jpg?optimize\"\n * <Image {...hero} alt=\"Hero\" priority />\n *\n * @example\n * // Manual usage\n * <Image src=\"/hero.jpg\" alt=\"Hero\" width={1200} height={630} />\n */\nexport function Image(props: ImageProps): VNodeChild {\n const isEager = props.priority || props.loading === 'eager'\n const loaded = signal(isEager)\n const inView = signal(isEager)\n const containerRef = createRef<HTMLElement>()\n\n // Resolve srcset from string or array\n const resolvedSrcset =\n typeof props.srcset === 'string'\n ? props.srcset\n : props.srcset?.map((s) => `${s.src} ${s.width}w`).join(', ')\n\n const sizes = props.sizes ?? '100vw'\n const fit = props.fit ?? 'cover'\n const hasFormats = props.formats && props.formats.length > 0\n const aspectRatio = `${props.width} / ${props.height}`\n\n if (!isEager) {\n useIntersectionObserver(\n () => containerRef.current ?? undefined,\n () => inView.set(true),\n )\n }\n\n // Static styles (don't depend on signals)\n const containerStyle = [\n 'position: relative',\n 'overflow: hidden',\n `aspect-ratio: ${aspectRatio}`,\n `max-width: ${props.width}px`,\n 'width: 100%',\n props.style,\n ]\n .filter(Boolean)\n .join('; ')\n\n const imgEl = (\n <img\n src={() => (inView() ? props.src : '')}\n srcSet={() => (!hasFormats && inView() && resolvedSrcset ? resolvedSrcset : '')}\n sizes={resolvedSrcset ? sizes : undefined}\n alt={props.alt}\n width={props.width}\n height={props.height}\n loading={isEager ? 'eager' : 'lazy'}\n decoding={props.decoding ?? 'async'}\n fetchPriority={props.priority ? 'high' : undefined}\n onLoad={() => loaded.set(true)}\n style={() =>\n [\n 'display: block',\n 'width: 100%',\n 'height: 100%',\n `object-fit: ${fit}`,\n 'transition: opacity 0.3s ease',\n props.placeholder && !loaded() ? 'opacity: 0' : 'opacity: 1',\n ].join('; ')\n }\n />\n )\n\n return (\n <div ref={containerRef} class={props.class} style={containerStyle}>\n {props.placeholder && (\n <img\n src={props.placeholder}\n alt=\"\"\n aria-hidden=\"true\"\n loading=\"eager\"\n style={() =>\n [\n 'position: absolute',\n 'inset: 0',\n 'width: 100%',\n 'height: 100%',\n 'object-fit: cover',\n 'filter: blur(20px)',\n 'transform: scale(1.1)',\n 'transition: opacity 0.4s ease',\n loaded() ? 'opacity: 0; pointer-events: none' : 'opacity: 1',\n ].join('; ')\n }\n />\n )}\n {hasFormats ? (\n <picture>\n {props.formats?.map((fmt) => (\n <source\n type={fmt.type}\n srcSet={() => (inView() ? (fmt.srcset ?? '') : '')}\n sizes={sizes}\n />\n ))}\n {imgEl}\n </picture>\n ) : (\n imgEl\n )}\n </div>\n )\n}\n","import { createRef } from '@pyreon/core'\nimport { useRouter } from '@pyreon/router'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Link component with prefetching ────────────────────────────────────────\n//\n// Provides client-side navigation, prefetching, and active state tracking.\n// Three levels of API:\n//\n// 1. useLink(props) — composable returning handlers, state, and ref callback\n// 2. createLink(Comp) — HOC wrapping any component with link behavior\n// 3. Link — default <a>-based link (built on createLink)\n\nexport interface LinkProps {\n /** Target URL path. */\n href: string\n /** Link content. */\n children?: any\n /** CSS class name. */\n class?: string\n /** Class applied when this link matches the current route. */\n activeClass?: string\n /** Class applied when this link exactly matches the current route. */\n exactActiveClass?: string\n /** Prefetch strategy. Default: \"hover\" */\n prefetch?: 'hover' | 'viewport' | 'none'\n /** Open in new tab. */\n external?: boolean\n /** Inline styles. */\n style?: string\n /** ARIA label. */\n 'aria-label'?: string\n /** Additional click handler — called before navigation. Call e.preventDefault() to cancel. */\n onClick?: ((e: MouseEvent) => void) | undefined\n}\n\n/** Props passed to a custom component via createLink. */\nexport interface LinkRenderProps {\n href: string\n ref: import('@pyreon/core').Ref<HTMLAnchorElement>\n onClick: (e: MouseEvent) => void\n onMouseEnter: () => void\n onTouchStart: () => void\n isActive: () => boolean\n isExactActive: () => boolean\n /** Reactive class string — pass directly to element for auto-updates on route change. */\n class: (() => string) | string | undefined\n style?: string\n target?: string\n rel?: string\n 'aria-label'?: string\n children?: any\n}\n\n/** Return type of useLink. */\nexport interface UseLinkReturn {\n /** Ref object — attach to the root element for viewport-based prefetch. */\n ref: import('@pyreon/core').Ref<HTMLAnchorElement>\n /** Click handler — performs client-side navigation. */\n handleClick: (e: MouseEvent) => void\n /** Mouse enter handler — triggers hover prefetch. */\n handleMouseEnter: () => void\n /** Touch start handler — triggers prefetch on mobile. */\n handleTouchStart: () => void\n /** Whether the link partially matches the current route. */\n isActive: () => boolean\n /** Whether the link exactly matches the current route. */\n isExactActive: () => boolean\n /** Resolved class string including active classes. */\n classes: () => string\n}\n\nconst MAX_PREFETCH_CACHE = 200\nconst prefetched = new Set<string>()\n\nfunction doPrefetch(href: string) {\n if (prefetched.has(href)) return\n // Evict oldest entries when cache is full\n if (prefetched.size >= MAX_PREFETCH_CACHE) {\n const first = prefetched.values().next().value\n if (first) prefetched.delete(first)\n }\n prefetched.add(href)\n\n const docLink = document.createElement('link')\n docLink.rel = 'prefetch'\n docLink.href = href\n docLink.as = 'document'\n document.head.appendChild(docLink)\n\n try {\n const chunkHint = document.createElement('link')\n chunkHint.rel = 'modulepreload'\n chunkHint.href = href\n document.head.appendChild(chunkHint)\n } catch {\n // modulepreload is a hint, not critical\n }\n}\n\n/**\n * Prefetch a route's JS chunk by injecting `<link rel=\"prefetch\">` into the\n * document head. Deduplicates — calling with the same href twice is a no-op.\n *\n * @example\n * prefetchRoute('/about')\n * prefetchRoute('/dashboard')\n */\nexport function prefetchRoute(href: string): void {\n doPrefetch(href)\n}\n\n/**\n * Composable that provides all link behavior — navigation, prefetching,\n * active state, and viewport observation.\n *\n * Use this for full control when `createLink` is too opinionated.\n *\n * @example\n * function MyLink(props: LinkProps) {\n * const link = useLink(props)\n * return (\n * <button ref={link.ref} class={link.classes()} onClick={link.handleClick}>\n * {props.children}\n * </button>\n * )\n * }\n */\nexport function useLink(props: LinkProps): UseLinkReturn {\n const router = useRouter()\n const elementRef = createRef<HTMLAnchorElement>()\n const strategy = props.prefetch ?? 'hover'\n\n function handleClick(e: MouseEvent) {\n // Call user's onClick first — they may call e.preventDefault()\n if (props.onClick) {\n ;(props.onClick as (e: MouseEvent) => void)(e)\n }\n\n if (\n e.defaultPrevented ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey ||\n props.external\n ) {\n return\n }\n e.preventDefault()\n router.push(props.href)\n }\n\n function handleMouseEnter() {\n if (strategy === 'hover') {\n doPrefetch(props.href)\n }\n }\n\n function handleTouchStart() {\n if (strategy === 'hover' || strategy === 'viewport') {\n doPrefetch(props.href)\n }\n }\n\n if (strategy === 'viewport') {\n useIntersectionObserver(\n () => elementRef.current ?? undefined,\n () => doPrefetch(props.href),\n )\n }\n\n const isActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath || !props.href) return false\n if (props.href === '/') return currentPath === '/'\n return currentPath.startsWith(props.href)\n }\n\n const isExactActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath) return false\n return currentPath === props.href\n }\n\n const classes = () => {\n const cls: string[] = []\n if (props.class) cls.push(props.class)\n if (props.activeClass && isActive()) cls.push(props.activeClass)\n if (props.exactActiveClass && isExactActive()) cls.push(props.exactActiveClass)\n return cls.join(' ')\n }\n\n return {\n ref: elementRef,\n handleClick,\n handleMouseEnter,\n handleTouchStart,\n isActive,\n isExactActive,\n classes,\n }\n}\n\n/**\n * Higher-order component that wraps any component with link behavior.\n *\n * The wrapped component receives {@link LinkRenderProps} with all handlers,\n * active state, and accessibility attributes pre-wired.\n *\n * @example\n * // Custom button link\n * const ButtonLink = createLink((props) => (\n * <button\n * ref={props.ref}\n * class={props.class}\n * onClick={props.onClick}\n * onMouseEnter={props.onMouseEnter}\n * >\n * {props.children}\n * </button>\n * ))\n *\n * // Custom styled component\n * const CardLink = createLink((props) => (\n * <div\n * ref={props.ref}\n * class={`card ${props.isActive() ? \"card--active\" : \"\"}`}\n * onClick={props.onClick}\n * onMouseEnter={props.onMouseEnter}\n * >\n * {props.children}\n * </div>\n * ))\n *\n * // Usage\n * <ButtonLink href=\"/about\">About</ButtonLink>\n * <CardLink href=\"/posts\" prefetch=\"viewport\">Posts</CardLink>\n */\nexport function createLink(Component: (props: LinkRenderProps) => any): (props: LinkProps) => any {\n return function WrappedLink(props: LinkProps) {\n const link = useLink(props)\n\n return (\n <Component\n href={props.href}\n ref={link.ref}\n onClick={link.handleClick}\n onMouseEnter={link.handleMouseEnter}\n onTouchStart={link.handleTouchStart}\n isActive={link.isActive}\n isExactActive={link.isExactActive}\n class={link.classes}\n {...(props.style ? { style: props.style } : {})}\n {...(props.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n children={props.children}\n />\n )\n }\n}\n\n/**\n * Default navigation link built on an `<a>` tag.\n *\n * @example\n * <Link href=\"/about\" prefetch=\"viewport\">About</Link>\n * <Link href=\"/posts\" activeClass=\"nav-active\">Posts</Link>\n */\nexport const Link = createLink((props: LinkRenderProps) => (\n <a\n ref={props.ref}\n href={props.href}\n {...(props.class ? { class: props.class } : {})}\n {...(props.style ? { style: props.style } : {})}\n {...(props.target ? { target: props.target } : {})}\n {...(props.rel ? { rel: props.rel } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n {...(props.isExactActive() ? { 'aria-current': 'page' as const } : {})}\n onClick={props.onClick}\n onMouseEnter={props.onMouseEnter}\n onTouchStart={props.onTouchStart}\n >\n {props.children}\n </a>\n))\n","import type { VNodeChild } from '@pyreon/core'\nimport { createRef, onMount, onUnmount } from '@pyreon/core'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Script optimization component ─────────────────────────────────────────\n//\n// <Script> provides optimized third-party script loading:\n// - Defer loading until after hydration\n// - Load on idle (requestIdleCallback)\n// - Load on interaction (click, scroll, etc.)\n// - Load on viewport entry\n// - Worker offloading for analytics scripts\n\nexport interface ScriptProps {\n /** Script source URL. */\n src: string\n /** Loading strategy. Default: \"afterHydration\" */\n strategy?: ScriptStrategy\n /** Inline script content (alternative to src). */\n children?: string\n /** Script id for deduplication. */\n id?: string\n /** Async attribute. Default: true */\n async?: boolean\n /** onLoad callback. */\n onLoad?: () => void\n /** onError callback. */\n onError?: (error: Error) => void\n}\n\nexport type ScriptStrategy =\n | 'beforeHydration'\n | 'afterHydration'\n | 'onIdle'\n | 'onInteraction'\n | 'onViewport'\n\n/**\n * Optimized script loading component.\n *\n * @example\n * // Load analytics after page is interactive\n * <Script src=\"https://analytics.example.com/script.js\" strategy=\"onIdle\" />\n *\n * // Load chat widget when user scrolls\n * <Script src=\"/chat-widget.js\" strategy=\"onViewport\" />\n *\n * // Inline script with deferred execution\n * <Script strategy=\"afterHydration\">\n * {`console.log(\"App hydrated!\")`}\n * </Script>\n */\nexport function Script(props: ScriptProps): VNodeChild {\n function loadScript() {\n // Deduplication\n if (props.id && document.getElementById(props.id)) return\n\n const script = document.createElement('script')\n if (props.src) script.src = props.src\n if (props.id) script.id = props.id\n script.async = props.async !== false\n\n if (props.onLoad) script.onload = props.onLoad\n if (props.onError) {\n script.onerror = () => props.onError?.(new Error(`Failed to load: ${props.src}`))\n }\n\n if (props.children && !props.src) {\n script.textContent = props.children\n }\n\n document.head.appendChild(script)\n }\n\n onMount(() => {\n const strategy = props.strategy ?? 'afterHydration'\n\n switch (strategy) {\n case 'beforeHydration':\n // Already in HTML — do nothing\n break\n\n case 'afterHydration':\n // Load immediately after mount (hydration is complete)\n loadScript()\n break\n\n case 'onIdle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => loadScript(), { timeout: 5000 })\n } else {\n setTimeout(loadScript, 200)\n }\n break\n\n case 'onInteraction': {\n const events = ['click', 'scroll', 'keydown', 'touchstart']\n function handler() {\n for (const e of events) document.removeEventListener(e, handler)\n loadScript()\n }\n for (const e of events) {\n document.addEventListener(e, handler, { once: true, passive: true })\n }\n onUnmount(() => {\n for (const e of events) document.removeEventListener(e, handler)\n })\n break\n }\n\n case 'onViewport':\n // Handled below via useIntersectionObserver on the sentinel element\n break\n }\n return undefined\n })\n\n const sentinelRef = createRef<HTMLElement>()\n const strategy = props.strategy ?? 'afterHydration'\n\n if (strategy === 'onViewport') {\n useIntersectionObserver(\n () => sentinelRef.current ?? undefined,\n () => loadScript(),\n )\n }\n\n if (strategy === 'onViewport') {\n return <div ref={sentinelRef} style=\"width:0;height:0;overflow:hidden\" />\n }\n\n return null\n}\n","import { createContext } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { Plugin } from 'vite'\n\n// ─── Localized routing ─────────────────────────────────────────────────────\n//\n// Adds locale-prefixed routes to Zero's file-system router:\n// - /about → /en/about, /de/about, /cs/about\n// - / → /en, /de, /cs (or default locale without prefix)\n// - Automatic locale detection from Accept-Language header\n// - Redirect to preferred locale\n// - hreflang link generation\n//\n// Usage:\n// import { i18nRouting } from \"@pyreon/zero\"\n// export default { plugins: [zero(), i18nRouting({ locales: [\"en\", \"de\"], defaultLocale: \"en\" })] }\n\nexport interface I18nRoutingConfig {\n /** Supported locales. e.g. [\"en\", \"de\", \"cs\"] */\n locales: string[]\n /** Default locale — served without prefix (/ instead of /en/). */\n defaultLocale: string\n /** Redirect root to detected locale. Default: true */\n detectLocale?: boolean\n /** Cookie name to persist locale preference. Default: \"locale\" */\n cookieName?: string\n /** URL strategy. Default: \"prefix-except-default\" */\n strategy?: 'prefix' | 'prefix-except-default'\n}\n\nexport interface LocaleContext {\n /** Current locale code. e.g. \"en\", \"de\" */\n locale: string\n /** All supported locales. */\n locales: string[]\n /** Default locale. */\n defaultLocale: string\n /** Build a localized path. e.g. localePath(\"/about\", \"de\") → \"/de/about\" */\n localePath: (path: string, locale?: string) => string\n /** Get hreflang alternates for the current path. */\n alternates: () => Array<{ locale: string; url: string }>\n}\n\n/**\n * Detect preferred locale from Accept-Language header.\n */\nexport function detectLocaleFromHeader(\n acceptLanguage: string | null | undefined,\n locales: string[],\n defaultLocale: string,\n): string {\n if (!acceptLanguage) return defaultLocale\n\n // Parse Accept-Language: en-US,en;q=0.9,de;q=0.8\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q] = part.trim().split(';q=')\n return {\n lang: lang?.split('-')[0]?.toLowerCase() ?? '',\n quality: q ? Number.parseFloat(q) : 1,\n }\n })\n .sort((a, b) => b.quality - a.quality)\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) return lang\n }\n\n return defaultLocale\n}\n\n/**\n * Extract locale from a URL path.\n * Returns { locale, pathWithoutLocale }.\n */\nexport function extractLocaleFromPath(\n path: string,\n locales: string[],\n defaultLocale: string,\n): { locale: string; pathWithoutLocale: string } {\n const segments = path.split('/').filter(Boolean)\n const firstSegment = segments[0]?.toLowerCase()\n\n if (firstSegment && locales.includes(firstSegment)) {\n return {\n locale: firstSegment,\n pathWithoutLocale: '/' + segments.slice(1).join('/') || '/',\n }\n }\n\n return { locale: defaultLocale, pathWithoutLocale: path }\n}\n\n/**\n * Build a localized path.\n */\nexport function buildLocalePath(\n path: string,\n locale: string,\n defaultLocale: string,\n strategy: 'prefix' | 'prefix-except-default',\n): string {\n const clean = path === '/' ? '' : path\n if (strategy === 'prefix-except-default' && locale === defaultLocale) {\n return path\n }\n return `/${locale}${clean}`\n}\n\n/**\n * Create a LocaleContext for use in components and loaders.\n */\nexport function createLocaleContext(\n locale: string,\n path: string,\n config: I18nRoutingConfig,\n): LocaleContext {\n const strategy = config.strategy ?? 'prefix-except-default'\n\n return {\n locale,\n locales: config.locales,\n defaultLocale: config.defaultLocale,\n\n localePath(targetPath: string, targetLocale?: string) {\n return buildLocalePath(\n targetPath,\n targetLocale ?? locale,\n config.defaultLocale,\n strategy,\n )\n },\n\n alternates() {\n const { pathWithoutLocale } = extractLocaleFromPath(\n path,\n config.locales,\n config.defaultLocale,\n )\n return config.locales.map((loc) => ({\n locale: loc,\n url: buildLocalePath(pathWithoutLocale, loc, config.defaultLocale, strategy),\n }))\n },\n }\n}\n\n/**\n * I18n routing middleware for Zero's server.\n *\n * - Detects locale from URL prefix or Accept-Language header\n * - Redirects root to preferred locale (when detectLocale is true)\n * - Sets locale context for loaders and components\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { i18nRouting } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * plugins: [\n * i18nRouting({\n * locales: [\"en\", \"de\", \"cs\"],\n * defaultLocale: \"en\",\n * }),\n * ],\n * })\n * ```\n */\nexport function i18nRouting(config: I18nRoutingConfig): Plugin {\n const strategy = config.strategy ?? 'prefix-except-default'\n const detectEnabled = config.detectLocale !== false\n const cookieName = config.cookieName ?? 'locale'\n\n return {\n name: 'pyreon-zero-i18n-routing',\n\n // Route duplication is NOT handled here. The fs-router's `scanRouteFiles`\n // consumes the i18n config to duplicate routes per locale at build time.\n // This plugin only provides: (1) the server middleware for locale detection\n // and (2) the runtime hooks (useLocale, setLocale) for client-side use.\n configResolved() {},\n\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '/'\n\n // Skip static assets\n if (url.startsWith('/@') || url.startsWith('/__') || url.includes('.')) {\n return next()\n }\n\n const { locale } = extractLocaleFromPath(\n url,\n config.locales,\n config.defaultLocale,\n )\n\n // Redirect root to detected locale\n if (detectEnabled && url === '/') {\n const cookies = parseCookies(req.headers.cookie)\n const preferredFromCookie = cookies[cookieName]\n const preferredFromHeader = detectLocaleFromHeader(\n req.headers['accept-language'],\n config.locales,\n config.defaultLocale,\n )\n const preferred = preferredFromCookie && config.locales.includes(preferredFromCookie)\n ? preferredFromCookie\n : preferredFromHeader\n\n if (strategy === 'prefix' || preferred !== config.defaultLocale) {\n res.writeHead(302, { Location: `/${preferred}/` })\n res.end()\n return\n }\n }\n\n // Attach locale context to request for loaders\n ;(req as any).__locale = locale\n ;(req as any).__localeContext = createLocaleContext(locale, url, config)\n\n // Update the module-level signal so useLocale() returns the correct value\n localeSignal.set(locale)\n\n next()\n })\n },\n }\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n if (!header) return {}\n const result: Record<string, string> = {}\n for (const pair of header.split(';')) {\n const [key, value] = pair.trim().split('=')\n if (key && value) result[key] = decodeURIComponent(value)\n }\n return result\n}\n\n// ─── Reactive locale hook ───────────────────────────────────────────────────\n\n/** @internal Context for the current locale. */\nexport const LocaleCtx = createContext<string>('en')\n\n/** Current locale signal — set by the server middleware or client-side detection. */\nexport const localeSignal = signal('en')\n\n/**\n * Read the current locale reactively.\n *\n * Returns the locale signal value directly — reactive in both SSR and CSR.\n * The server middleware sets `localeSignal` per-request, and client-side\n * `setLocale()` updates it as well.\n *\n * @example\n * ```tsx\n * const locale = useLocale() // \"en\", \"de\", etc.\n * ```\n */\nexport function useLocale(): string {\n return localeSignal()\n}\n\n/**\n * Set the locale client-side and update the URL.\n *\n * @example\n * ```tsx\n * <button onClick={() => setLocale('de')}>Deutsch</button>\n * ```\n */\nexport function setLocale(\n locale: string,\n config: I18nRoutingConfig,\n): void {\n localeSignal.set(locale)\n\n // Persist to cookie\n if (typeof document !== 'undefined') {\n document.cookie = `${config.cookieName ?? 'locale'}=${locale}; path=/; max-age=31536000`\n }\n\n // Navigate to localized URL — use pushState to avoid full page reload\n if (typeof window !== 'undefined') {\n const strategy = config.strategy ?? 'prefix-except-default'\n const { pathWithoutLocale } = extractLocaleFromPath(\n window.location.pathname,\n config.locales,\n config.defaultLocale,\n )\n const newPath = buildLocalePath(pathWithoutLocale, locale, config.defaultLocale, strategy)\n window.history.pushState(null, '', newPath)\n // Dispatch popstate so @pyreon/router picks up the URL change\n window.dispatchEvent(new PopStateEvent('popstate'))\n }\n}\n","import type { VNodeChild } from '@pyreon/core'\nimport type { UseHeadInput } from '@pyreon/head'\nimport { useHead } from '@pyreon/head'\nimport type { I18nRoutingConfig } from './i18n-routing'\nimport { extractLocaleFromPath } from './i18n-routing'\n\n// ─── Inline helpers (no node:fs dependency) ─────────────────────────────────\n// These are inlined to avoid importing from favicon.ts/og-image.ts which\n// pull in node:fs at the top level — making Meta unsafe for client bundles.\n\n/** Favicon plugin config shape (type-only). */\ninterface FaviconPluginConfig {\n source: string\n themeColor?: string\n manifest?: boolean\n locales?: Record<string, { source: string; darkSource?: string }>\n [key: string]: unknown\n}\n\nfunction faviconLinks(\n locale: string | undefined,\n config: FaviconPluginConfig,\n): Array<{ rel: string; type?: string; sizes?: string; href: string }> {\n const hasLocaleOverride = locale && config.locales?.[locale]\n const prefix = hasLocaleOverride ? `/${locale}` : ''\n const isSvg = (hasLocaleOverride ? config.locales![locale]!.source : config.source).endsWith('.svg')\n const links: Array<{ rel: string; type?: string; sizes?: string; href: string }> = []\n if (isSvg) links.push({ rel: 'icon', type: 'image/svg+xml', href: `${prefix}/favicon.svg` })\n links.push(\n { rel: 'icon', type: 'image/png', sizes: '32x32', href: `${prefix}/favicon-32x32.png` },\n { rel: 'icon', type: 'image/png', sizes: '16x16', href: `${prefix}/favicon-16x16.png` },\n { rel: 'apple-touch-icon', sizes: '180x180', href: `${prefix}/apple-touch-icon.png` },\n )\n if (config.manifest !== false) links.push({ rel: 'manifest', href: `${prefix}/site.webmanifest` })\n return links\n}\n\nfunction ogImagePath(templateName: string, locale?: string, outDir = 'og', format: 'png' | 'jpeg' = 'png'): string {\n const ext = format === 'jpeg' ? 'jpg' : 'png'\n const suffix = locale ? `-${locale}` : ''\n return `/${outDir}/${templateName}${suffix}.${ext}`\n}\n\n// ─── Meta component ────────────────────────────────────────────────────────\n\nexport interface MetaProps {\n /** Page title. Accepts reactive accessor `() => string`. */\n title?: string | (() => string)\n /** Page description. Accepts reactive accessor. */\n description?: string | (() => string)\n /** Canonical URL. Also sets og:url. */\n canonical?: string\n /** Open Graph image URL. Also sets twitter:image. */\n image?: string\n /** Image alt text for accessibility. */\n imageAlt?: string\n /** Image width in pixels (og:image:width). Helps crawlers layout before loading. */\n imageWidth?: number\n /** Image height in pixels (og:image:height). */\n imageHeight?: number\n /** Open Graph type. Default: \"website\" */\n type?: 'website' | 'article' | 'product' | 'profile'\n /** Site name for og:site_name. */\n siteName?: string\n /** Twitter card type. Default: \"summary_large_image\" */\n twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player'\n /** Twitter @handle. */\n twitterSite?: string\n /** Twitter creator @handle. */\n twitterCreator?: string\n /** Locale. Default: \"en_US\" */\n locale?: string\n /** Alternate locales for hreflang. */\n alternateLocales?: Array<{ locale: string; url: string }>\n /** Robots directives. Default: \"index, follow\" */\n robots?: string\n /** Convenience: set `true` to emit `noindex, nofollow`. Overrides `robots`. */\n noIndex?: boolean\n /** Published time (ISO 8601) for article type. */\n publishedTime?: string\n /** Modified time (ISO 8601) for article type. */\n modifiedTime?: string\n /** Article author. */\n author?: string\n /** Article tags. */\n tags?: string[]\n /** JSON-LD structured data object. */\n jsonLd?: Record<string, unknown>\n /** Additional custom meta tags. */\n extra?: Array<{ name?: string; property?: string; content: string }>\n /**\n * Open Graph video URL. Also sets og:video:type if the URL ends with\n * a known extension (.mp4, .webm).\n */\n video?: string\n /** Video width in pixels. */\n videoWidth?: number\n /** Video height in pixels. */\n videoHeight?: number\n /**\n * Open Graph audio URL.\n */\n audio?: string\n /**\n * I18n routing config — when provided, auto-generates hreflang alternate\n * links for all locales based on the current path.\n * Also sets og:locale and og:locale:alternate.\n */\n i18n?: I18nRoutingConfig\n /** Base URL for building absolute hreflang URLs. e.g. \"https://example.com\" */\n origin?: string\n /**\n * Favicon plugin config — when provided, injects locale-aware favicon\n * `<link>` tags into `<head>`. Uses the current locale to select\n * the correct favicon set.\n */\n favicon?: FaviconPluginConfig\n /**\n * OG image template name — auto-resolves to the correct locale-specific\n * OG image path generated by `ogImagePlugin`.\n * Sets both `og:image` and `twitter:image` unless `image` is also provided.\n */\n ogTemplate?: string\n /** Output directory for OG images. Default: \"og\" */\n ogImageDir?: string\n /** OG image format. Default: \"png\" */\n ogImageFormat?: 'png' | 'jpeg'\n children?: VNodeChild\n}\n\nconst resolveStr = (v: string | (() => string) | undefined): string | undefined =>\n typeof v === 'function' ? v() : v\n\n/**\n * Declarative meta component for SSR-compatible page metadata.\n *\n * Supports reactive title/description — when passed as `() => string` accessors,\n * they are forwarded to `useHead()` as a reactive getter so updates propagate\n * automatically via signal tracking.\n *\n * @example\n * ```tsx\n * <Meta title=\"My Page\" description=\"...\" image=\"/og.jpg\" canonical=\"https://...\" />\n * ```\n *\n * @example Reactive title\n * ```tsx\n * const count = signal(0)\n * <Meta title={() => `${count()} items`} />\n * ```\n */\nexport function Meta(props: MetaProps): VNodeChild {\n const hasReactiveTitle = typeof props.title === 'function'\n const hasReactiveDescription = typeof props.description === 'function'\n\n // If title or description are reactive accessors, pass a getter to useHead\n // so it re-evaluates when the signals change.\n if (hasReactiveTitle || hasReactiveDescription) {\n useHead((): UseHeadInput => {\n const title = resolveStr(props.title)\n const description = resolveStr(props.description)\n const resolved = { ...props, title, description } as Parameters<typeof buildMetaTags>[0]\n const tags = buildMetaTags(resolved)\n const input: UseHeadInput = { meta: tags.meta, link: tags.link, script: tags.script }\n if (title) input.title = title\n return input\n })\n } else {\n const title = resolveStr(props.title)\n const description = resolveStr(props.description)\n const resolved = { ...props, title, description } as Parameters<typeof buildMetaTags>[0]\n const tags = buildMetaTags(resolved)\n const input: UseHeadInput = { meta: tags.meta, link: tags.link, script: tags.script }\n if (title) input.title = title\n useHead(input)\n }\n\n return props.children ?? null\n}\n\ninterface MetaTagEntry {\n name?: string\n property?: string\n content: string\n [key: string]: string | undefined\n}\n\ninterface LinkTagEntry {\n rel: string\n href?: string\n hreflang?: string\n type?: string\n sizes?: string\n [key: string]: string | undefined\n}\n\ninterface ScriptTagEntry {\n type: string\n children: string\n}\n\ninterface MetaTags {\n meta: MetaTagEntry[]\n link: LinkTagEntry[]\n script: ScriptTagEntry[]\n}\n\nexport function buildMetaTags(\n props: Omit<MetaProps, 'title' | 'description' | 'children'> & {\n title?: string\n description?: string\n },\n): MetaTags {\n const meta: MetaTagEntry[] = []\n const link: LinkTagEntry[] = []\n const script: ScriptTagEntry[] = []\n\n const {\n title, description, canonical, imageAlt, imageWidth, imageHeight,\n type = 'website', siteName,\n twitterCard = 'summary_large_image', twitterSite, twitterCreator,\n locale = 'en_US', alternateLocales,\n publishedTime, modifiedTime, author, tags, jsonLd, extra,\n video, videoWidth, videoHeight, audio,\n favicon, ogTemplate, ogImageDir, ogImageFormat,\n } = props\n\n // noIndex convenience overrides robots\n const robots = props.noIndex ? 'noindex, nofollow' : (props.robots ?? 'index, follow')\n\n // Resolve image: explicit `image` prop takes precedence over `ogTemplate`\n const image = props.image ?? (\n ogTemplate\n ? ogImagePath(ogTemplate, locale !== 'en_US' ? locale : undefined, ogImageDir, ogImageFormat)\n : undefined\n )\n\n // Auto-resolve image dimensions for OG template images\n const resolvedImageWidth = imageWidth ?? (ogTemplate && !props.image ? 1200 : undefined)\n const resolvedImageHeight = imageHeight ?? (ogTemplate && !props.image ? 630 : undefined)\n\n if (description) meta.push({ name: 'description', content: description })\n if (robots) meta.push({ name: 'robots', content: robots })\n if (author) meta.push({ name: 'author', content: author })\n\n if (title) meta.push({ property: 'og:title', content: title })\n if (description) meta.push({ property: 'og:description', content: description })\n if (canonical) meta.push({ property: 'og:url', content: canonical })\n if (image) meta.push({ property: 'og:image', content: image })\n if (imageAlt) meta.push({ property: 'og:image:alt', content: imageAlt })\n if (resolvedImageWidth) meta.push({ property: 'og:image:width', content: String(resolvedImageWidth) })\n if (resolvedImageHeight) meta.push({ property: 'og:image:height', content: String(resolvedImageHeight) })\n meta.push({ property: 'og:type', content: type })\n if (siteName) meta.push({ property: 'og:site_name', content: siteName })\n meta.push({ property: 'og:locale', content: locale })\n\n // Video\n if (video) {\n meta.push({ property: 'og:video', content: video })\n if (videoWidth) meta.push({ property: 'og:video:width', content: String(videoWidth) })\n if (videoHeight) meta.push({ property: 'og:video:height', content: String(videoHeight) })\n // Auto-detect video type from extension\n if (video.endsWith('.mp4')) meta.push({ property: 'og:video:type', content: 'video/mp4' })\n else if (video.endsWith('.webm')) meta.push({ property: 'og:video:type', content: 'video/webm' })\n }\n\n // Audio\n if (audio) {\n meta.push({ property: 'og:audio', content: audio })\n }\n\n if (type === 'article') {\n if (publishedTime) meta.push({ property: 'article:published_time', content: publishedTime })\n if (modifiedTime) meta.push({ property: 'article:modified_time', content: modifiedTime })\n if (author) meta.push({ property: 'article:author', content: author })\n if (tags) for (const tag of tags) meta.push({ property: 'article:tag', content: tag })\n }\n\n meta.push({ name: 'twitter:card', content: twitterCard })\n if (title) meta.push({ name: 'twitter:title', content: title })\n if (description) meta.push({ name: 'twitter:description', content: description })\n if (image) meta.push({ name: 'twitter:image', content: image })\n if (imageAlt) meta.push({ name: 'twitter:image:alt', content: imageAlt })\n if (twitterSite) meta.push({ name: 'twitter:site', content: twitterSite })\n if (twitterCreator) meta.push({ name: 'twitter:creator', content: twitterCreator })\n\n if (canonical) link.push({ rel: 'canonical', href: canonical })\n if (alternateLocales) {\n for (const alt of alternateLocales) {\n link.push({ rel: 'alternate', hreflang: alt.locale, href: alt.url })\n }\n }\n\n if (jsonLd) {\n script.push({\n type: 'application/ld+json',\n children: JSON.stringify({ '@context': 'https://schema.org', ...jsonLd }),\n })\n }\n\n if (extra) for (const tag of extra) meta.push(tag)\n\n // I18n: auto-generate hreflang alternates from i18nRouting config\n if (props.i18n) {\n const i18nConfig = props.i18n\n const origin = props.origin ?? ''\n const currentPath = canonical?.replace(origin, '') ?? '/'\n const { pathWithoutLocale } = extractLocaleFromPath(\n currentPath,\n i18nConfig.locales,\n i18nConfig.defaultLocale,\n )\n const strategy = i18nConfig.strategy ?? 'prefix-except-default'\n\n for (const loc of i18nConfig.locales) {\n const localizedPath =\n strategy === 'prefix-except-default' && loc === i18nConfig.defaultLocale\n ? pathWithoutLocale\n : `/${loc}${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`\n\n link.push({\n rel: 'alternate',\n hreflang: loc,\n href: `${origin}${localizedPath}`,\n })\n\n // og:locale:alternate for non-current locales\n if (loc !== locale) {\n meta.push({ property: 'og:locale:alternate', content: loc })\n }\n }\n\n // x-default hreflang pointing to default locale\n link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: `${origin}${pathWithoutLocale}`,\n })\n }\n\n // Favicon: inject locale-aware favicon links\n if (favicon) {\n const faviconLocale = locale !== 'en_US' ? locale : undefined\n for (const fl of faviconLinks(faviconLocale, favicon)) {\n link.push(fl as LinkTagEntry)\n }\n // Theme color meta from favicon config\n if (favicon.themeColor) {\n meta.push({ name: 'theme-color', content: favicon.themeColor })\n }\n }\n\n return { meta, link, script }\n}\n","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** SSR fallback when system preference can't be detected. Default: 'light'. */\nlet _ssrDefault: 'light' | 'dark' = 'light'\n\n/**\n * Set the default theme for SSR (when `matchMedia` is unavailable).\n * Call once at server startup before rendering.\n */\nexport function setSSRThemeDefault(value: 'light' | 'dark'): void {\n _ssrDefault = value\n}\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return _ssrDefault\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes\n const dispose = effect(() => {\n document.documentElement.dataset.theme = resolvedTheme()\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()`\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils/use-intersection-observer.ts","../../../core/core/lib/jsx-runtime.js","../src/image.tsx","../src/link.tsx","../src/script.tsx","../src/i18n-routing.ts","../src/meta.tsx","../src/theme.tsx","../src/index.ts"],"sourcesContent":["import { onMount, onUnmount } from '@pyreon/core'\n\n/**\n * Observes an element and calls `onIntersect` once it enters the viewport.\n * Automatically disconnects after the first intersection.\n *\n * @param getElement - Getter for the target element (may be undefined before mount).\n * @param onIntersect - Callback fired when the element becomes visible.\n * @param rootMargin - IntersectionObserver rootMargin. Default: \"200px\".\n */\nexport function useIntersectionObserver(\n getElement: () => HTMLElement | undefined,\n onIntersect: () => void,\n rootMargin = '200px',\n) {\n onMount(() => {\n const el = getElement()\n if (!el) return undefined\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n onIntersect()\n observer.disconnect()\n }\n }\n },\n { rootMargin },\n )\n\n observer.observe(el)\n onUnmount(() => observer.disconnect())\n return undefined\n })\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { createRef } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { FormatSource } from './image-plugin'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Image optimization component ───────────────────────────────────────────\n//\n// <Image> provides:\n// - Lazy loading via IntersectionObserver (loads when near viewport)\n// - Automatic width/height to prevent CLS (Cumulative Layout Shift)\n// - Responsive srcset generation from width descriptors\n// - Multi-format support via <picture> (WebP/AVIF with fallback)\n// - Blur-up placeholder while loading\n// - Priority loading for above-the-fold images\n\nexport interface ImageProps {\n /** Image source URL. */\n src: string\n /** Alt text (required for accessibility). */\n alt: string\n /** Intrinsic width of the image. */\n width: number\n /** Intrinsic height of the image. */\n height: number\n /** Responsive sizes attribute. Default: \"100vw\" */\n sizes?: string\n /** Responsive srcset string or source array. */\n srcset?: string | ImageSource[]\n /** Per-format source sets for <picture>. Provided automatically by imagePlugin. */\n formats?: FormatSource[]\n /** Loading strategy. \"lazy\" uses IntersectionObserver, \"eager\" loads immediately. Default: \"lazy\" */\n loading?: 'lazy' | 'eager'\n /** Mark as priority (LCP image). Disables lazy loading, adds fetchPriority=\"high\". */\n priority?: boolean\n /** Low-quality placeholder image URL or base64 data URI for blur-up effect. */\n placeholder?: string\n /** CSS class name. */\n class?: string\n /** Inline styles. */\n style?: string\n /** CSS object-fit. Default: \"cover\" */\n fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n /** Decode async. Default: true */\n decoding?: 'sync' | 'async' | 'auto'\n}\n\nexport interface ImageSource {\n src: string\n width: number\n}\n\n/**\n * Optimized image component with lazy loading, responsive images,\n * multi-format <picture> support, and blur-up placeholders.\n *\n * @example\n * // With imagePlugin — spread the import directly\n * import hero from \"./hero.jpg?optimize\"\n * <Image {...hero} alt=\"Hero\" priority />\n *\n * @example\n * // Manual usage\n * <Image src=\"/hero.jpg\" alt=\"Hero\" width={1200} height={630} />\n */\nexport function Image(props: ImageProps): VNodeChild {\n const isEager = props.priority || props.loading === 'eager'\n const loaded = signal(isEager)\n const inView = signal(isEager)\n const containerRef = createRef<HTMLElement>()\n\n // Resolve srcset from string or array\n const resolvedSrcset =\n typeof props.srcset === 'string'\n ? props.srcset\n : props.srcset?.map((s) => `${s.src} ${s.width}w`).join(', ')\n\n const sizes = props.sizes ?? '100vw'\n const fit = props.fit ?? 'cover'\n const hasFormats = props.formats && props.formats.length > 0\n const aspectRatio = `${props.width} / ${props.height}`\n\n if (!isEager) {\n useIntersectionObserver(\n () => containerRef.current ?? undefined,\n () => inView.set(true),\n )\n }\n\n // Static styles (don't depend on signals)\n const containerStyle = [\n 'position: relative',\n 'overflow: hidden',\n `aspect-ratio: ${aspectRatio}`,\n `max-width: ${props.width}px`,\n 'width: 100%',\n props.style,\n ]\n .filter(Boolean)\n .join('; ')\n\n const imgEl = (\n <img\n src={() => (inView() ? props.src : '')}\n srcSet={() => (!hasFormats && inView() && resolvedSrcset ? resolvedSrcset : '')}\n sizes={resolvedSrcset ? sizes : undefined}\n alt={props.alt}\n width={props.width}\n height={props.height}\n loading={isEager ? 'eager' : 'lazy'}\n decoding={props.decoding ?? 'async'}\n fetchPriority={props.priority ? 'high' : undefined}\n onLoad={() => loaded.set(true)}\n style={() =>\n [\n 'display: block',\n 'width: 100%',\n 'height: 100%',\n `object-fit: ${fit}`,\n 'transition: opacity 0.3s ease',\n props.placeholder && !loaded() ? 'opacity: 0' : 'opacity: 1',\n ].join('; ')\n }\n />\n )\n\n return (\n <div ref={containerRef} class={props.class} style={containerStyle}>\n {props.placeholder && (\n <img\n src={props.placeholder}\n alt=\"\"\n aria-hidden=\"true\"\n loading=\"eager\"\n style={() =>\n [\n 'position: absolute',\n 'inset: 0',\n 'width: 100%',\n 'height: 100%',\n 'object-fit: cover',\n 'filter: blur(20px)',\n 'transform: scale(1.1)',\n 'transition: opacity 0.4s ease',\n loaded() ? 'opacity: 0; pointer-events: none' : 'opacity: 1',\n ].join('; ')\n }\n />\n )}\n {hasFormats ? (\n <picture>\n {props.formats?.map((fmt) => (\n <source\n type={fmt.type}\n srcSet={() => (inView() ? (fmt.srcset ?? '') : '')}\n sizes={sizes}\n />\n ))}\n {imgEl}\n </picture>\n ) : (\n imgEl\n )}\n </div>\n )\n}\n","import { createRef } from '@pyreon/core'\nimport { useRouter } from '@pyreon/router'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Link component with prefetching ────────────────────────────────────────\n//\n// Provides client-side navigation, prefetching, and active state tracking.\n// Three levels of API:\n//\n// 1. useLink(props) — composable returning handlers, state, and ref callback\n// 2. createLink(Comp) — HOC wrapping any component with link behavior\n// 3. Link — default <a>-based link (built on createLink)\n\nexport interface LinkProps {\n /** Target URL path. */\n href: string\n /** Link content. */\n children?: any\n /** CSS class name. */\n class?: string\n /** Class applied when this link matches the current route. */\n activeClass?: string\n /** Class applied when this link exactly matches the current route. */\n exactActiveClass?: string\n /** Prefetch strategy. Default: \"hover\" */\n prefetch?: 'hover' | 'viewport' | 'none'\n /** Open in new tab. */\n external?: boolean\n /** Inline styles. */\n style?: string\n /** ARIA label. */\n 'aria-label'?: string\n /** Additional click handler — called before navigation. Call e.preventDefault() to cancel. */\n onClick?: ((e: MouseEvent) => void) | undefined\n}\n\n/** Props passed to a custom component via createLink. */\nexport interface LinkRenderProps {\n href: string\n ref: import('@pyreon/core').Ref<HTMLAnchorElement>\n onClick: (e: MouseEvent) => void\n onMouseEnter: () => void\n onTouchStart: () => void\n isActive: () => boolean\n isExactActive: () => boolean\n /** Reactive class string — pass directly to element for auto-updates on route change. */\n class: (() => string) | string | undefined\n style?: string\n target?: string\n rel?: string\n 'aria-label'?: string\n children?: any\n}\n\n/** Return type of useLink. */\nexport interface UseLinkReturn {\n /** Ref object — attach to the root element for viewport-based prefetch. */\n ref: import('@pyreon/core').Ref<HTMLAnchorElement>\n /** Click handler — performs client-side navigation. */\n handleClick: (e: MouseEvent) => void\n /** Mouse enter handler — triggers hover prefetch. */\n handleMouseEnter: () => void\n /** Touch start handler — triggers prefetch on mobile. */\n handleTouchStart: () => void\n /** Whether the link partially matches the current route. */\n isActive: () => boolean\n /** Whether the link exactly matches the current route. */\n isExactActive: () => boolean\n /** Resolved class string including active classes. */\n classes: () => string\n}\n\nconst MAX_PREFETCH_CACHE = 200\nconst prefetched = new Set<string>()\n\nfunction doPrefetch(href: string) {\n if (prefetched.has(href)) return\n // Evict oldest entries when cache is full\n if (prefetched.size >= MAX_PREFETCH_CACHE) {\n const first = prefetched.values().next().value\n if (first) prefetched.delete(first)\n }\n prefetched.add(href)\n\n const docLink = document.createElement('link')\n docLink.rel = 'prefetch'\n docLink.href = href\n docLink.as = 'document'\n document.head.appendChild(docLink)\n\n try {\n const chunkHint = document.createElement('link')\n chunkHint.rel = 'modulepreload'\n chunkHint.href = href\n document.head.appendChild(chunkHint)\n } catch {\n // modulepreload is a hint, not critical\n }\n}\n\n/**\n * Prefetch a route's JS chunk by injecting `<link rel=\"prefetch\">` into the\n * document head. Deduplicates — calling with the same href twice is a no-op.\n *\n * @example\n * prefetchRoute('/about')\n * prefetchRoute('/dashboard')\n */\nexport function prefetchRoute(href: string): void {\n doPrefetch(href)\n}\n\n/**\n * Composable that provides all link behavior — navigation, prefetching,\n * active state, and viewport observation.\n *\n * Use this for full control when `createLink` is too opinionated.\n *\n * @example\n * function MyLink(props: LinkProps) {\n * const link = useLink(props)\n * return (\n * <button ref={link.ref} class={link.classes()} onClick={link.handleClick}>\n * {props.children}\n * </button>\n * )\n * }\n */\nexport function useLink(props: LinkProps): UseLinkReturn {\n const router = useRouter()\n const elementRef = createRef<HTMLAnchorElement>()\n const strategy = props.prefetch ?? 'hover'\n\n function handleClick(e: MouseEvent) {\n // Call user's onClick first — they may call e.preventDefault()\n if (props.onClick) {\n ;(props.onClick as (e: MouseEvent) => void)(e)\n }\n\n if (\n e.defaultPrevented ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey ||\n props.external\n ) {\n return\n }\n e.preventDefault()\n router.push(props.href)\n }\n\n function handleMouseEnter() {\n if (strategy === 'hover') {\n doPrefetch(props.href)\n }\n }\n\n function handleTouchStart() {\n if (strategy === 'hover' || strategy === 'viewport') {\n doPrefetch(props.href)\n }\n }\n\n if (strategy === 'viewport') {\n useIntersectionObserver(\n () => elementRef.current ?? undefined,\n () => doPrefetch(props.href),\n )\n }\n\n const isActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath || !props.href) return false\n if (props.href === '/') return currentPath === '/'\n return currentPath.startsWith(props.href)\n }\n\n const isExactActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath) return false\n return currentPath === props.href\n }\n\n const classes = () => {\n const cls: string[] = []\n if (props.class) cls.push(props.class)\n if (props.activeClass && isActive()) cls.push(props.activeClass)\n if (props.exactActiveClass && isExactActive()) cls.push(props.exactActiveClass)\n return cls.join(' ')\n }\n\n return {\n ref: elementRef,\n handleClick,\n handleMouseEnter,\n handleTouchStart,\n isActive,\n isExactActive,\n classes,\n }\n}\n\n/**\n * Higher-order component that wraps any component with link behavior.\n *\n * The wrapped component receives {@link LinkRenderProps} with all handlers,\n * active state, and accessibility attributes pre-wired.\n *\n * @example\n * // Custom button link\n * const ButtonLink = createLink((props) => (\n * <button\n * ref={props.ref}\n * class={props.class}\n * onClick={props.onClick}\n * onMouseEnter={props.onMouseEnter}\n * >\n * {props.children}\n * </button>\n * ))\n *\n * // Custom styled component\n * const CardLink = createLink((props) => (\n * <div\n * ref={props.ref}\n * class={`card ${props.isActive() ? \"card--active\" : \"\"}`}\n * onClick={props.onClick}\n * onMouseEnter={props.onMouseEnter}\n * >\n * {props.children}\n * </div>\n * ))\n *\n * // Usage\n * <ButtonLink href=\"/about\">About</ButtonLink>\n * <CardLink href=\"/posts\" prefetch=\"viewport\">Posts</CardLink>\n */\nexport function createLink(Component: (props: LinkRenderProps) => any): (props: LinkProps) => any {\n return function WrappedLink(props: LinkProps) {\n const link = useLink(props)\n\n return (\n <Component\n href={props.href}\n ref={link.ref}\n onClick={link.handleClick}\n onMouseEnter={link.handleMouseEnter}\n onTouchStart={link.handleTouchStart}\n isActive={link.isActive}\n isExactActive={link.isExactActive}\n class={link.classes}\n {...(props.style ? { style: props.style } : {})}\n {...(props.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n children={props.children}\n />\n )\n }\n}\n\n/**\n * Default navigation link built on an `<a>` tag.\n *\n * @example\n * <Link href=\"/about\" prefetch=\"viewport\">About</Link>\n * <Link href=\"/posts\" activeClass=\"nav-active\">Posts</Link>\n */\nexport const Link = createLink((props: LinkRenderProps) => (\n <a\n ref={props.ref}\n href={props.href}\n {...(props.class ? { class: props.class } : {})}\n {...(props.style ? { style: props.style } : {})}\n {...(props.target ? { target: props.target } : {})}\n {...(props.rel ? { rel: props.rel } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n {...(props.isExactActive() ? { 'aria-current': 'page' as const } : {})}\n onClick={props.onClick}\n onMouseEnter={props.onMouseEnter}\n onTouchStart={props.onTouchStart}\n >\n {props.children}\n </a>\n))\n","import type { VNodeChild } from '@pyreon/core'\nimport { createRef, onMount, onUnmount } from '@pyreon/core'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Script optimization component ─────────────────────────────────────────\n//\n// <Script> provides optimized third-party script loading:\n// - Defer loading until after hydration\n// - Load on idle (requestIdleCallback)\n// - Load on interaction (click, scroll, etc.)\n// - Load on viewport entry\n// - Worker offloading for analytics scripts\n\nexport interface ScriptProps {\n /** Script source URL. */\n src: string\n /** Loading strategy. Default: \"afterHydration\" */\n strategy?: ScriptStrategy\n /** Inline script content (alternative to src). */\n children?: string\n /** Script id for deduplication. */\n id?: string\n /** Async attribute. Default: true */\n async?: boolean\n /** onLoad callback. */\n onLoad?: () => void\n /** onError callback. */\n onError?: (error: Error) => void\n}\n\nexport type ScriptStrategy =\n | 'beforeHydration'\n | 'afterHydration'\n | 'onIdle'\n | 'onInteraction'\n | 'onViewport'\n\n/**\n * Optimized script loading component.\n *\n * @example\n * // Load analytics after page is interactive\n * <Script src=\"https://analytics.example.com/script.js\" strategy=\"onIdle\" />\n *\n * // Load chat widget when user scrolls\n * <Script src=\"/chat-widget.js\" strategy=\"onViewport\" />\n *\n * // Inline script with deferred execution\n * <Script strategy=\"afterHydration\">\n * {`console.log(\"App hydrated!\")`}\n * </Script>\n */\nexport function Script(props: ScriptProps): VNodeChild {\n function loadScript() {\n // Deduplication\n if (props.id && document.getElementById(props.id)) return\n\n const script = document.createElement('script')\n if (props.src) script.src = props.src\n if (props.id) script.id = props.id\n script.async = props.async !== false\n\n if (props.onLoad) script.onload = props.onLoad\n if (props.onError) {\n script.onerror = () => props.onError?.(new Error(`Failed to load: ${props.src}`))\n }\n\n if (props.children && !props.src) {\n script.textContent = props.children\n }\n\n document.head.appendChild(script)\n }\n\n onMount(() => {\n const strategy = props.strategy ?? 'afterHydration'\n\n switch (strategy) {\n case 'beforeHydration':\n // Already in HTML — do nothing\n break\n\n case 'afterHydration':\n // Load immediately after mount (hydration is complete)\n loadScript()\n break\n\n case 'onIdle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => loadScript(), { timeout: 5000 })\n } else {\n setTimeout(loadScript, 200)\n }\n break\n\n case 'onInteraction': {\n const events = ['click', 'scroll', 'keydown', 'touchstart']\n function handler() {\n for (const e of events) document.removeEventListener(e, handler)\n loadScript()\n }\n for (const e of events) {\n document.addEventListener(e, handler, { once: true, passive: true })\n }\n onUnmount(() => {\n for (const e of events) document.removeEventListener(e, handler)\n })\n break\n }\n\n case 'onViewport':\n // Handled below via useIntersectionObserver on the sentinel element\n break\n }\n return undefined\n })\n\n const sentinelRef = createRef<HTMLElement>()\n const strategy = props.strategy ?? 'afterHydration'\n\n if (strategy === 'onViewport') {\n useIntersectionObserver(\n () => sentinelRef.current ?? undefined,\n () => loadScript(),\n )\n }\n\n if (strategy === 'onViewport') {\n return <div ref={sentinelRef} style=\"width:0;height:0;overflow:hidden\" />\n }\n\n return null\n}\n","import { createContext } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { Plugin } from 'vite'\n\n// ─── Localized routing ─────────────────────────────────────────────────────\n//\n// Adds locale-prefixed routes to Zero's file-system router:\n// - /about → /en/about, /de/about, /cs/about\n// - / → /en, /de, /cs (or default locale without prefix)\n// - Automatic locale detection from Accept-Language header\n// - Redirect to preferred locale\n// - hreflang link generation\n//\n// Usage:\n// import { i18nRouting } from \"@pyreon/zero\"\n// export default { plugins: [zero(), i18nRouting({ locales: [\"en\", \"de\"], defaultLocale: \"en\" })] }\n\nexport interface I18nRoutingConfig {\n /** Supported locales. e.g. [\"en\", \"de\", \"cs\"] */\n locales: string[]\n /** Default locale — served without prefix (/ instead of /en/). */\n defaultLocale: string\n /** Redirect root to detected locale. Default: true */\n detectLocale?: boolean\n /** Cookie name to persist locale preference. Default: \"locale\" */\n cookieName?: string\n /** URL strategy. Default: \"prefix-except-default\" */\n strategy?: 'prefix' | 'prefix-except-default'\n}\n\nexport interface LocaleContext {\n /** Current locale code. e.g. \"en\", \"de\" */\n locale: string\n /** All supported locales. */\n locales: string[]\n /** Default locale. */\n defaultLocale: string\n /** Build a localized path. e.g. localePath(\"/about\", \"de\") → \"/de/about\" */\n localePath: (path: string, locale?: string) => string\n /** Get hreflang alternates for the current path. */\n alternates: () => Array<{ locale: string; url: string }>\n}\n\n/**\n * Detect preferred locale from Accept-Language header.\n */\nexport function detectLocaleFromHeader(\n acceptLanguage: string | null | undefined,\n locales: string[],\n defaultLocale: string,\n): string {\n if (!acceptLanguage) return defaultLocale\n\n // Parse Accept-Language: en-US,en;q=0.9,de;q=0.8\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q] = part.trim().split(';q=')\n return {\n lang: lang?.split('-')[0]?.toLowerCase() ?? '',\n quality: q ? Number.parseFloat(q) : 1,\n }\n })\n .sort((a, b) => b.quality - a.quality)\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) return lang\n }\n\n return defaultLocale\n}\n\n/**\n * Extract locale from a URL path.\n * Returns { locale, pathWithoutLocale }.\n */\nexport function extractLocaleFromPath(\n path: string,\n locales: string[],\n defaultLocale: string,\n): { locale: string; pathWithoutLocale: string } {\n const segments = path.split('/').filter(Boolean)\n const firstSegment = segments[0]?.toLowerCase()\n\n if (firstSegment && locales.includes(firstSegment)) {\n return {\n locale: firstSegment,\n pathWithoutLocale: '/' + segments.slice(1).join('/') || '/',\n }\n }\n\n return { locale: defaultLocale, pathWithoutLocale: path }\n}\n\n/**\n * Build a localized path.\n */\nexport function buildLocalePath(\n path: string,\n locale: string,\n defaultLocale: string,\n strategy: 'prefix' | 'prefix-except-default',\n): string {\n const clean = path === '/' ? '' : path\n if (strategy === 'prefix-except-default' && locale === defaultLocale) {\n return path\n }\n return `/${locale}${clean}`\n}\n\n/**\n * Create a LocaleContext for use in components and loaders.\n */\nexport function createLocaleContext(\n locale: string,\n path: string,\n config: I18nRoutingConfig,\n): LocaleContext {\n const strategy = config.strategy ?? 'prefix-except-default'\n\n return {\n locale,\n locales: config.locales,\n defaultLocale: config.defaultLocale,\n\n localePath(targetPath: string, targetLocale?: string) {\n return buildLocalePath(\n targetPath,\n targetLocale ?? locale,\n config.defaultLocale,\n strategy,\n )\n },\n\n alternates() {\n const { pathWithoutLocale } = extractLocaleFromPath(\n path,\n config.locales,\n config.defaultLocale,\n )\n return config.locales.map((loc) => ({\n locale: loc,\n url: buildLocalePath(pathWithoutLocale, loc, config.defaultLocale, strategy),\n }))\n },\n }\n}\n\n/**\n * I18n routing middleware for Zero's server.\n *\n * - Detects locale from URL prefix or Accept-Language header\n * - Redirects root to preferred locale (when detectLocale is true)\n * - Sets locale context for loaders and components\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { i18nRouting } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * plugins: [\n * i18nRouting({\n * locales: [\"en\", \"de\", \"cs\"],\n * defaultLocale: \"en\",\n * }),\n * ],\n * })\n * ```\n */\nexport function i18nRouting(config: I18nRoutingConfig): Plugin {\n const strategy = config.strategy ?? 'prefix-except-default'\n const detectEnabled = config.detectLocale !== false\n const cookieName = config.cookieName ?? 'locale'\n\n return {\n name: 'pyreon-zero-i18n-routing',\n\n // Route duplication is NOT handled here. The fs-router's `scanRouteFiles`\n // consumes the i18n config to duplicate routes per locale at build time.\n // This plugin only provides: (1) the server middleware for locale detection\n // and (2) the runtime hooks (useLocale, setLocale) for client-side use.\n configResolved() {},\n\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '/'\n\n // Skip static assets\n if (url.startsWith('/@') || url.startsWith('/__') || url.includes('.')) {\n return next()\n }\n\n const { locale } = extractLocaleFromPath(\n url,\n config.locales,\n config.defaultLocale,\n )\n\n // Redirect root to detected locale\n if (detectEnabled && url === '/') {\n const cookies = parseCookies(req.headers.cookie)\n const preferredFromCookie = cookies[cookieName]\n const preferredFromHeader = detectLocaleFromHeader(\n req.headers['accept-language'],\n config.locales,\n config.defaultLocale,\n )\n const preferred = preferredFromCookie && config.locales.includes(preferredFromCookie)\n ? preferredFromCookie\n : preferredFromHeader\n\n if (strategy === 'prefix' || preferred !== config.defaultLocale) {\n res.writeHead(302, { Location: `/${preferred}/` })\n res.end()\n return\n }\n }\n\n // Attach locale context to request for loaders\n ;(req as any).__locale = locale\n ;(req as any).__localeContext = createLocaleContext(locale, url, config)\n\n // Update the module-level signal so useLocale() returns the correct value\n localeSignal.set(locale)\n\n next()\n })\n },\n }\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n if (!header) return {}\n const result: Record<string, string> = {}\n for (const pair of header.split(';')) {\n const [key, value] = pair.trim().split('=')\n if (key && value) result[key] = decodeURIComponent(value)\n }\n return result\n}\n\n// ─── Reactive locale hook ───────────────────────────────────────────────────\n\n/** @internal Context for the current locale. */\nexport const LocaleCtx = createContext<string>('en')\n\n/** Current locale signal — set by the server middleware or client-side detection. */\nexport const localeSignal = signal('en')\n\n/**\n * Read the current locale reactively.\n *\n * Returns the locale signal value directly — reactive in both SSR and CSR.\n * The server middleware sets `localeSignal` per-request, and client-side\n * `setLocale()` updates it as well.\n *\n * @example\n * ```tsx\n * const locale = useLocale() // \"en\", \"de\", etc.\n * ```\n */\nexport function useLocale(): string {\n return localeSignal()\n}\n\n/**\n * Set the locale client-side and update the URL.\n *\n * @example\n * ```tsx\n * <button onClick={() => setLocale('de')}>Deutsch</button>\n * ```\n */\nexport function setLocale(\n locale: string,\n config: I18nRoutingConfig,\n): void {\n localeSignal.set(locale)\n\n // Persist to cookie\n if (typeof document !== 'undefined') {\n document.cookie = `${config.cookieName ?? 'locale'}=${locale}; path=/; max-age=31536000`\n }\n\n // Navigate to localized URL — use pushState to avoid full page reload\n if (typeof window !== 'undefined') {\n const strategy = config.strategy ?? 'prefix-except-default'\n const { pathWithoutLocale } = extractLocaleFromPath(\n window.location.pathname,\n config.locales,\n config.defaultLocale,\n )\n const newPath = buildLocalePath(pathWithoutLocale, locale, config.defaultLocale, strategy)\n window.history.pushState(null, '', newPath)\n // Dispatch popstate so @pyreon/router picks up the URL change\n window.dispatchEvent(new PopStateEvent('popstate'))\n }\n}\n","import type { VNodeChild } from '@pyreon/core'\nimport type { UseHeadInput } from '@pyreon/head'\nimport { useHead } from '@pyreon/head'\nimport type { I18nRoutingConfig } from './i18n-routing'\nimport { extractLocaleFromPath } from './i18n-routing'\n\n// ─── Inline helpers (no node:fs dependency) ─────────────────────────────────\n// These are inlined to avoid importing from favicon.ts/og-image.ts which\n// pull in node:fs at the top level — making Meta unsafe for client bundles.\n\n/** Favicon plugin config shape (type-only). */\ninterface FaviconPluginConfig {\n source: string\n themeColor?: string\n manifest?: boolean\n locales?: Record<string, { source: string; darkSource?: string }>\n [key: string]: unknown\n}\n\nfunction faviconLinks(\n locale: string | undefined,\n config: FaviconPluginConfig,\n): Array<{ rel: string; type?: string; sizes?: string; href: string }> {\n const hasLocaleOverride = locale && config.locales?.[locale]\n const prefix = hasLocaleOverride ? `/${locale}` : ''\n const isSvg = (hasLocaleOverride ? config.locales![locale]!.source : config.source).endsWith('.svg')\n const links: Array<{ rel: string; type?: string; sizes?: string; href: string }> = []\n if (isSvg) links.push({ rel: 'icon', type: 'image/svg+xml', href: `${prefix}/favicon.svg` })\n links.push(\n { rel: 'icon', type: 'image/png', sizes: '32x32', href: `${prefix}/favicon-32x32.png` },\n { rel: 'icon', type: 'image/png', sizes: '16x16', href: `${prefix}/favicon-16x16.png` },\n { rel: 'apple-touch-icon', sizes: '180x180', href: `${prefix}/apple-touch-icon.png` },\n )\n if (config.manifest !== false) links.push({ rel: 'manifest', href: `${prefix}/site.webmanifest` })\n return links\n}\n\nfunction ogImagePath(templateName: string, locale?: string, outDir = 'og', format: 'png' | 'jpeg' = 'png'): string {\n const ext = format === 'jpeg' ? 'jpg' : 'png'\n const suffix = locale ? `-${locale}` : ''\n return `/${outDir}/${templateName}${suffix}.${ext}`\n}\n\n// ─── Meta component ────────────────────────────────────────────────────────\n\nexport interface MetaProps {\n /** Page title. Accepts reactive accessor `() => string`. */\n title?: string | (() => string)\n /** Page description. Accepts reactive accessor. */\n description?: string | (() => string)\n /** Canonical URL. Also sets og:url. */\n canonical?: string\n /** Open Graph image URL. Also sets twitter:image. */\n image?: string\n /** Image alt text for accessibility. */\n imageAlt?: string\n /** Image width in pixels (og:image:width). Helps crawlers layout before loading. */\n imageWidth?: number\n /** Image height in pixels (og:image:height). */\n imageHeight?: number\n /** Open Graph type. Default: \"website\" */\n type?: 'website' | 'article' | 'product' | 'profile'\n /** Site name for og:site_name. */\n siteName?: string\n /** Twitter card type. Default: \"summary_large_image\" */\n twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player'\n /** Twitter @handle. */\n twitterSite?: string\n /** Twitter creator @handle. */\n twitterCreator?: string\n /** Locale. Default: \"en_US\" */\n locale?: string\n /** Alternate locales for hreflang. */\n alternateLocales?: Array<{ locale: string; url: string }>\n /** Robots directives. Default: \"index, follow\" */\n robots?: string\n /** Convenience: set `true` to emit `noindex, nofollow`. Overrides `robots`. */\n noIndex?: boolean\n /** Published time (ISO 8601) for article type. */\n publishedTime?: string\n /** Modified time (ISO 8601) for article type. */\n modifiedTime?: string\n /** Article author. */\n author?: string\n /** Article tags. */\n tags?: string[]\n /** JSON-LD structured data object. */\n jsonLd?: Record<string, unknown>\n /** Additional custom meta tags. */\n extra?: Array<{ name?: string; property?: string; content: string }>\n /**\n * Open Graph video URL. Also sets og:video:type if the URL ends with\n * a known extension (.mp4, .webm).\n */\n video?: string\n /** Video width in pixels. */\n videoWidth?: number\n /** Video height in pixels. */\n videoHeight?: number\n /**\n * Open Graph audio URL.\n */\n audio?: string\n /**\n * I18n routing config — when provided, auto-generates hreflang alternate\n * links for all locales based on the current path.\n * Also sets og:locale and og:locale:alternate.\n */\n i18n?: I18nRoutingConfig\n /** Base URL for building absolute hreflang URLs. e.g. \"https://example.com\" */\n origin?: string\n /**\n * Favicon plugin config — when provided, injects locale-aware favicon\n * `<link>` tags into `<head>`. Uses the current locale to select\n * the correct favicon set.\n */\n favicon?: FaviconPluginConfig\n /**\n * OG image template name — auto-resolves to the correct locale-specific\n * OG image path generated by `ogImagePlugin`.\n * Sets both `og:image` and `twitter:image` unless `image` is also provided.\n */\n ogTemplate?: string\n /** Output directory for OG images. Default: \"og\" */\n ogImageDir?: string\n /** OG image format. Default: \"png\" */\n ogImageFormat?: 'png' | 'jpeg'\n children?: VNodeChild\n}\n\nconst resolveStr = (v: string | (() => string) | undefined): string | undefined =>\n typeof v === 'function' ? v() : v\n\n/**\n * Declarative meta component for SSR-compatible page metadata.\n *\n * Supports reactive title/description — when passed as `() => string` accessors,\n * they are forwarded to `useHead()` as a reactive getter so updates propagate\n * automatically via signal tracking.\n *\n * @example\n * ```tsx\n * <Meta title=\"My Page\" description=\"...\" image=\"/og.jpg\" canonical=\"https://...\" />\n * ```\n *\n * @example Reactive title\n * ```tsx\n * const count = signal(0)\n * <Meta title={() => `${count()} items`} />\n * ```\n */\nexport function Meta(props: MetaProps): VNodeChild {\n const hasReactiveTitle = typeof props.title === 'function'\n const hasReactiveDescription = typeof props.description === 'function'\n\n // If title or description are reactive accessors, pass a getter to useHead\n // so it re-evaluates when the signals change.\n if (hasReactiveTitle || hasReactiveDescription) {\n useHead((): UseHeadInput => {\n const title = resolveStr(props.title)\n const description = resolveStr(props.description)\n const resolved = { ...props, title, description } as Parameters<typeof buildMetaTags>[0]\n const tags = buildMetaTags(resolved)\n const input: UseHeadInput = { meta: tags.meta, link: tags.link, script: tags.script }\n if (title) input.title = title\n return input\n })\n } else {\n const title = resolveStr(props.title)\n const description = resolveStr(props.description)\n const resolved = { ...props, title, description } as Parameters<typeof buildMetaTags>[0]\n const tags = buildMetaTags(resolved)\n const input: UseHeadInput = { meta: tags.meta, link: tags.link, script: tags.script }\n if (title) input.title = title\n useHead(input)\n }\n\n return props.children ?? null\n}\n\ninterface MetaTagEntry {\n name?: string\n property?: string\n content: string\n [key: string]: string | undefined\n}\n\ninterface LinkTagEntry {\n rel: string\n href?: string\n hreflang?: string\n type?: string\n sizes?: string\n [key: string]: string | undefined\n}\n\ninterface ScriptTagEntry {\n type: string\n children: string\n}\n\ninterface MetaTags {\n meta: MetaTagEntry[]\n link: LinkTagEntry[]\n script: ScriptTagEntry[]\n}\n\nexport function buildMetaTags(\n props: Omit<MetaProps, 'title' | 'description' | 'children'> & {\n title?: string\n description?: string\n },\n): MetaTags {\n const meta: MetaTagEntry[] = []\n const link: LinkTagEntry[] = []\n const script: ScriptTagEntry[] = []\n\n const {\n title, description, canonical, imageAlt, imageWidth, imageHeight,\n type = 'website', siteName,\n twitterCard = 'summary_large_image', twitterSite, twitterCreator,\n locale = 'en_US', alternateLocales,\n publishedTime, modifiedTime, author, tags, jsonLd, extra,\n video, videoWidth, videoHeight, audio,\n favicon, ogTemplate, ogImageDir, ogImageFormat,\n } = props\n\n // noIndex convenience overrides robots\n const robots = props.noIndex ? 'noindex, nofollow' : (props.robots ?? 'index, follow')\n\n // Resolve image: explicit `image` prop takes precedence over `ogTemplate`\n const image = props.image ?? (\n ogTemplate\n ? ogImagePath(ogTemplate, locale !== 'en_US' ? locale : undefined, ogImageDir, ogImageFormat)\n : undefined\n )\n\n // Auto-resolve image dimensions for OG template images\n const resolvedImageWidth = imageWidth ?? (ogTemplate && !props.image ? 1200 : undefined)\n const resolvedImageHeight = imageHeight ?? (ogTemplate && !props.image ? 630 : undefined)\n\n if (description) meta.push({ name: 'description', content: description })\n if (robots) meta.push({ name: 'robots', content: robots })\n if (author) meta.push({ name: 'author', content: author })\n\n if (title) meta.push({ property: 'og:title', content: title })\n if (description) meta.push({ property: 'og:description', content: description })\n if (canonical) meta.push({ property: 'og:url', content: canonical })\n if (image) meta.push({ property: 'og:image', content: image })\n if (imageAlt) meta.push({ property: 'og:image:alt', content: imageAlt })\n if (resolvedImageWidth) meta.push({ property: 'og:image:width', content: String(resolvedImageWidth) })\n if (resolvedImageHeight) meta.push({ property: 'og:image:height', content: String(resolvedImageHeight) })\n meta.push({ property: 'og:type', content: type })\n if (siteName) meta.push({ property: 'og:site_name', content: siteName })\n meta.push({ property: 'og:locale', content: locale })\n\n // Video\n if (video) {\n meta.push({ property: 'og:video', content: video })\n if (videoWidth) meta.push({ property: 'og:video:width', content: String(videoWidth) })\n if (videoHeight) meta.push({ property: 'og:video:height', content: String(videoHeight) })\n // Auto-detect video type from extension\n if (video.endsWith('.mp4')) meta.push({ property: 'og:video:type', content: 'video/mp4' })\n else if (video.endsWith('.webm')) meta.push({ property: 'og:video:type', content: 'video/webm' })\n }\n\n // Audio\n if (audio) {\n meta.push({ property: 'og:audio', content: audio })\n }\n\n if (type === 'article') {\n if (publishedTime) meta.push({ property: 'article:published_time', content: publishedTime })\n if (modifiedTime) meta.push({ property: 'article:modified_time', content: modifiedTime })\n if (author) meta.push({ property: 'article:author', content: author })\n if (tags) for (const tag of tags) meta.push({ property: 'article:tag', content: tag })\n }\n\n meta.push({ name: 'twitter:card', content: twitterCard })\n if (title) meta.push({ name: 'twitter:title', content: title })\n if (description) meta.push({ name: 'twitter:description', content: description })\n if (image) meta.push({ name: 'twitter:image', content: image })\n if (imageAlt) meta.push({ name: 'twitter:image:alt', content: imageAlt })\n if (twitterSite) meta.push({ name: 'twitter:site', content: twitterSite })\n if (twitterCreator) meta.push({ name: 'twitter:creator', content: twitterCreator })\n\n if (canonical) link.push({ rel: 'canonical', href: canonical })\n if (alternateLocales) {\n for (const alt of alternateLocales) {\n link.push({ rel: 'alternate', hreflang: alt.locale, href: alt.url })\n }\n }\n\n if (jsonLd) {\n script.push({\n type: 'application/ld+json',\n children: JSON.stringify({ '@context': 'https://schema.org', ...jsonLd }),\n })\n }\n\n if (extra) for (const tag of extra) meta.push(tag)\n\n // I18n: auto-generate hreflang alternates from i18nRouting config\n if (props.i18n) {\n const i18nConfig = props.i18n\n const origin = props.origin ?? ''\n const currentPath = canonical?.replace(origin, '') ?? '/'\n const { pathWithoutLocale } = extractLocaleFromPath(\n currentPath,\n i18nConfig.locales,\n i18nConfig.defaultLocale,\n )\n const strategy = i18nConfig.strategy ?? 'prefix-except-default'\n\n for (const loc of i18nConfig.locales) {\n const localizedPath =\n strategy === 'prefix-except-default' && loc === i18nConfig.defaultLocale\n ? pathWithoutLocale\n : `/${loc}${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`\n\n link.push({\n rel: 'alternate',\n hreflang: loc,\n href: `${origin}${localizedPath}`,\n })\n\n // og:locale:alternate for non-current locales\n if (loc !== locale) {\n meta.push({ property: 'og:locale:alternate', content: loc })\n }\n }\n\n // x-default hreflang pointing to default locale\n link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: `${origin}${pathWithoutLocale}`,\n })\n }\n\n // Favicon: inject locale-aware favicon links\n if (favicon) {\n const faviconLocale = locale !== 'en_US' ? locale : undefined\n for (const fl of faviconLinks(faviconLocale, favicon)) {\n link.push(fl as LinkTagEntry)\n }\n // Theme color meta from favicon config\n if (favicon.themeColor) {\n meta.push({ name: 'theme-color', content: favicon.themeColor })\n }\n }\n\n return { meta, link, script }\n}\n","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** SSR fallback when system preference can't be detected. Default: 'light'. */\nlet _ssrDefault: 'light' | 'dark' = 'light'\n\n/**\n * Set the default theme for SSR (when `matchMedia` is unavailable).\n * Call once at server startup before rendering.\n */\nexport function setSSRThemeDefault(value: 'light' | 'dark'): void {\n _ssrDefault = value\n}\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return _ssrDefault\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes — updates data-theme + favicons\n const dispose = effect(() => {\n const mode = resolvedTheme()\n document.documentElement.dataset.theme = mode\n\n // Swap favicon variants (if dual-variant favicons are present)\n const faviconLinks = document.querySelectorAll<HTMLLinkElement>('[data-favicon-theme]')\n for (const link of faviconLinks) {\n link.media = link.dataset.faviconTheme === mode ? '' : 'not all'\n }\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r;document.querySelectorAll(\"[data-favicon-theme]\").forEach(function(l){l.media=l.dataset.faviconTheme===r?\"\":\"not all\"})}catch(e){}})()`\n","/**\n * @pyreon/zero — client-safe exports.\n *\n * This entry contains only browser-safe components and hooks.\n * No node:fs, node:path, or other server-only imports.\n *\n * For server/build-time features, use subpath imports:\n * import { faviconPlugin } from \"@pyreon/zero/favicon\"\n * import { createServer } from \"@pyreon/zero/server\"\n * import { defineConfig } from \"@pyreon/zero/config\"\n * import { validateEnv } from \"@pyreon/zero/env\"\n */\n\n// ─── Components (browser-safe) ──────────────────────────────────────────────\n\nexport type { ImageProps, ImageSource } from \"./image\";\nexport { Image } from \"./image\";\nexport type { LinkProps, LinkRenderProps, UseLinkReturn } from \"./link\";\nexport { createLink, Link, prefetchRoute, useLink } from \"./link\";\nexport type { ScriptProps, ScriptStrategy } from \"./script\";\nexport { Script } from \"./script\";\nexport type { MetaProps } from \"./meta\";\nexport { buildMetaTags, Meta } from \"./meta\";\n\n// ─── Theme (browser-safe) ───────────────────────────────────────────────────\n\nexport type { Theme } from \"./theme\";\nexport {\n\tinitTheme,\n\tresolvedTheme,\n\tsetSSRThemeDefault,\n\tsetTheme,\n\tThemeToggle,\n\ttheme,\n\tthemeScript,\n\ttoggleTheme,\n} from \"./theme\";\n\n// ─── I18n hooks (browser-safe) ──────────────────────────────────────────────\n\nexport type { I18nRoutingConfig, LocaleContext } from \"./i18n-routing\";\nexport {\n\tbuildLocalePath,\n\textractLocaleFromPath,\n\tsetLocale,\n\tuseLocale,\n} from \"./i18n-routing\";\n\n// ─── Server-only stubs ──────────────────────────────────────────────────────\n// Throw clear error messages when developers accidentally import server-only\n// APIs from the main entry. These are tree-shaken if not imported.\n\nfunction serverOnly(name: string, subpath: string): never {\n throw new Error(\n `[Pyreon] \"${name}\" is server-only and cannot be imported from \"@pyreon/zero\".\\n` +\n `Import from the subpath instead:\\n\\n` +\n ` import { ${name} } from \"@pyreon/zero/${subpath}\"\\n`,\n )\n}\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n/** @deprecated Import from `@pyreon/zero/favicon` instead */\nexport function faviconPlugin(..._: unknown[]): never { return serverOnly('faviconPlugin', 'favicon') }\n/** @deprecated Import from `@pyreon/zero/seo` instead */\nexport function seoPlugin(..._: unknown[]): never { return serverOnly('seoPlugin', 'seo') }\n/** @deprecated Import from `@pyreon/zero/server` instead */\nexport function createServer(..._: unknown[]): never { return serverOnly('createServer', 'server') }\n/** @deprecated Import from `@pyreon/zero/config` instead */\nexport function defineConfig(..._: unknown[]): never { return serverOnly('defineConfig', 'config') }\n/** @deprecated Import from `@pyreon/zero/env` instead */\nexport function validateEnv(..._: unknown[]): never { return serverOnly('validateEnv', 'env') }\n/** @deprecated Import from `@pyreon/zero/og-image` instead */\nexport function ogImagePlugin(..._: unknown[]): never { return serverOnly('ogImagePlugin', 'og-image') }\n/** @deprecated Import from `@pyreon/zero/ai` instead */\nexport function aiPlugin(..._: unknown[]): never { return serverOnly('aiPlugin', 'ai') }\n\n// ─── Types (no runtime, safe everywhere) ────────────────────────────────────\n\nexport type {\n\tAdapter,\n\tAdapterBuildOptions,\n\tFileRoute,\n\tISRConfig,\n\tLoaderContext,\n\tRenderMode,\n\tRouteMeta,\n\tRouteMiddlewareEntry,\n\tRouteModule,\n\tZeroConfig,\n} from \"./types\";\n"],"mappings":"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"}
|
package/lib/theme.js
CHANGED
|
@@ -105,7 +105,10 @@ function initTheme() {
|
|
|
105
105
|
mq.addEventListener("change", onChange);
|
|
106
106
|
onUnmount(() => mq.removeEventListener("change", onChange));
|
|
107
107
|
const dispose = effect(() => {
|
|
108
|
-
|
|
108
|
+
const mode = resolvedTheme();
|
|
109
|
+
document.documentElement.dataset.theme = mode;
|
|
110
|
+
const faviconLinks = document.querySelectorAll("[data-favicon-theme]");
|
|
111
|
+
for (const link of faviconLinks) link.media = link.dataset.faviconTheme === mode ? "" : "not all";
|
|
109
112
|
});
|
|
110
113
|
if (dispose) onUnmount(() => dispose.dispose());
|
|
111
114
|
});
|
|
@@ -216,7 +219,7 @@ function ThemeToggle(props) {
|
|
|
216
219
|
* ...
|
|
217
220
|
* </head>
|
|
218
221
|
*/
|
|
219
|
-
const themeScript = `(function(){try{var t=localStorage.getItem("${STORAGE_KEY}");var r=t==="light"?"light":t==="dark"?"dark":window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.dataset.theme=r}catch(e){}})()`;
|
|
222
|
+
const themeScript = `(function(){try{var t=localStorage.getItem("${STORAGE_KEY}");var r=t==="light"?"light":t==="dark"?"dark":window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.dataset.theme=r;document.querySelectorAll("[data-favicon-theme]").forEach(function(l){l.media=l.dataset.faviconTheme===r?"":"not all"})}catch(e){}})()`;
|
|
220
223
|
|
|
221
224
|
//#endregion
|
|
222
225
|
export { ThemeToggle, initTheme, resolvedTheme, setSSRThemeDefault, setTheme, theme, themeScript, toggleTheme };
|
package/lib/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[],"sources":["../../../core/core/lib/jsx-runtime.js","../src/theme.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** SSR fallback when system preference can't be detected. Default: 'light'. */\nlet _ssrDefault: 'light' | 'dark' = 'light'\n\n/**\n * Set the default theme for SSR (when `matchMedia` is unavailable).\n * Call once at server startup before rendering.\n */\nexport function setSSRThemeDefault(value: 'light' | 'dark'): void {\n _ssrDefault = value\n}\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return _ssrDefault\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes\n const dispose = effect(() => {\n document.documentElement.dataset.theme = resolvedTheme()\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()`\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme.js","names":[],"sources":["../../../core/core/lib/jsx-runtime.js","../src/theme.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** SSR fallback when system preference can't be detected. Default: 'light'. */\nlet _ssrDefault: 'light' | 'dark' = 'light'\n\n/**\n * Set the default theme for SSR (when `matchMedia` is unavailable).\n * Call once at server startup before rendering.\n */\nexport function setSSRThemeDefault(value: 'light' | 'dark'): void {\n _ssrDefault = value\n}\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return _ssrDefault\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes — updates data-theme + favicons\n const dispose = effect(() => {\n const mode = resolvedTheme()\n document.documentElement.dataset.theme = mode\n\n // Swap favicon variants (if dual-variant favicons are present)\n const faviconLinks = document.querySelectorAll<HTMLLinkElement>('[data-favicon-theme]')\n for (const link of faviconLinks) {\n link.media = link.dataset.faviconTheme === mode ? '' : 'not all'\n }\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r;document.querySelectorAll(\"[data-favicon-theme]\").forEach(function(l){l.media=l.dataset.faviconTheme===r?\"\":\"not all\"})}catch(e){}})()`\n"],"mappings":"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"}
|
package/lib/types/favicon.d.ts
CHANGED
|
@@ -44,6 +44,23 @@ interface FaviconPluginConfig {
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
locales?: Record<string, FaviconLocaleConfig>;
|
|
47
|
+
/**
|
|
48
|
+
* Dev mode favicon — shown only during development to distinguish
|
|
49
|
+
* dev tabs from production. Can be:
|
|
50
|
+
* - A path to a separate icon file
|
|
51
|
+
* - `true` to auto-generate a dev badge (grayscale + "DEV" overlay)
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* faviconPlugin({
|
|
56
|
+
* source: "./icon.svg",
|
|
57
|
+
* devSource: "./icon-dev.svg", // custom dev icon
|
|
58
|
+
* // OR
|
|
59
|
+
* devSource: true, // auto-generate grayscale badge
|
|
60
|
+
* })
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
devSource?: string | boolean;
|
|
47
64
|
}
|
|
48
65
|
/**
|
|
49
66
|
* Favicon generation Vite plugin.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"favicon2.d.ts","names":[],"sources":["../../../src/favicon.ts"],"mappings":";;;UA6BiB,mBAAA;;EAEf,MAAA;EAFkC;EAIlC,UAAA;AAAA;AAAA,UAGe,mBAAA;EAAA;EAEf,MAAA;;EAEA,UAAA;EAFA;EAIA,eAAA;EAAA;EAEA,IAAA;EAEA;EAAA,QAAA;EA0BA;;;;;EApBA,UAAA;
|
|
1
|
+
{"version":3,"file":"favicon2.d.ts","names":[],"sources":["../../../src/favicon.ts"],"mappings":";;;UA6BiB,mBAAA;;EAEf,MAAA;EAFkC;EAIlC,UAAA;AAAA;AAAA,UAGe,mBAAA;EAAA;EAEf,MAAA;;EAEA,UAAA;EAFA;EAIA,eAAA;EAAA;EAEA,IAAA;EAEA;EAAA,QAAA;EA0BA;;;;;EApBA,UAAA;EAqEc;;;;;;;;;AA8XhB;;;;;;;;;;EA/aE,OAAA,GAAU,MAAA,SAAe,mBAAA;EAkb4B;;;AAmDvD;;;;;;;;;AAMA;;;;EA1dE,SAAA;AAAA;;;;;;;;;;;;;;;;;iBAgCc,aAAA,CAAc,MAAA,EAAQ,mBAAA,GAAsB,MAAA;;;;;;;;;;;iBA8X5C,YAAA,CACd,MAAA,sBACA,MAAA,EAAQ,mBAAA,GACP,KAAA;EAAQ,GAAA;EAAa,IAAA;EAAe,KAAA;EAAgB,IAAA;AAAA;AAAA,UAmDtC,QAAA;EACf,MAAA,EAAQ,MAAA;EACR,IAAA;AAAA;;iBAIc,iBAAA,CAAkB,OAAA,EAAS,QAAA,KAAa,UAAA"}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -481,7 +481,7 @@ declare function ThemeToggle(props: {
|
|
|
481
481
|
* ...
|
|
482
482
|
* </head>
|
|
483
483
|
*/
|
|
484
|
-
declare const themeScript = "(function(){try{var t=localStorage.getItem(\"zero-theme\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()";
|
|
484
|
+
declare const themeScript = "(function(){try{var t=localStorage.getItem(\"zero-theme\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r;document.querySelectorAll(\"[data-favicon-theme]\").forEach(function(l){l.media=l.dataset.faviconTheme===r?\"\":\"not all\"})}catch(e){}})()";
|
|
485
485
|
//#endregion
|
|
486
486
|
//#region src/types.d.ts
|
|
487
487
|
/** What a route file (e.g. `src/routes/index.tsx`) can export. */
|
|
@@ -590,5 +590,21 @@ interface AdapterBuildOptions {
|
|
|
590
590
|
config: ZeroConfig;
|
|
591
591
|
}
|
|
592
592
|
//#endregion
|
|
593
|
-
|
|
593
|
+
//#region src/index.d.ts
|
|
594
|
+
/** @deprecated Import from `@pyreon/zero/favicon` instead */
|
|
595
|
+
declare function faviconPlugin(..._: unknown[]): never;
|
|
596
|
+
/** @deprecated Import from `@pyreon/zero/seo` instead */
|
|
597
|
+
declare function seoPlugin(..._: unknown[]): never;
|
|
598
|
+
/** @deprecated Import from `@pyreon/zero/server` instead */
|
|
599
|
+
declare function createServer(..._: unknown[]): never;
|
|
600
|
+
/** @deprecated Import from `@pyreon/zero/config` instead */
|
|
601
|
+
declare function defineConfig(..._: unknown[]): never;
|
|
602
|
+
/** @deprecated Import from `@pyreon/zero/env` instead */
|
|
603
|
+
declare function validateEnv(..._: unknown[]): never;
|
|
604
|
+
/** @deprecated Import from `@pyreon/zero/og-image` instead */
|
|
605
|
+
declare function ogImagePlugin(..._: unknown[]): never;
|
|
606
|
+
/** @deprecated Import from `@pyreon/zero/ai` instead */
|
|
607
|
+
declare function aiPlugin(..._: unknown[]): never;
|
|
608
|
+
//#endregion
|
|
609
|
+
export { type Adapter, type AdapterBuildOptions, type FileRoute, type I18nRoutingConfig, type ISRConfig, Image, type ImageProps, type ImageSource, Link, type LinkProps, type LinkRenderProps, type LoaderContext, type LocaleContext, Meta, type MetaProps, type RenderMode, type RouteMeta, type RouteMiddlewareEntry, type RouteModule, Script, type ScriptProps, type ScriptStrategy, type Theme, ThemeToggle, type UseLinkReturn, type ZeroConfig, aiPlugin, buildLocalePath, buildMetaTags, createLink, createServer, defineConfig, extractLocaleFromPath, faviconPlugin, initTheme, ogImagePlugin, prefetchRoute, resolvedTheme, seoPlugin, setLocale, setSSRThemeDefault, setTheme, theme, themeScript, toggleTheme, useLink, useLocale, validateEnv };
|
|
594
610
|
//# sourceMappingURL=index2.d.ts.map
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/image-plugin.ts","../../../src/image.tsx","../../../src/link.tsx","../../../src/script.tsx","../../../src/i18n-routing.ts","../../../src/meta.tsx","../../../src/theme.tsx","../../../src/types.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/image-plugin.ts","../../../src/image.tsx","../../../src/link.tsx","../../../src/script.tsx","../../../src/i18n-routing.ts","../../../src/meta.tsx","../../../src/theme.tsx","../../../src/types.ts","../../../src/index.ts"],"mappings":"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"}
|
package/lib/types/server.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ interface CreateAppOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
declare function createApp(options: CreateAppOptions): {
|
|
26
26
|
App: () => _pyreon_core0.VNode;
|
|
27
|
-
router: _pyreon_router0.Router
|
|
27
|
+
router: _pyreon_router0.Router<string>;
|
|
28
28
|
};
|
|
29
29
|
//#endregion
|
|
30
30
|
//#region src/api-routes.d.ts
|
package/lib/types/theme.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ declare function ThemeToggle(props: {
|
|
|
43
43
|
* ...
|
|
44
44
|
* </head>
|
|
45
45
|
*/
|
|
46
|
-
declare const themeScript = "(function(){try{var t=localStorage.getItem(\"zero-theme\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()";
|
|
46
|
+
declare const themeScript = "(function(){try{var t=localStorage.getItem(\"zero-theme\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r;document.querySelectorAll(\"[data-favicon-theme]\").forEach(function(l){l.media=l.dataset.faviconTheme===r?\"\":\"not all\"})}catch(e){}})()";
|
|
47
47
|
//#endregion
|
|
48
48
|
export { Theme, ThemeToggle, initTheme, resolvedTheme, setSSRThemeDefault, setTheme, theme, themeScript, toggleTheme };
|
|
49
49
|
//# sourceMappingURL=theme2.d.ts.map
|
package/lib/types/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme2.d.ts","names":[],"sources":["../../../src/theme.tsx"],"mappings":";;;;KAYY,KAAA;;cAKC,KAAA,EAAK,mBAAA,CAAA,MAAA,CAAA,KAAA;AALlB;;;;AAAA,iBAcgB,kBAAA,CAAmB,KAAA;AATnC;AAAA,iBAcgB,aAAA,CAAA;;iBAUA,WAAA,CAAA;;iBAMA,QAAA,CAAS,CAAA,EAAG,KAAA;;;;;iBAgBZ,SAAA,CAAA;;;;;AAtBhB;;;
|
|
1
|
+
{"version":3,"file":"theme2.d.ts","names":[],"sources":["../../../src/theme.tsx"],"mappings":";;;;KAYY,KAAA;;cAKC,KAAA,EAAK,mBAAA,CAAA,MAAA,CAAA,KAAA;AALlB;;;;AAAA,iBAcgB,kBAAA,CAAmB,KAAA;AATnC;AAAA,iBAcgB,aAAA,CAAA;;iBAUA,WAAA,CAAA;;iBAMA,QAAA,CAAS,CAAA,EAAG,KAAA;;;;;iBAgBZ,SAAA,CAAA;;;;;AAtBhB;;;iBAuEgB,WAAA,CAAY,KAAA;EAAS,KAAA;EAAgB,KAAA;AAAA,IAAmB,UAAA;;;;AAjDxE;;;;;AAiDA;;;cAkEa,WAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/zero",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
4
4
|
"description": "Pyreon Zero — zero-config full-stack framework powered by Pyreon and Vite",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vit Bokisch",
|
|
@@ -166,17 +166,17 @@
|
|
|
166
166
|
"lint": "oxlint ."
|
|
167
167
|
},
|
|
168
168
|
"dependencies": {
|
|
169
|
-
"@pyreon/core": "^0.12.
|
|
170
|
-
"@pyreon/head": "^0.12.
|
|
171
|
-
"@pyreon/meta": "^0.12.
|
|
172
|
-
"@pyreon/router": "^0.12.
|
|
173
|
-
"@pyreon/runtime-dom": "^0.12.
|
|
174
|
-
"@pyreon/runtime-server": "^0.12.
|
|
175
|
-
"@pyreon/server": "^0.12.
|
|
176
|
-
"@pyreon/vite-plugin": "^0.12.
|
|
169
|
+
"@pyreon/core": "^0.12.5",
|
|
170
|
+
"@pyreon/head": "^0.12.5",
|
|
171
|
+
"@pyreon/meta": "^0.12.5",
|
|
172
|
+
"@pyreon/router": "^0.12.5",
|
|
173
|
+
"@pyreon/runtime-dom": "^0.12.5",
|
|
174
|
+
"@pyreon/runtime-server": "^0.12.5",
|
|
175
|
+
"@pyreon/server": "^0.12.5",
|
|
176
|
+
"@pyreon/vite-plugin": "^0.12.5",
|
|
177
177
|
"vite": "^8.0.0"
|
|
178
178
|
},
|
|
179
179
|
"peerDependencies": {
|
|
180
|
-
"@pyreon/reactivity": "^0.12.
|
|
180
|
+
"@pyreon/reactivity": "^0.12.5"
|
|
181
181
|
}
|
|
182
182
|
}
|