src/pages/You are building consumer-side code: pages, layouts, app-level screens that compose the shipped library. The 5 Laws (root CLAUDE.md) apply. The strict library audit protocol from src/CLAUDE.md does not apply here — page work should compose freely from existing primitives, not pre-flight every change against an analog.
This file is loaded for any session whose CWD is in src/pages/ or src/components/app/.
Before composing a new page in src/pages/ or src/components/app/, identify the matching exemplar in .claude/exemplars/ and view its wireframe + code as the canonical pattern. Composition recipes in COMPOSITION-RECIPES.md describe which primitives to compose; exemplars describe how to arrange them. The exemplar is an anchor, not a jail — deviate when the brief demands, using VE primitives correctly.
Discovery flow: .claude/rules/exemplars.md.
src/components/{ui,icons,core,chrome,modals}/. Do NOT modify them from here.MEMORY.md) is the fast lookup. The full source of truth is src/config/component-registry.json. Read the registry before assuming a primitive does or doesn’t exist.Navigation.svelte.src/components/app/modals/) and are wired into the modal system via src/config/modal-registry.ts. Shipped fragments under src/components/modals/ are read-only.When the task is page or app composition, read in this order:
src/config/component-registry.json — what already exists<button> missing btn-void, fieldset overrides, sticky-vs-nav math, conditional rendering jumps, chip-as-toggle anti-patterns. Read this BEFORE writing markup.src/pages/, src/layouts/, or src/components/app/.claude/rules/*.md (auto-loaded on path match — page-composition.md, tailwind-registry.md, spacing-protocol.md, etc.)Default to editing consumer files. Do not edit src/components/ui/, src/components/icons/, src/components/core/, src/styles/, src/types/, or src/config/design-tokens.ts unless the user explicitly asks for system-level work.
.astro files are route shells. src/layouts/ owns the shared app shell. src/components/app/*.svelte owns sections, form state, async actions. See .claude/rules/page-composition.md for the full scaffold and architecture rules.
---
import Layout from '../layouts/Layout.astro';
import AccountPage from '@components/app/AccountPage.svelte';
---
<Layout title="Account">
<AccountPage client:load />
</Layout>
Layout imports use a relative path (../layouts/Layout.astro). There is no @layouts/* alias — every page in src/pages/ follows this convention, and nested routes (e.g. src/pages/atmospheres/[brand]/[atmosphere].astro) walk up the appropriate number of segments.
Spacing rhythm follows .claude/rules/spacing-protocol.md. Quick reference:
container flex flex-col gap-2xl py-2xlflex flex-col gap-xlsurface-raised p-lg flex flex-col gap-lgsurface-sunk p-md flex flex-col gap-mdSurface variety — plain surface-raised is the right default for most blocks; reach beyond it only when the role genuinely differs. Restraint doctrine + full catalog in .claude/rules/component-usage.md §“Surface catalog” and .claude/rules/silent-failures.md §14. Quick-pick (use sparingly):
surface-spotlight p-mdsurface-void p-md overflow-autosurface-raised surface-tint-warm / cool / accentbg-system-subtle / bg-premium-subtle / bg-success-subtle + matching text-*Before reaching for raw <input> / <button> / <select>, check the registry for a composite. Common composites:
SearchField, EditField, EditTextarea, PasswordField, CopyField, ColorField, GenerateField, GenerateTextarea, FormField (label + slot), Selector, Combobox, Switcher, SliderField, ToggleActionBtn (icon + text), IconBtn (icon-only round), ProfileBtn, ThemesBtn. Use plain <button class="btn-cta"> / <button class="btn-ghost"> for text-only actions.Modal, Dropdown (open via the modal / layerStack singletons — never re-instantiate). Sidebar is mounted directly in markup, not via singletons — two variants: 'sidebar' (default — primary section nav inside .docs-layout or .docs-layout-three-col) or 'toc' (article TOC inside .docs-layout-toc — hides below large-desktop).Tabs, Pagination, LoadMore. Pair Sidebar variant="toc" with ScrollProgress for long-form reading pages.If a composite doesn’t support ...rest and you need to pass extra native attributes, extend the composite — don’t duplicate its template.
<script lang="ts">
import { Heart, TriangleAlert } from '@lucide/svelte';
</script>
<Heart class="icon" data-size="lg" />
<TriangleAlert class="icon text-error" />
Lucide icons (ISC, commercial-safe). Always class="icon". Sizing via data-size (sm | md | lg | xl | 2xl | 3xl | 4xl). State via data-* attributes only.
Custom animated icons live in src/components/icons/. Use them like Lucide icons — class pattern is icon-[name] icon (component class first, base class second). Each interactive icon namespaces its own icon-[name] selector.
Icons inherit color from their parent via currentColor. Color is decided at the usage site:
<Check class="icon text-success" />.btn-icon hover: uses filter: brightness() — no hover:text-* needed.btn-icon): set color on parent — <button class="text-mute hover:text-error"><X class="icon" /></button>.Each custom icon’s stroke and fill mode is declared as an attribute on the <svg> root, not in .icon SCSS or Tailwind. This prevents cascade-layer fights (Tailwind utilities outranking SCSS) and lets each icon pick the mode that matches its shape language.
The three categories:
<!-- Stroke-only (line icons, like most Lucide) -->
<svg class="icon-arrow icon" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path d="M5 12h14M13 5l7 7-7 7" />
</svg>
<!-- Mixed (line + accent fill) -->
<svg class="icon-pin icon" stroke="currentColor" fill="currentColor" viewBox="0 0 24 24">
<path d="..." stroke-width="2" fill="none" />
<circle cx="12" cy="10" r="3" />
</svg>
<!-- Fill-only (solid glyphs, logos) -->
<svg class="icon-spark icon" fill="currentColor" viewBox="0 0 24 24">
<path d="..." />
</svg>
Lucide icons declare their own fill="none" internally — just use class="icon", don’t add fill-none.
Never set fill or stroke from CSS / Tailwind on .icon. Tailwind v4’s utilities layer outranks void-scss, and any SCSS rule like .icon { fill: var(--energy-primary); } will silently lose to a text-* utility on the same element. Color flows through currentColor and the parent’s text color — that’s why the SVG attributes use currentColor.
Non-square viewBox icons (e.g. LogoDGRS, LogoCoNexus) carry data-render="logo" on the <svg> root so SCSS can opt out of square data-size constraints.
SVG <defs> IDs (masks, filters, gradients) are auto-generated via $props.id() per instance. Don’t pass a manual id prop expecting to namespace internal defs — the runtime already does it. CSS animation hooks target classes (.mask-rect, .mask-x), not IDs.
fill / stroke from CSS — declare them as attributes on the <svg>.is-active for icon state — use data-state="active".Plain text, muted color, centered, generous padding. No italic — italic is reserved for prose semantics.
<p class="text-mute text-center p-lg">No items yet</p>
Premium packages (@void-energy/ambient-layers, @void-energy/kinetic-text) are consumed through their published exports. From consumer code:
import { ambientLayer } from '@void-energy/ambient-layers';
import { kinetic, narrative } from '@void-energy/kinetic-text';
Do not reach into packages/<name>/src/ via relative paths. Each premium package documents its API in its own AI-REFERENCE.md (see packages/ambient-layers/AI-REFERENCE.md and packages/kinetic-text/AI-REFERENCE.md).
$state in the page-level Svelte component.modal, toast, user, voidEngine, layerStack, shortcutRegistry) — see root CLAUDE.md.