Appearance
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 (
isAdminfalse): a keyboard skip-link, thenSiteShellwrappingSiteHeader(top), the page (#main-content), andSiteFooter(bottom). - Admin (
isAdmintrue): the page renders bare —src/routes/admin/+layout.sveltesupplies 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-faceblock. - It lives under
src/lib/themes/because that is the only directory thecheck:tokensaudit 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
#250matches thecheck:tokenshex-colour regex. "Suite 250" is the faithful equivalent.
Changing the menu or footer
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.forceMobileNavmakes 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 logostatic/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-contentowes 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: aResizeObserverreports 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: 0reset. 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-tabutility 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.