Skip to content

Public site chrome

Every public route renders inside the NanaWall site chrome — the shared header and footer from @nanawallweb/site-chrome — so NanaSelect reads as a focused tool within the NanaWall brand rather than a detached app. Admin (/admin/*) is deliberately excluded and keeps its own dense shell. Added in #308; the same SiteShell/SiteHeader/SiteFooter approach the sibling nanaawards app uses.

What renders where

The root layout (src/routes/+layout.svelte) branches on the path:

isAdmin = page.url.pathname.startsWith('/admin')
  • Public (isAdmin false): a keyboard skip-link, then SiteShell wrapping SiteHeader (top), the page (#main-content), and SiteFooter (bottom).
  • Admin (isAdmin true): the page renders bare — src/routes/admin/+layout.svelte supplies the sidebar shell. No public header/footer, no public skip-link.

The --nw-* token bridge + fonts

@nanawallweb/site-chrome styles itself through a flat --nw-* custom-property family (--nw-color-primary, --nw-font-sans, --nw-spacing-md, …). That is a different namespace from @nanawallweb/design-tokens' 3-tier --nw-core-* / --nw-semantic-* tree (which layout.css imports to source the --ns-* theme). The two don't collide.

Both the bridge and the self-hosted brand fonts live in src/lib/themes/nanawall-chrome-tokens.css:

  • It has no @theme {…} manifest, so the theme registry (src/lib/themes/index.ts) skips it — it is not a selectable theme, just a global token + @font-face block.
  • It lives under src/lib/themes/ because that is the only directory the check:tokens audit exempts from the "no literal brand values" rule (the bridge is full of brand hex). See theming.
  • The root layout's import.meta.glob('../lib/themes/*.css') injects it globally.

Source Sans Pro (300/400/600/700) + Source Serif Pro (400) are served from static/fonts/*.woff2 — no fonts.googleapis.com round-trip. The two first-paint weights are <link rel="preload">ed in the layout head; the @import '@nanawallweb/site-chrome/styles' sits in layout.css.

The office suite renders as "Suite 250", not "#250" — the literal #250 matches the check:tokens hex-colour regex. "Suite 250" is the faithful equivalent.

Both are static in the root layout (admin-managed nav/footer content is out of scope for #308). Edit the arrays in src/routes/+layout.svelte:

  • mainNavLinks: NavLink[] — the hamburger menu. NanaSelect product-finder actions first (/select, /compare, product resources), then links back to nanawall.com. forceMobileNav makes this the primary nav at every width.
  • cta — the primary header button (currently "Start selection" → /select).
  • footerColumns, footerContact, footerLegal — the footer's About column, NanaWall contact block (white logo static/nanawall-logo-w.svg, phone, address), and legal links. Copyright holder is "Nana Wall Systems, Inc."

The header logo is static/logo.svg (site-chrome's default /logo.svg).

NanaSage launcher occlusion contract (#1518 / #1743)

The floating Ask-NanaSage launcher (mounted once in the root layout, NanaSageLauncher.svelte) owns the viewport's lower-right corner. Two mechanisms keep it from occluding page content, and every new surface must use one of them:

  • Scrollable content → the shell owes clearance. A host shell reserves var(--sage-launcher-clearance, 4.5rem) of bottom padding below its content column so the last in-flow controls can always scroll clear of the launcher. Both shells honor this: the admin layout (routes/admin/+layout.svelte) and the public #main-content (routes/+layout.svelte, #1743). A new shell or full-bleed surface that opts out of #main-content owes the same padding.
  • Sticky/fixed CTAs → the launcher yields. A pinned action tray can never scroll clear, so the page registers the tray's measured footprint with launcherYield ($lib/nanasage/launcher-yield.svelte.ts); the launcher collapses to its compact icon and lifts above the tray (bottom: … + var(--sage-tray-clearance)). The /compare selection tray is the reference implementation: a ResizeObserver reports height changes (chip rows, viewport), and unmount clears the yield. This is the standing pattern for any future sticky CTA bar — register the footprint, never right-inset the tray (that wastes primary-CTA space on mobile).

Both halves are pinned by launcher-clearance.guard.spec.ts (static contract) and e2e/visual/launcher-occlusion.spec.ts (geometric non-overlap, desktop + mobile widths, empty and picked tray states).

Deviations from the nanaawards port

  • No global border-radius: 0 reset. nanaawards resets radii app-wide; NanaSelect does not, because it would flatten the admin theme's rounded corners (--ns-radius-*). site-chrome's own components get sharp corners from their styles + --nw-radius-base: 0.
  • No .nw-btn / .nw-container / .nw-form-* / .nw-tab utility classes. NanaSelect styles its own surfaces through the --ns-* theme; only the chrome tokens + fonts were ported.
  • No contest branding, A/B nav variants, or mega-menu content — those are nanaawards-specific and out of scope here.