@jdcpuwiz/homelab-ui 0.10.1 → 0.10.3
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/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -13
- package/dist/index.d.ts +39 -13
- package/dist/index.js.map +1 -1
- package/fonts/LICENSE-jetbrains-mono.txt +93 -0
- package/fonts/LICENSE-orbitron.txt +93 -0
- package/fonts/LICENSE-poppins.txt +93 -0
- package/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
- package/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
- package/fonts/orbitron-latin-400-normal.woff2 +0 -0
- package/fonts/orbitron-latin-500-normal.woff2 +0 -0
- package/fonts/orbitron-latin-600-normal.woff2 +0 -0
- package/fonts/orbitron-latin-700-normal.woff2 +0 -0
- package/fonts/orbitron-latin-800-normal.woff2 +0 -0
- package/fonts/orbitron-latin-900-normal.woff2 +0 -0
- package/fonts/poppins-latin-300-normal.woff2 +0 -0
- package/fonts/poppins-latin-400-normal.woff2 +0 -0
- package/fonts/poppins-latin-500-normal.woff2 +0 -0
- package/fonts/poppins-latin-600-normal.woff2 +0 -0
- package/fonts/poppins-latin-700-normal.woff2 +0 -0
- package/fonts/poppins-latin-800-normal.woff2 +0 -0
- package/fonts.css +148 -0
- package/package.json +11 -7
- package/starter/next-layout.tsx +40 -34
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Button.tsx","../src/lib/utils.ts","../src/components/ConfirmDialog.tsx","../src/components/Modal.tsx","../src/components/EmptyState.tsx","../src/components/PageHeader.tsx","../src/components/Sidebar.tsx","../src/components/SidebarNavItem.tsx","../src/components/Spinner.tsx","../src/components/StatTile.tsx","../src/lib/colors.ts","../src/components/TagChips.tsx","../src/lib/fonts.ts"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ntype Variant = \"primary\" | \"secondary\" | \"tertiary\" | \"danger\";\ntype Size = \"sm\" | \"md\";\n\nexport type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n variant?: Variant;\n size?: Size;\n};\n\nconst BASE =\n \"inline-flex items-center justify-center gap-1.5 rounded-[0.5rem] font-semibold transition active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--hl-brand-glow)]\";\n\n// `danger`'s BACKGROUND stays a literal — status colors are a fixed fleet\n// semantic (the preset hardcodes them too), not a per-project brand token.\n// Its label stays literal `text-white` for the same reason: that is text\n// ON a status chip, which lib/colors.ts already rules is always white,\n// NOT reading text on an app surface. Everything else here is a token.\nconst VARIANT: Record<Variant, string> = {\n primary:\n \"bg-[var(--hl-brand)] text-[var(--hl-brand-fg)] hover:bg-[var(--hl-brand-hover)]\",\n secondary:\n \"bg-[var(--hl-card)] text-[var(--hl-foreground)] hover:bg-[var(--hl-card-hover)]\",\n tertiary:\n \"text-[var(--hl-foreground-muted)] hover:bg-[var(--hl-card-hover)] hover:text-[var(--hl-foreground)]\",\n danger: \"bg-[#b91c1c] text-white hover:brightness-110\",\n};\n\nconst SIZE: Record<Size, string> = {\n sm: \"px-3 py-1 text-xs\",\n md: \"px-4 py-1.5 text-sm\",\n};\n\n/**\n * Homelab Button. Use the variant + size props; never pass raw color\n * classes through className. className is for layout extras only\n * (e.g. `w-full`).\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n { variant = \"secondary\", size = \"md\", className, ...rest },\n ref,\n ) {\n return (\n <button\n ref={ref}\n {...rest}\n className={cn(BASE, VARIANT[variant], SIZE[size], className)}\n />\n );\n },\n);\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Conditional Tailwind class joiner. Merges variants and resolves\n * conflicts (e.g. `cn(\"p-2\", \"p-4\")` → `\"p-4\"`).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Modal } from \"./Modal\";\nimport { Button } from \"./Button\";\n\nexport type ConfirmDialogProps = {\n open: boolean;\n title: string;\n message: string;\n confirmLabel?: string;\n cancelLabel?: string;\n tone?: \"danger\" | \"primary\";\n onConfirm: () => void;\n onClose: () => void;\n};\n\n/**\n * Confirm/cancel dialog built on Modal + Button. Enter triggers confirm,\n * Esc cancels (via Modal's built-in handling).\n */\nexport function ConfirmDialog({\n open,\n title,\n message,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n tone = \"danger\",\n onConfirm,\n onClose,\n}: ConfirmDialogProps) {\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Enter\") onConfirm();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open, onConfirm]);\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={title}\n footer={\n <>\n <Button variant=\"tertiary\" size=\"md\" onClick={onClose}>\n {cancelLabel}\n </Button>\n <Button\n variant={tone === \"danger\" ? \"danger\" : \"primary\"}\n size=\"md\"\n onClick={onConfirm}\n >\n {confirmLabel}\n </Button>\n </>\n }\n >\n <p className=\"whitespace-pre-line text-sm text-[var(--hl-foreground)]\">{message}</p>\n </Modal>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\ntype Density = \"form\" | \"media\";\n\nexport type ModalProps = {\n open: boolean;\n onClose: () => void;\n title?: string;\n /** Show the close X in the top right. Default: true when title is set. */\n showClose?: boolean;\n /**\n * Overlay weight:\n * `form` — dialogs / pickers / confirms (`bg-black/70`)\n * `media` — lightbox / asset preview (`bg-black/90`)\n * Defaults to `form`.\n */\n density?: Density;\n /** Max width — default `max-w-md`. */\n maxWidth?: string;\n /** Footer slot — render buttons here. Always right-aligned. */\n footer?: React.ReactNode;\n children: React.ReactNode;\n};\n\n/**\n * Single modal frame. Every dialog routes through this:\n * - overlay + click-outside dismiss\n * - Esc-to-close\n * - role=dialog + aria-modal + aria-labelledby wiring\n * - DESIGN.md header treatment (uppercase tracking-widest)\n * - shared right-aligned footer row\n */\nexport function Modal({\n open,\n onClose,\n title,\n showClose,\n density = \"form\",\n maxWidth = \"max-w-md\",\n footer,\n children,\n}: ModalProps) {\n const titleIdRef = useRef(\n `hl-modal-${Math.random().toString(36).slice(2, 8)}`,\n );\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open, onClose]);\n\n if (!open) return null;\n\n const showCloseBtn = showClose ?? Boolean(title);\n const overlayClass = density === \"media\" ? \"bg-black/90\" : \"bg-black/70\";\n\n return (\n <div\n className={cn(\n \"fixed inset-0 z-50 flex items-center justify-center p-4\",\n overlayClass,\n )}\n onClick={onClose}\n role=\"presentation\"\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleIdRef.current : undefined}\n className={cn(\n \"w-full rounded-[1rem] border-2 border-[var(--hl-border)] bg-[var(--hl-sidebar)] p-5\",\n maxWidth,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || showCloseBtn) && (\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n {title && (\n <h2\n id={titleIdRef.current}\n className=\"text-sm font-semibold uppercase tracking-widest text-[var(--hl-foreground)]\"\n >\n {title}\n </h2>\n )}\n {showCloseBtn && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"ml-auto rounded-[0.25rem] p-1 text-[var(--hl-foreground-muted)] transition hover:bg-[var(--hl-card-hover)] hover:text-[var(--hl-foreground)] active:scale-90\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n )}\n {children}\n {footer && (\n <div className=\"mt-5 flex flex-wrap items-center justify-end gap-2\">\n {footer}\n </div>\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { cn } from \"../lib/utils\";\n\nexport type EmptyStateProps = {\n icon?: React.ReactNode;\n title?: string;\n message: string;\n action?: React.ReactNode;\n /**\n * `panel` — large empty state with warm card background (page bodies).\n * `inline` — compact text-only line for sidebar tree, popovers, etc.\n */\n variant?: \"panel\" | \"inline\";\n className?: string;\n};\n\nexport function EmptyState({\n icon,\n title,\n message,\n action,\n variant = \"panel\",\n className,\n}: EmptyStateProps) {\n if (variant === \"inline\") {\n return (\n <p className={cn(\"px-3 py-2 text-xs text-[var(--hl-foreground)]\", className)}>\n {message}\n </p>\n );\n }\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center gap-3 rounded-[1rem] bg-[var(--hl-card-warm)] p-12 text-center\",\n className,\n )}\n >\n {icon && (\n <div className=\"text-[var(--hl-foreground-faint)]\">{icon}</div>\n )}\n {title && (\n <h3 className=\"text-sm font-semibold text-[var(--hl-foreground)]\">\n {title}\n </h3>\n )}\n <p className=\"text-sm text-[var(--hl-foreground)]\">{message}</p>\n {action}\n </div>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { ArrowLeft, ChevronRight, ImageOff } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\nexport type Crumb = {\n label: string;\n onClick?: () => void;\n};\n\nexport type PageHeaderProps = {\n title: string;\n description?: string;\n /** Back link rendered above the title. */\n back?: { href: string; label: string; render?: (href: string, children: React.ReactNode) => React.ReactNode };\n /** Right-aligned actions slot. */\n actions?: React.ReactNode;\n /**\n * Header image URL (e.g. `/logos.png`). When the URL resolves, the\n * image is the visual title. When it 404s, a styled placeholder at\n * the same dimensions takes its place — the layout stays stable so\n * creating a new themed page \"just works\" the moment you drop a\n * matching PNG in /public/.\n *\n * When `imageSrc` is undefined, no image block renders and the\n * text title is the visual title.\n */\n imageSrc?: string;\n imageAlt?: string;\n /**\n * Breadcrumb trail. When provided, replaces the text title. Last\n * crumb is current (full foreground + bold); earlier crumbs are muted\n * and clickable.\n */\n breadcrumb?: Crumb[];\n className?: string;\n};\n\n/**\n * Page-header block. The image block reserves space whenever `imageSrc`\n * is set — drop the PNG in /public/ and the layout just fills in.\n *\n * Text title rules:\n * - imageSrc set + image present → text becomes sr-only\n * - imageSrc set + image 404 → placeholder block + text becomes sr-only\n * - imageSrc unset → text title is visible\n * - breadcrumb provided → breadcrumb replaces text title regardless of image\n *\n * The back link uses a plain <a> by default. In Next.js, pass\n * `back.render` to use next/link:\n *\n * import Link from \"next/link\";\n * <PageHeader back={{ href: \"/foo\", label: \"Back\", render: (href, c) => <Link href={href}>{c}</Link> }} />\n */\nexport function PageHeader({\n title,\n description,\n back,\n actions,\n imageSrc,\n imageAlt,\n breadcrumb,\n className,\n}: PageHeaderProps) {\n const [imageErrored, setImageErrored] = useState(false);\n useEffect(() => {\n setImageErrored(false);\n }, [imageSrc]);\n\n const hasImageSlot = !!imageSrc;\n const imageRenders = hasImageSlot && !imageErrored;\n const hasBreadcrumb = Array.isArray(breadcrumb) && breadcrumb.length > 0;\n const textIsSrOnly = hasImageSlot;\n\n const backInner = back ? (\n <>\n <ArrowLeft className=\"h-3 w-3\" />\n {back.label}\n </>\n ) : null;\n\n return (\n <header\n className={cn(\n \"flex flex-wrap items-end justify-between gap-3\",\n className,\n )}\n >\n <div className=\"flex min-w-0 items-end gap-4\">\n {hasImageSlot &&\n (imageRenders ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={imageSrc}\n alt={imageAlt ?? title}\n onError={() => setImageErrored(true)}\n className=\"h-28 w-28 shrink-0 rounded-[1rem] object-contain md:h-32 md:w-32\"\n />\n ) : (\n <PlaceholderHeader title={title} imageSrc={imageSrc!} />\n ))}\n <div className=\"min-w-0\">\n {back &&\n (back.render ? (\n back.render(\n back.href,\n <span className=\"inline-flex items-center gap-1 text-xs text-[var(--hl-foreground-muted)] transition hover:text-[var(--hl-foreground)]\">\n {backInner}\n </span>,\n )\n ) : (\n <a\n href={back.href}\n className=\"inline-flex items-center gap-1 text-xs text-[var(--hl-foreground-muted)] transition hover:text-[var(--hl-foreground)]\"\n >\n {backInner}\n </a>\n ))}\n {hasBreadcrumb ? (\n <Breadcrumb\n crumbs={breadcrumb!}\n srTitle={title}\n className={back ? \"mt-1\" : \"\"}\n />\n ) : (\n <h1\n className={cn(\n \"text-xl font-semibold text-[var(--hl-title)]\",\n back && \"mt-1\",\n textIsSrOnly && \"sr-only\",\n )}\n >\n {title}\n </h1>\n )}\n {description && (\n <p className=\"mt-1 text-xs text-[var(--hl-foreground)]\">{description}</p>\n )}\n </div>\n </div>\n {actions && <div className=\"flex items-center gap-2\">{actions}</div>}\n </header>\n );\n}\n\nfunction PlaceholderHeader({\n title,\n imageSrc,\n}: {\n title: string;\n imageSrc: string;\n}) {\n const file = imageSrc.replace(/^\\//, \"\");\n return (\n <div\n title={`Drop ${file} in /public to brand this header`}\n className=\"flex h-28 w-28 shrink-0 flex-col items-center justify-center gap-1 rounded-[1rem] border-2 border-dashed border-[var(--hl-border)] bg-[var(--hl-card-warm)] text-[var(--hl-foreground-faint)] md:h-32 md:w-32\"\n >\n <ImageOff className=\"h-6 w-6\" />\n <span className=\"px-2 text-center text-[10px] font-semibold uppercase tracking-widest\">\n {title}\n </span>\n </div>\n );\n}\n\nfunction Breadcrumb({\n crumbs,\n srTitle,\n className,\n}: {\n crumbs: Crumb[];\n srTitle: string;\n className?: string;\n}) {\n return (\n <nav\n aria-label=\"Breadcrumb\"\n className={cn(\n \"flex flex-wrap items-center gap-1.5 text-xl font-semibold\",\n className,\n )}\n >\n <h1 className=\"sr-only\">{srTitle}</h1>\n {crumbs.map((crumb, idx) => {\n const isLast = idx === crumbs.length - 1;\n return (\n <span\n key={`${crumb.label}-${idx}`}\n className=\"flex items-center gap-1.5\"\n >\n {idx > 0 && (\n <ChevronRight\n className=\"h-4 w-4 shrink-0 text-[var(--hl-foreground-faint)]\"\n aria-hidden=\"true\"\n />\n )}\n {crumb.onClick && !isLast ? (\n <button\n type=\"button\"\n onClick={crumb.onClick}\n className=\"rounded px-1 text-[var(--hl-foreground-muted)] transition hover:text-[var(--hl-foreground)]\"\n >\n {crumb.label}\n </button>\n ) : (\n <span\n className={cn(\n isLast\n ? \"text-[var(--hl-foreground)]\"\n : \"text-[var(--hl-foreground-muted)]\",\n isLast && \"px-1\",\n )}\n aria-current={isLast ? \"page\" : undefined}\n >\n {crumb.label}\n </span>\n )}\n </span>\n );\n })}\n </nav>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Menu, X } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\nexport type SidebarProps = {\n /**\n * Logo image src — typically `/logo.png` or `/<project>-logo-256.png`.\n * Rendered at w-36 h-36 inside a centered block with px-4 pt-6 pb-5\n * + border-b-2 (doghouse-canonical spec). Override sizing/shape via\n * `logoClassName`.\n */\n logoSrc: string;\n /** Alt text for the logo + label shown in the mobile top bar. */\n logoAlt: string;\n /**\n * Override the className applied to the logo `<img>`. Default is\n * `\"h-36 w-36 rounded-[1rem] object-contain\"` (doghouse-canonical\n * square logo). Pass `\"h-auto w-full rounded-md object-contain\"` for\n * full-width banner-style logos, or any other Tailwind classes.\n * Pass the whole class string when overriding — your value replaces\n * the default (no merge).\n */\n logoClassName?: string;\n /**\n * Slots — every section is optional. Render them in this order:\n * 1. Logo block (always)\n * 2. widgets — clock / weather / homepage rotator / etc\n * 3. nav — primary navigation\n * 4. middle — long-running content (folder tree, etc) — flex-1\n * 5. lower — secondary nav (manage-tags, settings link, etc)\n * 6. (spacer)\n * 7. admin — admin section\n * 8. footer — version stamp, etc\n */\n widgets?: React.ReactNode;\n nav?: React.ReactNode;\n middle?: React.ReactNode;\n lower?: React.ReactNode;\n admin?: React.ReactNode;\n footer?: React.ReactNode;\n /**\n * Mobile-drawer behavior. Controlled — the consumer owns the open\n * state so they can wire it from anywhere (top-bar hamburger,\n * keyboard shortcut, route change, etc.).\n *\n * When omitted, the Sidebar manages its own drawer state using the\n * built-in mobile top bar (hamburger renders top-left on screens < md).\n */\n mobileOpen?: boolean;\n onMobileOpenChange?: (open: boolean) => void;\n /** Extra classes on the outer <aside>. */\n className?: string;\n /**\n * Override the className used for the `middle` slot's wrapper.\n *\n * The default — `\"mx-3 mt-3 flex-1 overflow-y-auto rounded-[0.75rem] bg-[var(--hl-card)] p-2\"` —\n * is correct for asset-den's \"single panel with a scrollable folder\n * tree inside.\" Override when the middle slot's content is itself a\n * stack of cards (e.g. doghouse's per-service status widgets) — those\n * already sit on the card token, so wrapping them in another same-color\n * panel makes them disappear.\n *\n * Pass the whole class string when overriding — your value replaces\n * the default (no merge).\n */\n middleClassName?: string;\n /**\n * When true, render a built-in mobile top bar (logo + hamburger) on\n * screens < md. Defaults to true. Disable when the consumer renders\n * its own top chrome.\n */\n showMobileTopBar?: boolean;\n};\n\n/**\n * Canonical homelab sidebar shell. Doghouse is the reference impl;\n * this is the extracted version every other project consumes.\n *\n * Dimensions are non-negotiable:\n * - md+ width: 15rem default (override per-project via `:root { --hl-sidebar-width: 18rem; }`)\n * - md+ position: fixed-flex in a `display:flex` parent\n * - mobile: off-canvas drawer at full sidebar width\n * - logo block: w-36 h-36 centered, px-4 pt-6 pb-5, border-b-2\n *\n * Surfaces are `bg-[var(--hl-sidebar)]` / `bg-[var(--hl-card)]` /\n * `border-[var(--hl-border)]` — arbitrary-value classes that read the\n * tokens directly, so they still work when the consumer skips the\n * Tailwind preset AND re-skin correctly when the consumer overrides the\n * vars. (They were raw hex until 0.8.0, which silently pinned every\n * consumer to the fleet palette.) Import globals.css for the defaults.\n */\nexport function Sidebar({\n logoSrc,\n logoAlt,\n logoClassName = \"h-36 w-36 rounded-[1rem] object-contain\",\n widgets,\n nav,\n middle,\n lower,\n admin,\n footer,\n mobileOpen: controlledOpen,\n onMobileOpenChange,\n className,\n middleClassName = \"mx-3 mt-3 flex-1 overflow-y-auto rounded-[0.75rem] bg-[var(--hl-card)] p-2\",\n showMobileTopBar = true,\n}: SidebarProps) {\n const [internalOpen, setInternalOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onMobileOpenChange?.(next);\n };\n\n // Lock body scroll while the mobile drawer is open.\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n if (!open) return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [open]);\n\n return (\n <>\n {showMobileTopBar && (\n <div className=\"fixed inset-x-0 top-0 z-30 flex items-center gap-2 border-b-2 border-[var(--hl-border)] bg-[var(--hl-sidebar)] px-3 py-2 md:hidden\">\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"rounded-[0.25rem] p-1.5 text-[var(--hl-foreground)] transition hover:bg-[var(--hl-card-hover)] active:scale-90\"\n aria-label=\"Open menu\"\n >\n <Menu className=\"h-5 w-5\" />\n </button>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={logoSrc} alt=\"\" width={24} height={24} />\n <p className=\"text-sm font-semibold text-[var(--hl-foreground)]\">\n {logoAlt}\n </p>\n </div>\n )}\n\n {open && (\n <div\n className=\"fixed inset-0 z-40 bg-black/70 md:hidden\"\n onClick={() => setOpen(false)}\n role=\"presentation\"\n />\n )}\n\n <aside\n className={cn(\n \"fixed inset-y-0 left-0 z-50 flex w-[var(--hl-sidebar-width,15rem)] shrink-0 flex-col border-r-2 border-[var(--hl-border)] bg-[var(--hl-sidebar)] transition-transform md:static md:translate-x-0 md:h-screen\",\n open ? \"translate-x-0\" : \"-translate-x-full md:translate-x-0\",\n className,\n )}\n >\n <div className=\"relative flex flex-col items-center border-b-2 border-[var(--hl-border)] px-4 pt-6 pb-5\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoSrc}\n alt={logoAlt}\n className={logoClassName}\n />\n {showMobileTopBar && (\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n className=\"absolute right-3 top-3 rounded-[0.25rem] p-1 text-[var(--hl-foreground-muted)] transition hover:bg-[var(--hl-card-hover)] hover:text-[var(--hl-foreground)] md:hidden\"\n aria-label=\"Close menu\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n\n {widgets && <div className=\"mx-3 mt-3\">{widgets}</div>}\n\n {nav && (\n <nav className=\"mx-3 mt-3 space-y-0.5 rounded-[0.75rem] bg-[var(--hl-card)] p-2\">\n {nav}\n </nav>\n )}\n\n {middle && <div className={middleClassName}>{middle}</div>}\n\n {lower && (\n <nav className=\"mx-3 mt-3 space-y-0.5 rounded-[0.75rem] bg-[var(--hl-card)] p-2\">\n {lower}\n </nav>\n )}\n\n {!middle && <div className=\"flex-1\" />}\n\n {admin && <div>{admin}</div>}\n\n {footer && (\n <div className=\"border-t-2 border-[var(--hl-border)] px-4 py-3 text-center text-xs text-[var(--hl-foreground-faint)]\">\n {footer}\n </div>\n )}\n </aside>\n </>\n );\n}\n","\"use client\";\n\nimport { cn } from \"../lib/utils\";\n\nexport type SidebarNavItemProps = {\n label: string;\n icon?: React.ReactNode;\n active?: boolean;\n onClick?: () => void;\n /**\n * When set, renders as <a href>. Otherwise renders as <button>. In\n * Next.js, prefer passing `render` so you can wrap in next/link\n * without losing classes:\n *\n * <SidebarNavItem\n * label=\"Home\"\n * href=\"/\"\n * render={(props, children) => <Link {...props}>{children}</Link>}\n * />\n *\n * Spread `{...props}` rather than destructuring individual fields\n * (`{ href, className }`) — the props object also carries `aria-current`\n * for the active item, and a narrow destructure silently drops it (and\n * any future field) even though the type still says it's there.\n */\n href?: string;\n render?: (\n props: { href: string; className: string; \"aria-current\"?: \"page\" },\n children: React.ReactNode,\n ) => React.ReactNode;\n};\n\nconst ITEM_CLASS_BASE =\n \"flex items-center gap-2 rounded-[0.5rem] px-3 py-2.5 text-base font-medium transition-all\";\n\nconst itemClass = (active: boolean) =>\n cn(\n ITEM_CLASS_BASE,\n active\n ? \"bg-[var(--hl-brand)] text-[var(--hl-brand-fg)]\"\n : \"text-[var(--hl-foreground-muted)] hover:bg-[var(--hl-hover-overlay)] hover:text-[var(--hl-foreground)]\",\n );\n\n/**\n * Single nav row for the Sidebar's `nav` / `lower` slots. Matches the\n * doghouse-canonical style: active = brand bg + brand-fg text, inactive =\n * muted text lifting to full foreground on hover, over a hover wash.\n *\n * Every colour here is a token. The active pill reads `--hl-brand` /\n * `--hl-brand-fg` (it was `bg-[#ff9900] text-white` until 0.8.0); the\n * inactive row reads `--hl-foreground-muted` / `--hl-hover-overlay` /\n * `--hl-foreground` (it was `text-white/45 hover:bg-white/5\n * hover:text-white` until 0.9.0). Both were themeability bugs of the same\n * shape — see globals.css.\n */\nexport function SidebarNavItem({\n label,\n icon,\n active = false,\n onClick,\n href,\n render,\n}: SidebarNavItemProps) {\n const className = itemClass(active);\n const ariaCurrent = active ? (\"page\" as const) : undefined;\n const inner = (\n <>\n {icon}\n <span>{label}</span>\n </>\n );\n\n if (href && render)\n return (\n <>{render({ href, className, \"aria-current\": ariaCurrent }, inner)}</>\n );\n if (href) {\n return (\n <a href={href} aria-current={ariaCurrent} className={className}>\n {inner}\n </a>\n );\n }\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-current={ariaCurrent}\n aria-pressed={active}\n className={`${className} w-full`}\n >\n {inner}\n </button>\n );\n}\n","\"use client\";\n\nimport { Loader2 } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\nexport type SpinnerProps = {\n label?: string;\n className?: string;\n};\n\n/**\n * Consistent inline loading affordance — used wherever the app would\n * otherwise say \"Loading…\" in plain text. Compact spinner + label, on the\n * same `--hl-foreground-muted` as every other secondary line.\n */\nexport function Spinner({ label = \"Loading…\", className }: SpinnerProps) {\n return (\n <p\n className={cn(\n \"flex items-center gap-2 px-3 py-2 text-xs text-[var(--hl-foreground-muted)]\",\n className,\n )}\n >\n <Loader2 className=\"h-3 w-3 animate-spin\" />\n {label}\n </p>\n );\n}\n","import { cn } from \"../lib/utils\";\n\nexport type StatTileProps = {\n /** Uppercase micro-label above the value. */\n label: string;\n /** The stat itself — keep it short; it truncates rather than wraps. */\n value: React.ReactNode;\n /** Optional caption under the value. The line is ALWAYS reserved so the\n * tile height never changes when a sub appears/disappears. */\n sub?: React.ReactNode;\n className?: string;\n};\n\n/**\n * Live-stat tile with STABLE GEOMETRY — the fleet standard for status\n * cards (born on zoo-docs' dashboard, 2026-07-13):\n *\n * - text truncates, never wraps → tile height is constant\n * - the sub line renders even when empty → no height jump when data\n * (queue counts, countdowns) comes and goes between polls\n * - value/sub render in font-mono (metrically monospaced) so ticking\n * numbers don't wiggle the width — tabular-nums alone doesn't do this,\n * since the fleet's Poppins/Orbitron subsets don't ship the tnum\n * OpenType feature (B264)\n *\n * Anything elastic in HEIGHT shoves the whole page around on every poll;\n * width elasticity belongs to the row (see StatTileRow).\n */\nexport function StatTile({ label, value, sub, className }: StatTileProps) {\n return (\n <div className={cn(\"min-w-0 rounded-lg bg-[var(--hl-card)] px-4 py-3\", className)}>\n <p className=\"truncate text-[10px] font-semibold uppercase tracking-widest text-[var(--hl-foreground)]\">\n {label}\n </p>\n <p className=\"mt-1 truncate font-mono text-xl font-semibold tabular-nums text-[var(--hl-foreground)]\">{value}</p>\n <p className=\"truncate font-mono text-[11px] tabular-nums text-[var(--hl-foreground)]\">{sub ?? \" \"}</p>\n </div>\n );\n}\n\nexport type StatTileRowProps = {\n children: React.ReactNode;\n /** Minimum tile width before the grid drops a column. Default \"9rem\". */\n minTileWidth?: string;\n className?: string;\n};\n\n/**\n * Full-width, equal-column container for StatTiles. `auto-fit` +\n * `minmax(minTileWidth, 1fr)` means the row stretches edge-to-edge with\n * the browser and sheds whole columns on narrow screens — tiles never\n * overflow off-screen and never wrap their text.\n *\n * Pair with the layout standard: content pages are FULL WIDTH (no\n * max-w-* caps) unless a page explicitly opts out.\n */\nexport function StatTileRow({ children, minTileWidth = \"9rem\", className }: StatTileRowProps) {\n return (\n <div\n className={cn(\"grid gap-3\", className)}\n style={{ gridTemplateColumns: `repeat(auto-fit, minmax(${minTileWidth}, 1fr))` }}\n >\n {children}\n </div>\n );\n}\n","/**\n * Pick black or white text for a solid color chip.\n *\n * The homelab status palette forces white text on every status color\n * EXCEPT yellow (warning: #eab308), which uses black for contrast. This\n * helper enforces that rule wherever a user-picked color drives a chip\n * background (TagChips, status badges, etc).\n *\n * The yellow rule generalizes — anything in the yellow band (high\n * luminance + warm hue) gets black ink.\n */\nexport function tagTextColor(hex: string): \"#ffffff\" | \"#000000\" {\n const normalized = hex.trim().replace(/^#/, \"\");\n if (normalized.length !== 3 && normalized.length !== 6) return \"#ffffff\";\n const expand =\n normalized.length === 3\n ? normalized\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n : normalized;\n const r = parseInt(expand.slice(0, 2), 16);\n const g = parseInt(expand.slice(2, 4), 16);\n const b = parseInt(expand.slice(4, 6), 16);\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return \"#ffffff\";\n // Perceived luminance per ITU BT.601.\n const luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n return luma > 0.65 ? \"#000000\" : \"#ffffff\";\n}\n","\"use client\";\n\nimport { tagTextColor } from \"../lib/colors\";\n\nexport type TagRef = {\n id: string | number;\n name: string;\n color: string;\n};\n\nexport type TagChipsProps = {\n tags: TagRef[];\n size?: \"xs\" | \"sm\";\n onClick?: (tag: TagRef) => void;\n};\n\n/**\n * Render a row of solid-color tag chips. Yellow auto-flips text to\n * black per the homelab status-palette contrast rule (see tagTextColor).\n *\n * Renders `<button>` when interactive (`onClick` set), `<span>` when\n * read-only — keeps screen-reader semantics honest.\n */\nexport function TagChips({ tags, size = \"xs\", onClick }: TagChipsProps) {\n if (tags.length === 0) return null;\n const sizeClasses =\n size === \"xs\" ? \"px-1.5 py-0.5 text-[10px]\" : \"px-2 py-0.5 text-xs\";\n\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.map((t) => {\n const style = {\n backgroundColor: t.color,\n color: tagTextColor(t.color),\n };\n const className = `inline-block rounded-[0.25rem] font-semibold ${sizeClasses}`;\n return onClick ? (\n <button\n key={t.id}\n type=\"button\"\n onClick={() => onClick(t)}\n className={`${className} transition active:scale-95`}\n style={style}\n >\n {t.name}\n </button>\n ) : (\n <span key={t.id} className={className} style={style}>\n {t.name}\n </span>\n );\n })}\n </div>\n );\n}\n","/**\n * Font wiring for `@jdcpuwiz/homelab-ui`.\n *\n * ## The standard: three self-hosted faces, split by JOB (BuildPlan #57)\n *\n * Wiz ruling 2026-07-24. This SUPERSEDES both the old \"Geist via next/font\"\n * mandate AND the system-ui-only ruling (#345) that briefly replaced it.\n *\n * SANS Poppins — all body / UI / headings / labels\n * DISPLAY Orbitron — BIG numbers only (stat heroes, clock, ≥ ~1.5rem)\n * MONO JetBrains Mono — small values, table figures, version stamps,\n * code (< ~1.5rem)\n *\n * The size line is the hard boundary: Orbitron is a display face and must\n * NEVER be applied to a dense small cell — if a number is small, it's\n * JetBrains Mono.\n *\n * ## Loading is PER-APP — the package can't do it for you\n *\n * A `next/font` loader cannot be re-exported from this package: Next's SWC\n * plugin scans the app's OWN source for literal `const Foo = Font({...})`\n * calls, and bundling rewrites `const`→`var` so Next refuses. So each app\n * self-hosts its faces and binds them to the CSS vars this package reads.\n * The package only owns the `--hl-font-*` wiring in `globals.css`.\n *\n * The convention (see the `starter/` folder for copy-paste layouts):\n *\n * - Next apps: `next/font/google`, each loader given the matching\n * `variable` from `FONT_APP_VARIABLES` below, Poppins weights 300–800,\n * `display: \"swap\"`. Add the three `.variable` classes to `<body>`.\n * - Vite apps: `@fontsource/poppins` etc. — the plain family name resolves,\n * no CSS var needed (the package's `--hl-font-*` already falls back to the\n * literal family name).\n *\n * `globals.css` declares `--hl-font-sans` / `--hl-font-display` /\n * `--hl-font-mono` referencing the app variables below first, then the literal\n * face name, then the platform fallback — so text is readable even mid-load\n * and an un-migrated app degrades to the system stack, never to nothing.\n *\n * Sanity check after deploy — this should name Poppins:\n * getComputedStyle(document.body).fontFamily // → \"…Poppins…\"\n */\n\n/**\n * The package's own token names — the `--hl-font-*` vars the Tailwind preset's\n * `font-sans` / `font-mono` / `font-display` classes resolve to. Consumers\n * rarely touch these directly.\n */\nexport const FONT_CSS_VARIABLES = {\n sans: \"--hl-font-sans\",\n mono: \"--hl-font-mono\",\n display: \"--hl-font-display\",\n} as const;\n\n/**\n * The app-side CSS variable each next/font loader must expose (its `variable:`\n * option). `globals.css` reads these, so an app that follows this naming gets\n * the three faces wired with zero extra config. Match these EXACTLY.\n *\n * const poppins = Poppins({ ..., variable: FONT_APP_VARIABLES.sans });\n */\nexport const FONT_APP_VARIABLES = {\n sans: \"--font-poppins\",\n display: \"--font-orbitron\",\n mono: \"--font-jetbrains-mono\",\n} as const;\n"],"mappings":"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","names":["Button","useEffect","jsx","jsx","jsxs","useEffect","jsx","jsxs","useEffect","Fragment","jsx","jsxs","useEffect","useEffect","useState","X","Fragment","jsx","jsxs","useState","useEffect","X","Fragment","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/Button.tsx","../src/lib/utils.ts","../src/components/ConfirmDialog.tsx","../src/components/Modal.tsx","../src/components/EmptyState.tsx","../src/components/PageHeader.tsx","../src/components/Sidebar.tsx","../src/components/SidebarNavItem.tsx","../src/components/Spinner.tsx","../src/components/StatTile.tsx","../src/lib/colors.ts","../src/components/TagChips.tsx","../src/lib/fonts.ts"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ntype Variant = \"primary\" | \"secondary\" | \"tertiary\" | \"danger\";\ntype Size = \"sm\" | \"md\";\n\nexport type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n variant?: Variant;\n size?: Size;\n};\n\nconst BASE =\n \"inline-flex items-center justify-center gap-1.5 rounded-[0.5rem] font-semibold transition active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--hl-brand-glow)]\";\n\n// `danger`'s BACKGROUND stays a literal — status colors are a fixed fleet\n// semantic (the preset hardcodes them too), not a per-project brand token.\n// Its label stays literal `text-white` for the same reason: that is text\n// ON a status chip, which lib/colors.ts already rules is always white,\n// NOT reading text on an app surface. Everything else here is a token.\nconst VARIANT: Record<Variant, string> = {\n primary:\n \"bg-[var(--hl-brand)] text-[var(--hl-brand-fg)] hover:bg-[var(--hl-brand-hover)]\",\n secondary:\n \"bg-[var(--hl-card)] text-[var(--hl-foreground)] hover:bg-[var(--hl-card-hover)]\",\n tertiary:\n \"text-[var(--hl-foreground-muted)] hover:bg-[var(--hl-card-hover)] hover:text-[var(--hl-foreground)]\",\n danger: \"bg-[#b91c1c] text-white hover:brightness-110\",\n};\n\nconst SIZE: Record<Size, string> = {\n sm: \"px-3 py-1 text-xs\",\n md: \"px-4 py-1.5 text-sm\",\n};\n\n/**\n * Homelab Button. Use the variant + size props; never pass raw color\n * classes through className. className is for layout extras only\n * (e.g. `w-full`).\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n { variant = \"secondary\", size = \"md\", className, ...rest },\n ref,\n ) {\n return (\n <button\n ref={ref}\n {...rest}\n className={cn(BASE, VARIANT[variant], SIZE[size], className)}\n />\n );\n },\n);\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Conditional Tailwind class joiner. Merges variants and resolves\n * conflicts (e.g. `cn(\"p-2\", \"p-4\")` → `\"p-4\"`).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { Modal } from \"./Modal\";\nimport { Button } from \"./Button\";\n\nexport type ConfirmDialogProps = {\n open: boolean;\n title: string;\n message: string;\n confirmLabel?: string;\n cancelLabel?: string;\n tone?: \"danger\" | \"primary\";\n onConfirm: () => void;\n onClose: () => void;\n};\n\n/**\n * Confirm/cancel dialog built on Modal + Button. Enter triggers confirm,\n * Esc cancels (via Modal's built-in handling).\n */\nexport function ConfirmDialog({\n open,\n title,\n message,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n tone = \"danger\",\n onConfirm,\n onClose,\n}: ConfirmDialogProps) {\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Enter\") onConfirm();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open, onConfirm]);\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={title}\n footer={\n <>\n <Button variant=\"tertiary\" size=\"md\" onClick={onClose}>\n {cancelLabel}\n </Button>\n <Button\n variant={tone === \"danger\" ? \"danger\" : \"primary\"}\n size=\"md\"\n onClick={onConfirm}\n >\n {confirmLabel}\n </Button>\n </>\n }\n >\n <p className=\"whitespace-pre-line text-sm text-[var(--hl-foreground)]\">{message}</p>\n </Modal>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\ntype Density = \"form\" | \"media\";\n\nexport type ModalProps = {\n open: boolean;\n onClose: () => void;\n title?: string;\n /** Show the close X in the top right. Default: true when title is set. */\n showClose?: boolean;\n /**\n * Overlay weight:\n * `form` — dialogs / pickers / confirms (`bg-black/70`)\n * `media` — lightbox / asset preview (`bg-black/90`)\n * Defaults to `form`.\n */\n density?: Density;\n /** Max width — default `max-w-md`. */\n maxWidth?: string;\n /** Footer slot — render buttons here. Always right-aligned. */\n footer?: React.ReactNode;\n children: React.ReactNode;\n};\n\n/**\n * Single modal frame. Every dialog routes through this:\n * - overlay + click-outside dismiss\n * - Esc-to-close\n * - role=dialog + aria-modal + aria-labelledby wiring\n * - DESIGN.md header treatment (uppercase tracking-widest)\n * - shared right-aligned footer row\n */\nexport function Modal({\n open,\n onClose,\n title,\n showClose,\n density = \"form\",\n maxWidth = \"max-w-md\",\n footer,\n children,\n}: ModalProps) {\n const titleIdRef = useRef(\n `hl-modal-${Math.random().toString(36).slice(2, 8)}`,\n );\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open, onClose]);\n\n if (!open) return null;\n\n const showCloseBtn = showClose ?? Boolean(title);\n const overlayClass = density === \"media\" ? \"bg-black/90\" : \"bg-black/70\";\n\n return (\n <div\n className={cn(\n \"fixed inset-0 z-50 flex items-center justify-center p-4\",\n overlayClass,\n )}\n onClick={onClose}\n role=\"presentation\"\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleIdRef.current : undefined}\n className={cn(\n \"w-full rounded-[1rem] border-2 border-[var(--hl-border)] bg-[var(--hl-sidebar)] p-5\",\n maxWidth,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || showCloseBtn) && (\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n {title && (\n <h2\n id={titleIdRef.current}\n className=\"text-sm font-semibold uppercase tracking-widest text-[var(--hl-foreground)]\"\n >\n {title}\n </h2>\n )}\n {showCloseBtn && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"ml-auto rounded-[0.25rem] p-1 text-[var(--hl-foreground-muted)] transition hover:bg-[var(--hl-card-hover)] hover:text-[var(--hl-foreground)] active:scale-90\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n )}\n {children}\n {footer && (\n <div className=\"mt-5 flex flex-wrap items-center justify-end gap-2\">\n {footer}\n </div>\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { cn } from \"../lib/utils\";\n\nexport type EmptyStateProps = {\n icon?: React.ReactNode;\n title?: string;\n message: string;\n action?: React.ReactNode;\n /**\n * `panel` — large empty state with warm card background (page bodies).\n * `inline` — compact text-only line for sidebar tree, popovers, etc.\n */\n variant?: \"panel\" | \"inline\";\n className?: string;\n};\n\nexport function EmptyState({\n icon,\n title,\n message,\n action,\n variant = \"panel\",\n className,\n}: EmptyStateProps) {\n if (variant === \"inline\") {\n return (\n <p className={cn(\"px-3 py-2 text-xs text-[var(--hl-foreground)]\", className)}>\n {message}\n </p>\n );\n }\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center gap-3 rounded-[1rem] bg-[var(--hl-card-warm)] p-12 text-center\",\n className,\n )}\n >\n {icon && (\n <div className=\"text-[var(--hl-foreground-faint)]\">{icon}</div>\n )}\n {title && (\n <h3 className=\"text-sm font-semibold text-[var(--hl-foreground)]\">\n {title}\n </h3>\n )}\n <p className=\"text-sm text-[var(--hl-foreground)]\">{message}</p>\n {action}\n </div>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { ArrowLeft, ChevronRight, ImageOff } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\nexport type Crumb = {\n label: string;\n onClick?: () => void;\n};\n\nexport type PageHeaderProps = {\n title: string;\n description?: string;\n /** Back link rendered above the title. */\n back?: { href: string; label: string; render?: (href: string, children: React.ReactNode) => React.ReactNode };\n /** Right-aligned actions slot. */\n actions?: React.ReactNode;\n /**\n * Header image URL (e.g. `/logos.png`). When the URL resolves, the\n * image is the visual title. When it 404s, a styled placeholder at\n * the same dimensions takes its place — the layout stays stable so\n * creating a new themed page \"just works\" the moment you drop a\n * matching PNG in /public/.\n *\n * When `imageSrc` is undefined, no image block renders and the\n * text title is the visual title.\n */\n imageSrc?: string;\n imageAlt?: string;\n /**\n * Breadcrumb trail. When provided, replaces the text title. Last\n * crumb is current (full foreground + bold); earlier crumbs are muted\n * and clickable.\n */\n breadcrumb?: Crumb[];\n className?: string;\n};\n\n/**\n * Page-header block. The image block reserves space whenever `imageSrc`\n * is set — drop the PNG in /public/ and the layout just fills in.\n *\n * Text title rules:\n * - imageSrc set + image present → text becomes sr-only\n * - imageSrc set + image 404 → placeholder block + text becomes sr-only\n * - imageSrc unset → text title is visible\n * - breadcrumb provided → breadcrumb replaces text title regardless of image\n *\n * The back link uses a plain <a> by default. In Next.js, pass\n * `back.render` to use next/link:\n *\n * import Link from \"next/link\";\n * <PageHeader back={{ href: \"/foo\", label: \"Back\", render: (href, c) => <Link href={href}>{c}</Link> }} />\n */\nexport function PageHeader({\n title,\n description,\n back,\n actions,\n imageSrc,\n imageAlt,\n breadcrumb,\n className,\n}: PageHeaderProps) {\n const [imageErrored, setImageErrored] = useState(false);\n useEffect(() => {\n setImageErrored(false);\n }, [imageSrc]);\n\n const hasImageSlot = !!imageSrc;\n const imageRenders = hasImageSlot && !imageErrored;\n const hasBreadcrumb = Array.isArray(breadcrumb) && breadcrumb.length > 0;\n const textIsSrOnly = hasImageSlot;\n\n const backInner = back ? (\n <>\n <ArrowLeft className=\"h-3 w-3\" />\n {back.label}\n </>\n ) : null;\n\n return (\n <header\n className={cn(\n \"flex flex-wrap items-end justify-between gap-3\",\n className,\n )}\n >\n <div className=\"flex min-w-0 items-end gap-4\">\n {hasImageSlot &&\n (imageRenders ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={imageSrc}\n alt={imageAlt ?? title}\n onError={() => setImageErrored(true)}\n className=\"h-28 w-28 shrink-0 rounded-[1rem] object-contain md:h-32 md:w-32\"\n />\n ) : (\n <PlaceholderHeader title={title} imageSrc={imageSrc!} />\n ))}\n <div className=\"min-w-0\">\n {back &&\n (back.render ? (\n back.render(\n back.href,\n <span className=\"inline-flex items-center gap-1 text-xs text-[var(--hl-foreground-muted)] transition hover:text-[var(--hl-foreground)]\">\n {backInner}\n </span>,\n )\n ) : (\n <a\n href={back.href}\n className=\"inline-flex items-center gap-1 text-xs text-[var(--hl-foreground-muted)] transition hover:text-[var(--hl-foreground)]\"\n >\n {backInner}\n </a>\n ))}\n {hasBreadcrumb ? (\n <Breadcrumb\n crumbs={breadcrumb!}\n srTitle={title}\n className={back ? \"mt-1\" : \"\"}\n />\n ) : (\n <h1\n className={cn(\n \"text-xl font-semibold text-[var(--hl-title)]\",\n back && \"mt-1\",\n textIsSrOnly && \"sr-only\",\n )}\n >\n {title}\n </h1>\n )}\n {description && (\n <p className=\"mt-1 text-xs text-[var(--hl-foreground)]\">{description}</p>\n )}\n </div>\n </div>\n {actions && <div className=\"flex items-center gap-2\">{actions}</div>}\n </header>\n );\n}\n\nfunction PlaceholderHeader({\n title,\n imageSrc,\n}: {\n title: string;\n imageSrc: string;\n}) {\n const file = imageSrc.replace(/^\\//, \"\");\n return (\n <div\n title={`Drop ${file} in /public to brand this header`}\n className=\"flex h-28 w-28 shrink-0 flex-col items-center justify-center gap-1 rounded-[1rem] border-2 border-dashed border-[var(--hl-border)] bg-[var(--hl-card-warm)] text-[var(--hl-foreground-faint)] md:h-32 md:w-32\"\n >\n <ImageOff className=\"h-6 w-6\" />\n <span className=\"px-2 text-center text-[10px] font-semibold uppercase tracking-widest\">\n {title}\n </span>\n </div>\n );\n}\n\nfunction Breadcrumb({\n crumbs,\n srTitle,\n className,\n}: {\n crumbs: Crumb[];\n srTitle: string;\n className?: string;\n}) {\n return (\n <nav\n aria-label=\"Breadcrumb\"\n className={cn(\n \"flex flex-wrap items-center gap-1.5 text-xl font-semibold\",\n className,\n )}\n >\n <h1 className=\"sr-only\">{srTitle}</h1>\n {crumbs.map((crumb, idx) => {\n const isLast = idx === crumbs.length - 1;\n return (\n <span\n key={`${crumb.label}-${idx}`}\n className=\"flex items-center gap-1.5\"\n >\n {idx > 0 && (\n <ChevronRight\n className=\"h-4 w-4 shrink-0 text-[var(--hl-foreground-faint)]\"\n aria-hidden=\"true\"\n />\n )}\n {crumb.onClick && !isLast ? (\n <button\n type=\"button\"\n onClick={crumb.onClick}\n className=\"rounded px-1 text-[var(--hl-foreground-muted)] transition hover:text-[var(--hl-foreground)]\"\n >\n {crumb.label}\n </button>\n ) : (\n <span\n className={cn(\n isLast\n ? \"text-[var(--hl-foreground)]\"\n : \"text-[var(--hl-foreground-muted)]\",\n isLast && \"px-1\",\n )}\n aria-current={isLast ? \"page\" : undefined}\n >\n {crumb.label}\n </span>\n )}\n </span>\n );\n })}\n </nav>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Menu, X } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\nexport type SidebarProps = {\n /**\n * Logo image src — typically `/logo.png` or `/<project>-logo-256.png`.\n * Rendered at w-36 h-36 inside a centered block with px-4 pt-6 pb-5\n * + border-b-2 (doghouse-canonical spec). Override sizing/shape via\n * `logoClassName`.\n */\n logoSrc: string;\n /** Alt text for the logo + label shown in the mobile top bar. */\n logoAlt: string;\n /**\n * Override the className applied to the logo `<img>`. Default is\n * `\"h-36 w-36 rounded-[1rem] object-contain\"` (doghouse-canonical\n * square logo). Pass `\"h-auto w-full rounded-md object-contain\"` for\n * full-width banner-style logos, or any other Tailwind classes.\n * Pass the whole class string when overriding — your value replaces\n * the default (no merge).\n */\n logoClassName?: string;\n /**\n * Slots — every section is optional. Render them in this order:\n * 1. Logo block (always)\n * 2. widgets — clock / weather / homepage rotator / etc\n * 3. nav — primary navigation\n * 4. middle — long-running content (folder tree, etc) — flex-1\n * 5. lower — secondary nav (manage-tags, settings link, etc)\n * 6. (spacer)\n * 7. admin — admin section\n * 8. footer — version stamp, etc\n */\n widgets?: React.ReactNode;\n nav?: React.ReactNode;\n middle?: React.ReactNode;\n lower?: React.ReactNode;\n admin?: React.ReactNode;\n footer?: React.ReactNode;\n /**\n * Mobile-drawer behavior. Controlled — the consumer owns the open\n * state so they can wire it from anywhere (top-bar hamburger,\n * keyboard shortcut, route change, etc.).\n *\n * When omitted, the Sidebar manages its own drawer state using the\n * built-in mobile top bar (hamburger renders top-left on screens < md).\n */\n mobileOpen?: boolean;\n onMobileOpenChange?: (open: boolean) => void;\n /** Extra classes on the outer <aside>. */\n className?: string;\n /**\n * Override the className used for the `middle` slot's wrapper.\n *\n * The default — `\"mx-3 mt-3 flex-1 overflow-y-auto rounded-[0.75rem] bg-[var(--hl-card)] p-2\"` —\n * is correct for asset-den's \"single panel with a scrollable folder\n * tree inside.\" Override when the middle slot's content is itself a\n * stack of cards (e.g. doghouse's per-service status widgets) — those\n * already sit on the card token, so wrapping them in another same-color\n * panel makes them disappear.\n *\n * Pass the whole class string when overriding — your value replaces\n * the default (no merge).\n */\n middleClassName?: string;\n /**\n * When true, render a built-in mobile top bar (logo + hamburger) on\n * screens < md. Defaults to true. Disable when the consumer renders\n * its own top chrome.\n */\n showMobileTopBar?: boolean;\n};\n\n/**\n * Canonical homelab sidebar shell. Doghouse is the reference impl;\n * this is the extracted version every other project consumes.\n *\n * Dimensions are non-negotiable:\n * - md+ width: 15rem default (override per-project via `:root { --hl-sidebar-width: 18rem; }`)\n * - md+ position: fixed-flex in a `display:flex` parent\n * - mobile: off-canvas drawer at full sidebar width\n * - logo block: w-36 h-36 centered, px-4 pt-6 pb-5, border-b-2\n *\n * Surfaces are `bg-[var(--hl-sidebar)]` / `bg-[var(--hl-card)]` /\n * `border-[var(--hl-border)]` — arbitrary-value classes that read the\n * tokens directly, so they still work when the consumer skips the\n * Tailwind preset AND re-skin correctly when the consumer overrides the\n * vars. (They were raw hex until 0.8.0, which silently pinned every\n * consumer to the fleet palette.) Import globals.css for the defaults.\n */\nexport function Sidebar({\n logoSrc,\n logoAlt,\n logoClassName = \"h-36 w-36 rounded-[1rem] object-contain\",\n widgets,\n nav,\n middle,\n lower,\n admin,\n footer,\n mobileOpen: controlledOpen,\n onMobileOpenChange,\n className,\n middleClassName = \"mx-3 mt-3 flex-1 overflow-y-auto rounded-[0.75rem] bg-[var(--hl-card)] p-2\",\n showMobileTopBar = true,\n}: SidebarProps) {\n const [internalOpen, setInternalOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onMobileOpenChange?.(next);\n };\n\n // Lock body scroll while the mobile drawer is open.\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n if (!open) return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [open]);\n\n return (\n <>\n {showMobileTopBar && (\n <div className=\"fixed inset-x-0 top-0 z-30 flex items-center gap-2 border-b-2 border-[var(--hl-border)] bg-[var(--hl-sidebar)] px-3 py-2 md:hidden\">\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"rounded-[0.25rem] p-1.5 text-[var(--hl-foreground)] transition hover:bg-[var(--hl-card-hover)] active:scale-90\"\n aria-label=\"Open menu\"\n >\n <Menu className=\"h-5 w-5\" />\n </button>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={logoSrc} alt=\"\" width={24} height={24} />\n <p className=\"text-sm font-semibold text-[var(--hl-foreground)]\">\n {logoAlt}\n </p>\n </div>\n )}\n\n {open && (\n <div\n className=\"fixed inset-0 z-40 bg-black/70 md:hidden\"\n onClick={() => setOpen(false)}\n role=\"presentation\"\n />\n )}\n\n <aside\n className={cn(\n \"fixed inset-y-0 left-0 z-50 flex w-[var(--hl-sidebar-width,15rem)] shrink-0 flex-col border-r-2 border-[var(--hl-border)] bg-[var(--hl-sidebar)] transition-transform md:static md:translate-x-0 md:h-screen\",\n open ? \"translate-x-0\" : \"-translate-x-full md:translate-x-0\",\n className,\n )}\n >\n <div className=\"relative flex flex-col items-center border-b-2 border-[var(--hl-border)] px-4 pt-6 pb-5\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoSrc}\n alt={logoAlt}\n className={logoClassName}\n />\n {showMobileTopBar && (\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n className=\"absolute right-3 top-3 rounded-[0.25rem] p-1 text-[var(--hl-foreground-muted)] transition hover:bg-[var(--hl-card-hover)] hover:text-[var(--hl-foreground)] md:hidden\"\n aria-label=\"Close menu\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n\n {widgets && <div className=\"mx-3 mt-3\">{widgets}</div>}\n\n {nav && (\n <nav className=\"mx-3 mt-3 space-y-0.5 rounded-[0.75rem] bg-[var(--hl-card)] p-2\">\n {nav}\n </nav>\n )}\n\n {middle && <div className={middleClassName}>{middle}</div>}\n\n {lower && (\n <nav className=\"mx-3 mt-3 space-y-0.5 rounded-[0.75rem] bg-[var(--hl-card)] p-2\">\n {lower}\n </nav>\n )}\n\n {!middle && <div className=\"flex-1\" />}\n\n {admin && <div>{admin}</div>}\n\n {footer && (\n <div className=\"border-t-2 border-[var(--hl-border)] px-4 py-3 text-center text-xs text-[var(--hl-foreground-faint)]\">\n {footer}\n </div>\n )}\n </aside>\n </>\n );\n}\n","\"use client\";\n\nimport { cn } from \"../lib/utils\";\n\nexport type SidebarNavItemProps = {\n label: string;\n icon?: React.ReactNode;\n active?: boolean;\n onClick?: () => void;\n /**\n * When set, renders as <a href>. Otherwise renders as <button>. In\n * Next.js, prefer passing `render` so you can wrap in next/link\n * without losing classes:\n *\n * <SidebarNavItem\n * label=\"Home\"\n * href=\"/\"\n * render={(props, children) => <Link {...props}>{children}</Link>}\n * />\n *\n * Spread `{...props}` rather than destructuring individual fields\n * (`{ href, className }`) — the props object also carries `aria-current`\n * for the active item, and a narrow destructure silently drops it (and\n * any future field) even though the type still says it's there.\n */\n href?: string;\n render?: (\n props: { href: string; className: string; \"aria-current\"?: \"page\" },\n children: React.ReactNode,\n ) => React.ReactNode;\n};\n\nconst ITEM_CLASS_BASE =\n \"flex items-center gap-2 rounded-[0.5rem] px-3 py-2.5 text-base font-medium transition-all\";\n\nconst itemClass = (active: boolean) =>\n cn(\n ITEM_CLASS_BASE,\n active\n ? \"bg-[var(--hl-brand)] text-[var(--hl-brand-fg)]\"\n : \"text-[var(--hl-foreground-muted)] hover:bg-[var(--hl-hover-overlay)] hover:text-[var(--hl-foreground)]\",\n );\n\n/**\n * Single nav row for the Sidebar's `nav` / `lower` slots. Matches the\n * doghouse-canonical style: active = brand bg + brand-fg text, inactive =\n * muted text lifting to full foreground on hover, over a hover wash.\n *\n * Every colour here is a token. The active pill reads `--hl-brand` /\n * `--hl-brand-fg` (it was `bg-[#ff9900] text-white` until 0.8.0); the\n * inactive row reads `--hl-foreground-muted` / `--hl-hover-overlay` /\n * `--hl-foreground` (it was `text-white/45 hover:bg-white/5\n * hover:text-white` until 0.9.0). Both were themeability bugs of the same\n * shape — see globals.css.\n */\nexport function SidebarNavItem({\n label,\n icon,\n active = false,\n onClick,\n href,\n render,\n}: SidebarNavItemProps) {\n const className = itemClass(active);\n const ariaCurrent = active ? (\"page\" as const) : undefined;\n const inner = (\n <>\n {icon}\n <span>{label}</span>\n </>\n );\n\n if (href && render)\n return (\n <>{render({ href, className, \"aria-current\": ariaCurrent }, inner)}</>\n );\n if (href) {\n return (\n <a href={href} aria-current={ariaCurrent} className={className}>\n {inner}\n </a>\n );\n }\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-current={ariaCurrent}\n aria-pressed={active}\n className={`${className} w-full`}\n >\n {inner}\n </button>\n );\n}\n","\"use client\";\n\nimport { Loader2 } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\nexport type SpinnerProps = {\n label?: string;\n className?: string;\n};\n\n/**\n * Consistent inline loading affordance — used wherever the app would\n * otherwise say \"Loading…\" in plain text. Compact spinner + label, on the\n * same `--hl-foreground-muted` as every other secondary line.\n */\nexport function Spinner({ label = \"Loading…\", className }: SpinnerProps) {\n return (\n <p\n className={cn(\n \"flex items-center gap-2 px-3 py-2 text-xs text-[var(--hl-foreground-muted)]\",\n className,\n )}\n >\n <Loader2 className=\"h-3 w-3 animate-spin\" />\n {label}\n </p>\n );\n}\n","import { cn } from \"../lib/utils\";\n\nexport type StatTileProps = {\n /** Uppercase micro-label above the value. */\n label: string;\n /** The stat itself — keep it short; it truncates rather than wraps. */\n value: React.ReactNode;\n /** Optional caption under the value. The line is ALWAYS reserved so the\n * tile height never changes when a sub appears/disappears. */\n sub?: React.ReactNode;\n className?: string;\n};\n\n/**\n * Live-stat tile with STABLE GEOMETRY — the fleet standard for status\n * cards (born on zoo-docs' dashboard, 2026-07-13):\n *\n * - text truncates, never wraps → tile height is constant\n * - the sub line renders even when empty → no height jump when data\n * (queue counts, countdowns) comes and goes between polls\n * - value/sub render in font-mono (metrically monospaced) so ticking\n * numbers don't wiggle the width — tabular-nums alone doesn't do this,\n * since the fleet's Poppins/Orbitron subsets don't ship the tnum\n * OpenType feature (B264)\n *\n * Anything elastic in HEIGHT shoves the whole page around on every poll;\n * width elasticity belongs to the row (see StatTileRow).\n */\nexport function StatTile({ label, value, sub, className }: StatTileProps) {\n return (\n <div className={cn(\"min-w-0 rounded-lg bg-[var(--hl-card)] px-4 py-3\", className)}>\n <p className=\"truncate text-[10px] font-semibold uppercase tracking-widest text-[var(--hl-foreground)]\">\n {label}\n </p>\n <p className=\"mt-1 truncate font-mono text-xl font-semibold tabular-nums text-[var(--hl-foreground)]\">{value}</p>\n <p className=\"truncate font-mono text-[11px] tabular-nums text-[var(--hl-foreground)]\">{sub ?? \" \"}</p>\n </div>\n );\n}\n\nexport type StatTileRowProps = {\n children: React.ReactNode;\n /** Minimum tile width before the grid drops a column. Default \"9rem\". */\n minTileWidth?: string;\n className?: string;\n};\n\n/**\n * Full-width, equal-column container for StatTiles. `auto-fit` +\n * `minmax(minTileWidth, 1fr)` means the row stretches edge-to-edge with\n * the browser and sheds whole columns on narrow screens — tiles never\n * overflow off-screen and never wrap their text.\n *\n * Pair with the layout standard: content pages are FULL WIDTH (no\n * max-w-* caps) unless a page explicitly opts out.\n */\nexport function StatTileRow({ children, minTileWidth = \"9rem\", className }: StatTileRowProps) {\n return (\n <div\n className={cn(\"grid gap-3\", className)}\n style={{ gridTemplateColumns: `repeat(auto-fit, minmax(${minTileWidth}, 1fr))` }}\n >\n {children}\n </div>\n );\n}\n","/**\n * Pick black or white text for a solid color chip.\n *\n * The homelab status palette forces white text on every status color\n * EXCEPT yellow (warning: #eab308), which uses black for contrast. This\n * helper enforces that rule wherever a user-picked color drives a chip\n * background (TagChips, status badges, etc).\n *\n * The yellow rule generalizes — anything in the yellow band (high\n * luminance + warm hue) gets black ink.\n */\nexport function tagTextColor(hex: string): \"#ffffff\" | \"#000000\" {\n const normalized = hex.trim().replace(/^#/, \"\");\n if (normalized.length !== 3 && normalized.length !== 6) return \"#ffffff\";\n const expand =\n normalized.length === 3\n ? normalized\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n : normalized;\n const r = parseInt(expand.slice(0, 2), 16);\n const g = parseInt(expand.slice(2, 4), 16);\n const b = parseInt(expand.slice(4, 6), 16);\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return \"#ffffff\";\n // Perceived luminance per ITU BT.601.\n const luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n return luma > 0.65 ? \"#000000\" : \"#ffffff\";\n}\n","\"use client\";\n\nimport { tagTextColor } from \"../lib/colors\";\n\nexport type TagRef = {\n id: string | number;\n name: string;\n color: string;\n};\n\nexport type TagChipsProps = {\n tags: TagRef[];\n size?: \"xs\" | \"sm\";\n onClick?: (tag: TagRef) => void;\n};\n\n/**\n * Render a row of solid-color tag chips. Yellow auto-flips text to\n * black per the homelab status-palette contrast rule (see tagTextColor).\n *\n * Renders `<button>` when interactive (`onClick` set), `<span>` when\n * read-only — keeps screen-reader semantics honest.\n */\nexport function TagChips({ tags, size = \"xs\", onClick }: TagChipsProps) {\n if (tags.length === 0) return null;\n const sizeClasses =\n size === \"xs\" ? \"px-1.5 py-0.5 text-[10px]\" : \"px-2 py-0.5 text-xs\";\n\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.map((t) => {\n const style = {\n backgroundColor: t.color,\n color: tagTextColor(t.color),\n };\n const className = `inline-block rounded-[0.25rem] font-semibold ${sizeClasses}`;\n return onClick ? (\n <button\n key={t.id}\n type=\"button\"\n onClick={() => onClick(t)}\n className={`${className} transition active:scale-95`}\n style={style}\n >\n {t.name}\n </button>\n ) : (\n <span key={t.id} className={className} style={style}>\n {t.name}\n </span>\n );\n })}\n </div>\n );\n}\n","/**\n * Font wiring for `@jdcpuwiz/homelab-ui`.\n *\n * ## The standard: three self-hosted faces, split by JOB (BuildPlan #57)\n *\n * Wiz ruling 2026-07-24. This SUPERSEDES both the old \"Geist via next/font\"\n * mandate AND the system-ui-only ruling (#345) that briefly replaced it.\n *\n * SANS Poppins — all body / UI / headings / labels\n * DISPLAY Orbitron — BIG numbers only (stat heroes, clock, ≥ ~1.5rem)\n * MONO JetBrains Mono — small values, table figures, version stamps,\n * code (< ~1.5rem)\n *\n * The size line is the hard boundary: Orbitron is a display face and must\n * NEVER be applied to a dense small cell — if a number is small, it's\n * JetBrains Mono.\n *\n * ## The package NOW OWNS THE FACES (C525) — one import, no per-app config\n *\n * import \"@jdcpuwiz/homelab-ui/fonts.css\"\n *\n * That registers all three faces from woff2 shipped in this package, so\n * `next build` never reaches fonts.gstatic.com.\n *\n * This block previously said loading had to be per-app. That was true only of\n * `next/font`: its SWC plugin scans the app's OWN source for literal\n * `const Foo = Font({...})` calls and bundling rewrites `const`→`var`, so a\n * re-exported LOADER genuinely cannot work. The constraint never applied to\n * plain `@font-face`, which is what fonts.css uses.\n *\n * - Next apps: import `fonts.css`. Nothing else — no loaders, no\n * `.variable` classes, no `--font-*` variables.\n * - Vite apps: import `fonts.css` too, or `@fontsource/poppins` etc.\n *\n * ⚠ `FONT_APP_VARIABLES` below is LEGACY. Apps still running `next/font` set\n * those vars and `globals.css` still reads them first, so both mechanisms\n * work side by side during the rollout. Do NOT reference `--font-poppins`\n * and friends in new code: once an app moves to fonts.css they are unset, and\n * `var(--x)` with no fallback ARGUMENT is invalid at computed-value time —\n * the whole declaration is discarded and the element inherits its parent's\n * face rather than reaching your authored fallback. Use `--hl-font-sans` /\n * `--hl-font-display` / `--hl-font-mono`.\n *\n * ## `next/font/google` is a BUILD-TIME NETWORK DEPENDENCY (2026-08-15)\n *\n * The Next path above makes `next build` reach fonts.gstatic.com, which means\n * every consuming app's DEPLOY depends on it. zoovault's did, and lost: its\n * image floated `next` to a release whose next/font/google requests began\n * 404ing, and the deploy died on a commit that built clean locally. The\n * root-cause fix was pinning `next` from a committed lockfile inside the image\n * (`npm ci`, never a bare `npm install`) — do that first, everywhere. Beyond\n * that, self-hosted faces remove the network from the build entirely;\n * zoovault now runs `next/font/local` over woff2 files sourced from\n * @fontsource and committed in-repo.\n *\n * Note the fallback chain in `globals.css` — `var(--font-poppins, \"Poppins\")`.\n * The package asks for the CSS var and then for the LITERAL FAMILY NAME, so\n * anything that registers a \"Poppins\" @font-face satisfies it. next/font is a\n * convention here, not a requirement, and the Vite path above is proof.\n *\n * `globals.css` declares `--hl-font-sans` / `--hl-font-display` /\n * `--hl-font-mono` referencing the app variables below first, then the literal\n * face name, then the platform fallback — so text is readable even mid-load\n * and an un-migrated app degrades to the system stack, never to nothing.\n *\n * Sanity check after deploy — this should name Poppins:\n * getComputedStyle(document.body).fontFamily // → \"…Poppins…\"\n */\n\n/**\n * The package's own token names — the `--hl-font-*` vars the Tailwind preset's\n * `font-sans` / `font-mono` / `font-display` classes resolve to. Consumers\n * rarely touch these directly.\n */\nexport const FONT_CSS_VARIABLES = {\n sans: \"--hl-font-sans\",\n mono: \"--hl-font-mono\",\n display: \"--hl-font-display\",\n} as const;\n\n/**\n * The app-side CSS variable each next/font loader must expose (its `variable:`\n * option). `globals.css` reads these, so an app that follows this naming gets\n * the three faces wired with zero extra config. Match these EXACTLY.\n *\n * const poppins = Poppins({ ..., variable: FONT_APP_VARIABLES.sans });\n */\nexport const FONT_APP_VARIABLES = {\n sans: \"--font-poppins\",\n display: \"--font-orbitron\",\n mono: \"--font-jetbrains-mono\",\n} as const;\n"],"mappings":"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","names":["Button","useEffect","jsx","jsx","jsxs","useEffect","jsx","jsxs","useEffect","Fragment","jsx","jsxs","useEffect","useEffect","useState","X","Fragment","jsx","jsxs","useState","useEffect","X","Fragment","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2018 The Orbitron Project Authors (https://github.com/theleagueof/orbitron)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ThinItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraLight.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraLightItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Light.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-LightItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Regular.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Italic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Medium.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-MediumItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-SemiBold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-SemiBoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Bold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-BoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraBold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraBoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Black.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-BlackItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|