The icon's artwork is now the source of truth for the palette: - frost-* is a navy-tinted neutral scale and accent-* is the icon's snowflake blue; light page #f4f7fd / sidebar #e9eff9 / border #dce4f1, dark page #0e1424 / sidebar #0a0f1c / surfaces #171f33. - Surfaces are frosted glass: .card, .glass-panel, .list-group and .sidebar-surface are translucent with backdrop-blur/saturate, and the page wrapper no longer paints an opaque --page, so the fixed navy/blue backdrop reads through the sidebar, cards, chrome and sheets. - The primary CTA is the icon's blue (#1d61ab in light, #59a8f7 in dark with ink text). - The gate stage, fields and glows are navy/blue; viewport theme-color and the manifest background/theme colour are #0e1424. - The old monochrome snowflake tile is gone from the brand: sidebar, header, key gate and empty state now show the real app icon (the install sheet already did). Decorative snowflakes stay. - --surface-strong is finally defined (it was referenced but never declared, so those inputs had no fill). - Icon URLs carry ?v=2 so browser favicon caches refetch. Updated the contrast audit (21 pairs) and the deck-chrome token assertions to the new theme. Verified: contrast 21/21, deck chrome 15/15, key gate 49/49, install sheet 64/64, final ux 12/12, retry 14/14, zoom 6/6, security 8/8, pwa 16/16, deck fixes 15/15.
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { GearIcon, MenuIcon } from "@/components/icons";
|
|
import { SettingsDialog } from "@/components/SettingsDialog";
|
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
|
import { sidebarOpen } from "@/lib/nav";
|
|
import { OPEN_SETTINGS_EVENT } from "@/lib/ui-events";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* Minimal, sticky frosted top bar: brand on the left, quiet actions on the
|
|
* right. The phone layout keeps the brand compact and relies on the tab bar
|
|
* for navigation.
|
|
*/
|
|
export function AppHeader({ className, compact = false }: { className?: string; compact?: boolean }) {
|
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const onOpen = () => setSettingsOpen(true);
|
|
window.addEventListener(OPEN_SETTINGS_EVENT, onOpen);
|
|
return () => window.removeEventListener(OPEN_SETTINGS_EVENT, onOpen);
|
|
}, []);
|
|
|
|
// Restore the desktop sidebar width preference once, on the client.
|
|
useEffect(() => {
|
|
void import("@/components/Sidebar").then((module) => module.sidebarCollapse.hydrate());
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<header
|
|
className={cn(
|
|
"glass sticky top-0 z-40 -mx-4 mb-6 px-4 lg:hidden sm:-mx-6 sm:px-6",
|
|
className,
|
|
)}
|
|
>
|
|
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={() => sidebarOpen.toggle()}
|
|
aria-label="Open menu"
|
|
className="mr-1 grid size-9 shrink-0 place-items-center rounded-full text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]"
|
|
>
|
|
<MenuIcon className="size-5" />
|
|
</button>
|
|
<Link href="/" className="group flex items-center gap-2.5 rounded-xl px-1 py-1" aria-label="Freezmo home">
|
|
{/* eslint-disable-next-line @next/next/no-img-element -- static app icon, no optimisation needed */}
|
|
<img
|
|
src="/icons/icon-192.png"
|
|
alt=""
|
|
aria-hidden
|
|
className="size-8 rounded-[0.6rem] transition-transform group-hover:scale-[1.04]"
|
|
/>
|
|
<span className="flex items-baseline gap-2">
|
|
<span className="text-[15px] font-semibold tracking-[-0.01em] text-[var(--text)]">
|
|
Freezmo
|
|
</span>
|
|
{!compact ? (
|
|
<span className="hidden text-[13px] text-[var(--text-subtle)] sm:inline">
|
|
study feed
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
</Link>
|
|
|
|
<div className="flex items-center gap-1">
|
|
<button
|
|
type="button"
|
|
onClick={() => setSettingsOpen(true)}
|
|
aria-label="Open settings and Ollama status"
|
|
title="Settings"
|
|
className="inline-flex size-9 items-center justify-center rounded-full text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]"
|
|
>
|
|
<GearIcon className="size-[18px]" />
|
|
</button>
|
|
<ThemeToggle />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<SettingsDialog open={settingsOpen} onClose={() => setSettingsOpen(false)} />
|
|
</>
|
|
);
|
|
}
|