Skip to content

Feature icon grid

Three columns of icon, title and paragraph — the workhorse feature section.

MarketingstarterFeaturedfeaturesgridiconsoverview

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

This exact file renders the preview above.

import {
  Accessibility,
  Code2,
  Layers,
  Palette,
  Server,
  Sprout,
  type LucideIcon,
} from 'lucide-react'
import { Container } from '@/components/ui/layout'
import { features } from '@/content/demo'

/**
 * Feature icon grid
 *
 * The workhorse feature section: three columns of icon, title and paragraph.
 * The icon map is explicit rather than dynamic, so tree-shaking keeps only the
 * six icons this section actually uses.
 */
const icons: Record<string, LucideIcon> = {
  Palette,
  Accessibility,
  Server,
  Layers,
  Code2,
  Sprout,
}

export default function FeatureIconGrid() {
  return (
    <section className="border-b border-line bg-canvas py-section">
      <Container>
        <div className="max-w-2xl">
          <p className="label-caps text-accent">Why Foundry</p>
          <h2 className="display-type mt-3 text-2xl font-semibold text-ink-strong sm:text-3xl">
            Decisions made once, applied everywhere.
          </h2>
        </div>

        <ul className="mt-12 grid gap-x-8 gap-y-10 sm:grid-cols-2 lg:grid-cols-3">
          {features.map((feature) => {
            const Icon = icons[feature.icon] ?? Layers
            return (
              <li key={feature.title}>
                <span className="flex size-10 items-center justify-center rounded-lg border border-line bg-surface text-accent">
                  <Icon className="size-5" aria-hidden="true" />
                </span>
                <h3 className="mt-4 text-md font-semibold text-ink-strong">{feature.title}</h3>
                <p className="mt-2 text-sm leading-relaxed text-ink-muted">{feature.description}</p>
              </li>
            )
          })}
        </ul>
      </Container>
    </section>
  )
}

components/blocks/sections/features/icon-grid.tsx

import {
  Accessibility,
  Code2,
  Layers,
  Palette,
  Server,
  Sprout,
  type LucideIcon,
} from 'lucide-react'
import { Container } from '@/components/ui/layout'
import { features } from '@/content/demo'

/**
 * Feature icon grid
 *
 * The workhorse feature section: three columns of icon, title and paragraph.
 * The icon map is explicit rather than dynamic, so tree-shaking keeps only the
 * six icons this section actually uses.
 */
const icons: Record<string, LucideIcon> = {
  Palette,
  Accessibility,
  Server,
  Layers,
  Code2,
  Sprout,
}

export default function FeatureIconGrid() {
  return (
    <section className="border-b border-line bg-canvas py-section">
      <Container>
        <div className="max-w-2xl">
          <p className="label-caps text-accent">Why Foundry</p>
          <h2 className="display-type mt-3 text-2xl font-semibold text-ink-strong sm:text-3xl">
            Decisions made once, applied everywhere.
          </h2>
        </div>

        <ul className="mt-12 grid gap-x-8 gap-y-10 sm:grid-cols-2 lg:grid-cols-3">
          {features.map((feature) => {
            const Icon = icons[feature.icon] ?? Layers
            return (
              <li key={feature.title}>
                <span className="flex size-10 items-center justify-center rounded-lg border border-line bg-surface text-accent">
                  <Icon className="size-5" aria-hidden="true" />
                </span>
                <h3 className="mt-4 text-md font-semibold text-ink-strong">{feature.title}</h3>
                <p className="mt-2 text-sm leading-relaxed text-ink-muted">{feature.description}</p>
              </li>
            )
          })}
        </ul>
      </Container>
    </section>
  )
}

content/demo.ts

/**
 * Shared demo content.
 *
 * Marketing copy lives here rather than inside JSX, so a section file stays a
 * layout decision and nothing else. Every section in the library draws from
 * this module, which is also what keeps 110 sections from becoming 110 copies
 * of the same paragraph.
 *
 * All names, companies and figures are fictional.
 */

export interface Feature {
  title: string
  description: string
  /** lucide-react icon name, resolved by the consuming section. */
  icon: string
}

export const features: Feature[] = [
  {
    title: 'Token-driven theming',
    description:
      'Three orthogonal axes — scheme, palette, density — compose into every surface. Change one and the whole system follows.',
    icon: 'Palette',
  },
  {
    title: 'Accessible by construction',
    description:
      'Focus management, live regions and keyboard contracts are built into the primitives, not bolted on during review.',
    icon: 'Accessibility',
  },
  {
    title: 'Server-first rendering',
    description:
      'Sections are Server Components. Interactivity is scoped to small client islands, so a marketing page ships almost no JavaScript.',
    icon: 'Server',
  },
  {
    title: 'Composable sections',
    description:
      'Every block is a standalone export. Assemble a page from nine of them, or lift one into an existing codebase.',
    icon: 'Layers',
  },
  {
    title: 'Real source, always',
    description:
      'The code viewer reads the same file that renders the preview. Documentation cannot drift from implementation.',
    icon: 'Code2',
  },
  {
    title: 'Built to grow',
    description:
      'Adding a component means adding a catalogue record and a file. No page in the application needs to change.',
    icon: 'Sprout',
  },
]

export const secondaryFeatures: Feature[] = [
  {
    title: 'Responsive previews',
    description: 'Inspect any block at 390, 768 and 1440 without leaving the page.',
    icon: 'Smartphone',
  },
  {
    title: 'Deterministic copy',
    description: 'The copy button copies exactly what the viewer displays — no rewriting.',
    icon: 'Clipboard',
  },
  {
    title: 'Typed catalogue',
    description: 'One record shape across six families, validated by the test suite.',
    icon: 'ListTree',
  },
  {
    title: 'Zero runtime CSS',
    description: 'Tailwind v4 plus custom properties. No styling library in the bundle.',
    icon: 'Feather',
  },
  {
    title: 'Keyboard everything',
    description: 'Menus, tabs, dialogs and the palette all follow published ARIA patterns.',
    icon: 'Keyboard',
  },
  {
    title: 'Dark authored, not inverted',
    description: 'The dark scheme is designed, not derived from a filter.',
    icon: 'Moon',
  },
]

export interface Stat {
  value: string
  label: string
  detail?: string
}

export const stats: Stat[] = [
  { value: '4', label: 'Composition levels', detail: 'Primitives through complete products' },
  { value: '3', label: 'Theme axes', detail: 'Scheme, palette, density' },
  { value: '0', label: 'Runtime dependencies', detail: 'Beyond React, Next and icons' },
  { value: '100%', label: 'Statically rendered', detail: 'Every catalogue route' },
]

export const companyLogos = [
  'Northwind',
  'Halcyon',
  'Meridian',
  'Kestrel',
  'Lumen Works',
  'Atlas Forge',
  'Verdant',
  'Ironwood',
]

export interface Testimonial {
  quote: string
  name: string
  role: string
  company: string
}

export const testimonials: Testimonial[] = [
  {
    quote:
      'We replaced four half-finished internal libraries with one. The density axis alone paid for the migration — our admin tools and our marketing site finally share components.',
    name: 'Priya Raman',
    role: 'Principal Engineer',
    company: 'Northwind',
  },
  {
    quote:
      'The accessibility work is the part I did not expect. Focus return, live regions, manual tab activation — the things that normally get filed as tech debt were already handled.',
    name: 'Tomas Lindqvist',
    role: 'Head of Design Systems',
    company: 'Halcyon',
  },
  {
    quote:
      'Being able to read the exact source that renders the preview removed an entire class of "the docs are wrong" tickets.',
    name: 'Amara Osei',
    role: 'Staff Frontend Engineer',
    company: 'Meridian',
  },
  {
    quote:
      'Our first landing page took an afternoon. The second took forty minutes, because by then we were composing rather than building.',
    name: 'Jun Watanabe',
    role: 'Product Lead',
    company: 'Kestrel',
  },
  {
    quote:
      'Five palettes sounds like a gimmick until a client asks for a different accent on the third day and it is a one-line change.',
    name: 'Elena Rossi',
    role: 'Creative Director',
    company: 'Lumen Works',
  },
  {
    quote:
      'The starters are the honest part. They are not screenshots — every route resolves, including the 404s.',
    name: 'Marcus Bell',
    role: 'Engineering Manager',
    company: 'Atlas Forge',
  },
]

export interface PricingTier {
  name: string
  price: string
  cadence: string
  description: string
  features: string[]
  cta: string
  featured?: boolean
  note?: string
}

export const pricingTiers: PricingTier[] = [
  {
    name: 'Solo',
    price: '$0',
    cadence: 'forever',
    description: 'For personal projects and evaluation.',
    features: [
      'Every component and section',
      'Copy-paste source',
      'Light and dark schemes',
      'Community support',
    ],
    cta: 'Start building',
  },
  {
    name: 'Team',
    price: '$18',
    cadence: 'per seat / month',
    description: 'For product teams shipping together.',
    features: [
      'Everything in Solo',
      'All five palettes',
      'Starter products',
      'Figma token export',
      'Priority issue triage',
    ],
    cta: 'Start a trial',
    featured: true,
    note: 'Most teams start here.',
  },
  {
    name: 'Business',
    price: '$2,400',
    cadence: 'per year',
    description: 'For organisations with a design-system function.',
    features: [
      'Everything in Team',
      'Unlimited seats',
      'Private component registry',
      'Accessibility review sessions',
      'Named support contact',
    ],
    cta: 'Contact sales',
  },
]

export interface FAQ {
  question: string
  answer: string
}

export const faqs: FAQ[] = [
  {
    question: 'Is Foundry a published npm package?',
    answer:
      'No. Foundry is a template demonstration. Components are copied into your project and owned by you — there is no registry, no install command and no version to upgrade.',
  },
  {
    question: 'How do I change the accent colour?',
    answer:
      'Every accent in the system resolves from one ramp of custom properties. Redefine those seven values in your stylesheet and every button, focus ring, link and badge follows.',
  },
  {
    question: 'Do the components require JavaScript?',
    answer:
      'Most do not. Sections, cards, tables, forms and layout primitives render entirely on the server. Only overlays, menus and the command palette are client islands.',
  },
  {
    question: 'What does the density axis actually change?',
    answer:
      'Control heights, control padding, stack rhythm, card padding, table row height and section padding. It deliberately does not change the type scale, so text stays legible at every density.',
  },
  {
    question: 'Can I use only part of the library?',
    answer:
      'Yes. Each block is a standalone file whose imports are visible in the code viewer. Copying one section brings the primitives it names and nothing else.',
  },
  {
    question: 'How is the source in the documentation kept accurate?',
    answer:
      'A build step reads the real files from disk and generates the source registry. The preview and the code you copy come from the same file, and a test fails if the two ever diverge.',
  },
  {
    question: 'Is there a Figma library?',
    answer:
      'The token manifest is designed to be exported to Figma variables, but no Figma file ships with this demo.',
  },
  {
    question: 'What browsers are supported?',
    answer:
      'Current versions of Chrome, Edge, Safari and Firefox. The library uses container-free responsive CSS and standard custom properties throughout.',
  },
]

export interface TeamMember {
  name: string
  role: string
  bio: string
  location: string
}

export const team: TeamMember[] = [
  {
    name: 'Priya Raman',
    role: 'Design systems',
    bio: 'Spent six years untangling component libraries before deciding to write one properly.',
    location: 'Bengaluru',
  },
  {
    name: 'Tomas Lindqvist',
    role: 'Accessibility',
    bio: 'Audits by day, writes focus-management utilities by night.',
    location: 'Stockholm',
  },
  {
    name: 'Amara Osei',
    role: 'Frontend architecture',
    bio: 'Believes most performance problems are really architecture problems.',
    location: 'Accra',
  },
  {
    name: 'Jun Watanabe',
    role: 'Product',
    bio: 'Turns "we should document this" into things that are actually documented.',
    location: 'Osaka',
  },
  {
    name: 'Elena Rossi',
    role: 'Visual design',
    bio: 'Editorial background; still measures leading by eye and is usually right.',
    location: 'Milan',
  },
  {
    name: 'Marcus Bell',
    role: 'Developer experience',
    bio: 'Optimises for the twentieth time you use something, not the first.',
    location: 'Manchester',
  },
  {
    name: 'Sofia Delgado',
    role: 'Documentation',
    bio: 'Thinks a component without a usage note is only half shipped.',
    location: 'Madrid',
  },
  {
    name: 'Idris Karim',
    role: 'Infrastructure',
    bio: 'Keeps the build under a minute so nobody is tempted to skip it.',
    location: 'Toronto',
  },
]

export interface ProcessStep {
  title: string
  description: string
  duration?: string
}

export const processSteps: ProcessStep[] = [
  {
    title: 'Audit',
    description:
      'Inventory every surface, control and one-off in the existing product. Nothing is designed before the mess is measured.',
    duration: 'Week 1–2',
  },
  {
    title: 'Tokenise',
    description:
      'Extract the real colour, type and spacing decisions into a token set the whole team can read.',
    duration: 'Week 3',
  },
  {
    title: 'Build primitives',
    description:
      'Ship the twenty components that account for eighty per cent of screens, with states and keyboard contracts.',
    duration: 'Week 4–7',
  },
  {
    title: 'Compose',
    description:
      'Assemble sections and page patterns, then migrate a real route to prove the system holds.',
    duration: 'Week 8–10',
  },
  {
    title: 'Document',
    description:
      'Write the usage guidance while the decisions are fresh, and wire the docs to the source.',
    duration: 'Week 11',
  },
  {
    title: 'Hand over',
    description:
      'Contribution guide, review checklist, and a session on how to add the twenty-first component.',
    duration: 'Week 12',
  },
]

export interface TimelineEvent {
  year: string
  title: string
  description: string
}

export const timelineEvents: TimelineEvent[] = [
  {
    year: '2021',
    title: 'One repository, four libraries',
    description:
      'Four product teams, four component sets, four opinions about what "medium" meant.',
  },
  {
    year: '2022',
    title: 'The first token pass',
    description: 'Colour and spacing consolidated. Everything else stayed where it was.',
  },
  {
    year: '2023',
    title: 'Primitives extracted',
    description:
      'Twenty components, one focus treatment, one control height. Adoption became easier than resistance.',
  },
  {
    year: '2024',
    title: 'Sections and patterns',
    description: 'Marketing stopped rebuilding heroes and started composing them.',
  },
  {
    year: '2025',
    title: 'Density as an axis',
    description: 'Admin tools and marketing pages finally shared a component set.',
  },
  {
    year: '2026',
    title: 'Foundry 1.0',
    description: 'Four levels, one system, documented against its own source.',
  },
]

export interface CaseStudy {
  slug: string
  client: string
  title: string
  summary: string
  sector: string
  year: string
  metrics: Array<{ label: string; value: string }>
}

export const caseStudies: CaseStudy[] = [
  {
    slug: 'northwind-console',
    client: 'Northwind',
    title: 'One console for eleven internal tools',
    summary:
      'Eleven admin surfaces, each with its own table implementation, consolidated into a single shell with a shared data layer.',
    sector: 'Logistics',
    year: '2025',
    metrics: [
      { label: 'Tools consolidated', value: '11' },
      { label: 'Bundle reduction', value: '61%' },
      { label: 'Time to new screen', value: '2 days' },
    ],
  },
  {
    slug: 'halcyon-storefront',
    client: 'Halcyon',
    title: 'A storefront that loads on a train',
    summary:
      'Rebuilt a client-rendered catalogue as server components with small islands, targeting the worst connection the team could find.',
    sector: 'Retail',
    year: '2025',
    metrics: [
      { label: 'JS shipped', value: '−78%' },
      { label: 'LCP, 3G', value: '1.4s' },
      { label: 'Conversion', value: '+9%' },
    ],
  },
  {
    slug: 'meridian-docs',
    client: 'Meridian',
    title: 'Documentation that cannot go stale',
    summary:
      'Wired the documentation site to the component source so every example is generated from the file it documents.',
    sector: 'Developer tools',
    year: '2024',
    metrics: [
      { label: 'Stale examples', value: '0' },
      { label: 'Docs PRs / month', value: '×3' },
      { label: 'Support tickets', value: '−34%' },
    ],
  },
  {
    slug: 'kestrel-onboarding',
    client: 'Kestrel',
    title: 'Onboarding in four steps instead of nine',
    summary:
      'Reduced a nine-screen signup to four, with real validation, resumable progress and a working keyboard path throughout.',
    sector: 'Fintech',
    year: '2024',
    metrics: [
      { label: 'Steps removed', value: '5' },
      { label: 'Completion', value: '+27%' },
      { label: 'Support contacts', value: '−41%' },
    ],
  },
  {
    slug: 'lumen-rebrand',
    client: 'Lumen Works',
    title: 'A rebrand in one pull request',
    summary:
      'Because every surface resolved from tokens, a full visual rebrand touched one file and shipped in a single review.',
    sector: 'Media',
    year: '2023',
    metrics: [
      { label: 'Files changed', value: '1' },
      { label: 'Screens updated', value: '240+' },
      { label: 'Regressions', value: '0' },
    ],
  },
]

export interface Post {
  slug: string
  title: string
  excerpt: string
  category: string
  date: string
  readingTime: string
  author: string
}

export const posts: Post[] = [
  {
    slug: 'density-as-an-axis',
    title: 'Density belongs in your token system',
    excerpt:
      'Most design systems treat compact mode as a table prop. Making it a theme axis is what finally lets admin tools and marketing pages share components.',
    category: 'Design systems',
    date: '2026-03-12',
    readingTime: '8 min',
    author: 'Priya Raman',
  },
  {
    slug: 'manual-tab-activation',
    title: 'Why your tabs should not auto-activate',
    excerpt:
      'Automatic activation feels responsive and quietly punishes keyboard users. A short argument for the boring default.',
    category: 'Accessibility',
    date: '2026-02-27',
    readingTime: '6 min',
    author: 'Tomas Lindqvist',
  },
  {
    slug: 'documentation-from-source',
    title: 'Generate documentation from the file, not about it',
    excerpt:
      'A build step that reads the real component and a test that fails on drift removes an entire category of wrong documentation.',
    category: 'Tooling',
    date: '2026-02-09',
    readingTime: '5 min',
    author: 'Sofia Delgado',
  },
  {
    slug: 'server-components-marketing',
    title: 'Marketing pages do not need a framework runtime',
    excerpt:
      'What actually needs to be interactive on a landing page, and how small the remaining island can be.',
    category: 'Performance',
    date: '2026-01-22',
    readingTime: '9 min',
    author: 'Amara Osei',
  },
  {
    slug: 'variants-that-earn-their-place',
    title: 'A variant should represent a decision, not a padding value',
    excerpt:
      'How to tell a real variant from an inflated catalogue, and why one hundred good components beat three hundred similar ones.',
    category: 'Design systems',
    date: '2026-01-08',
    readingTime: '7 min',
    author: 'Elena Rossi',
  },
  {
    slug: 'focus-return',
    title: 'The half of focus management everyone forgets',
    excerpt:
      'Trapping focus in a dialog is the easy part. Putting it back where it came from is what keyboard users actually notice.',
    category: 'Accessibility',
    date: '2025-12-15',
    readingTime: '4 min',
    author: 'Tomas Lindqvist',
  },
]

export interface Integration {
  name: string
  category: string
  description: string
}

export const integrations: Integration[] = [
  {
    name: 'Vector',
    category: 'Analytics',
    description: 'Event streaming with a typed schema registry.',
  },
  {
    name: 'Cinder',
    category: 'Monitoring',
    description: 'Traces, logs and alerts in one timeline.',
  },
  {
    name: 'Postmark',
    category: 'Email',
    description: 'Transactional delivery with per-template metrics.',
  },
  { name: 'Harbour', category: 'Storage', description: 'Object storage with signed upload URLs.' },
  {
    name: 'Sentinel',
    category: 'Security',
    description: 'Dependency and secret scanning on every push.',
  },
  { name: 'Ledger', category: 'Billing', description: 'Usage metering and invoice generation.' },
  {
    name: 'Relay',
    category: 'Messaging',
    description: 'Webhooks with automatic retry and replay.',
  },
  {
    name: 'Atlas',
    category: 'Search',
    description: 'Typo-tolerant search with faceted filtering.',
  },
  { name: 'Beacon', category: 'Support', description: 'Shared inbox with conversation routing.' },
  { name: 'Quarry', category: 'Data', description: 'Warehouse sync with incremental models.' },
  { name: 'Pilot', category: 'Deployment', description: 'Preview environments per pull request.' },
  { name: 'Lantern', category: 'Documentation', description: 'Docs generated from typed schemas.' },
]

export interface Product {
  slug: string
  name: string
  category: string
  priceCents: number
  compareAtCents?: number
  description: string
  material: string
  badge?: string
  inStock: boolean
  rating: number
  reviews: number
}

export const products: Product[] = [
  {
    slug: 'field-shell-jacket',
    name: 'Field Shell Jacket',
    category: 'outerwear',
    priceCents: 32800,
    compareAtCents: 41000,
    description:
      'A three-layer shell cut close enough to wear in a city and sealed well enough not to.',
    material: 'Recycled ripstop',
    badge: 'Last season pricing',
    inStock: true,
    rating: 4.7,
    reviews: 214,
  },
  {
    slug: 'quarry-overshirt',
    name: 'Quarry Overshirt',
    category: 'outerwear',
    priceCents: 18500,
    description: 'Heavyweight cotton twill that behaves like a jacket and packs like a shirt.',
    material: 'Organic cotton twill',
    inStock: true,
    rating: 4.5,
    reviews: 132,
  },
  {
    slug: 'meridian-knit',
    name: 'Meridian Merino Knit',
    category: 'knitwear',
    priceCents: 14900,
    description: 'Fine-gauge merino with a high neck and no branding anywhere on it.',
    material: 'Extrafine merino',
    badge: 'Restocked',
    inStock: true,
    rating: 4.8,
    reviews: 341,
  },
  {
    slug: 'harbour-cardigan',
    name: 'Harbour Cardigan',
    category: 'knitwear',
    priceCents: 21000,
    description: 'A boxy cardigan with horn buttons and a shawl collar that holds its shape.',
    material: 'Lambswool',
    inStock: false,
    rating: 4.4,
    reviews: 88,
  },
  {
    slug: 'atlas-chino',
    name: 'Atlas Chino',
    category: 'trousers',
    priceCents: 12800,
    compareAtCents: 15900,
    description: 'A straight-leg chino with a proper waistband and pockets that hold a phone.',
    material: 'Compact cotton',
    inStock: true,
    rating: 4.6,
    reviews: 507,
  },
  {
    slug: 'ironwood-trouser',
    name: 'Ironwood Wool Trouser',
    category: 'trousers',
    priceCents: 24500,
    description: 'Pleated, cuffed, and cut for people who sit down during the day.',
    material: 'Wool flannel',
    inStock: true,
    rating: 4.3,
    reviews: 76,
  },
  {
    slug: 'lumen-tote',
    name: 'Lumen Tote',
    category: 'accessories',
    priceCents: 9800,
    description: 'Waxed canvas with a leather base and a strap long enough for a coat.',
    material: 'Waxed canvas',
    badge: 'Best seller',
    inStock: true,
    rating: 4.9,
    reviews: 623,
  },
  {
    slug: 'kestrel-cap',
    name: 'Kestrel Six-Panel Cap',
    category: 'accessories',
    priceCents: 5200,
    description: 'Unstructured, cotton-lined, with a brim that survives being sat on.',
    material: 'Cotton canvas',
    inStock: true,
    rating: 4.2,
    reviews: 195,
  },
  {
    slug: 'verdant-tee',
    name: 'Verdant Heavy Tee',
    category: 'basics',
    priceCents: 4800,
    description: 'A 240gsm tee that holds its collar past the first wash.',
    material: 'Organic cotton',
    inStock: true,
    rating: 4.6,
    reviews: 1102,
  },
  {
    slug: 'halcyon-socks',
    name: 'Halcyon Ribbed Socks',
    category: 'basics',
    priceCents: 2200,
    description: 'Three pairs. Ribbed, reinforced, and the same length as each other.',
    material: 'Merino blend',
    inStock: true,
    rating: 4.5,
    reviews: 289,
  },
]

export const productCategories = [
  { slug: 'outerwear', name: 'Outerwear', description: 'Shells, overshirts and coats.' },
  { slug: 'knitwear', name: 'Knitwear', description: 'Merino, lambswool and cotton knits.' },
  { slug: 'trousers', name: 'Trousers', description: 'Chinos, wool and workwear cuts.' },
  { slug: 'accessories', name: 'Accessories', description: 'Bags, caps and small goods.' },
  { slug: 'basics', name: 'Basics', description: 'Tees, socks and everyday layers.' },
]

export interface MenuDish {
  name: string
  description: string
  price: string
  tags?: string[]
}

export interface MenuCourse {
  course: string
  note?: string
  dishes: MenuDish[]
}

export const restaurantMenu: MenuCourse[] = [
  {
    course: 'To begin',
    note: 'Served from opening until close.',
    dishes: [
      {
        name: 'Cured trout, fennel, dill oil',
        description: 'Two-day cure, shaved fennel, dill pressed the same morning.',
        price: '16',
        tags: ['GF'],
      },
      {
        name: 'Charred leek, hazelnut, aged sheep cheese',
        description: 'Leeks over embers until the outer layer gives up entirely.',
        price: '14',
        tags: ['V'],
      },
      {
        name: 'Sourdough, cultured butter',
        description: 'Four-day starter, butter churned in-house on Tuesdays.',
        price: '7',
        tags: ['V'],
      },
    ],
  },
  {
    course: 'Mains',
    dishes: [
      {
        name: 'Aged duck breast, quince, bitter leaves',
        description: 'Dry-aged fourteen days, quince from the orchard at Ashfield.',
        price: '34',
      },
      {
        name: 'Turbot on the bone, brown butter, capers',
        description: 'Whole fish for two, carved at the table.',
        price: '58',
        tags: ['For two'],
      },
      {
        name: 'Barley, wild mushroom, preserved lemon',
        description: 'Slow-cooked barley with a mushroom broth reduced for six hours.',
        price: '26',
        tags: ['V', 'Vegan on request'],
      },
    ],
  },
  {
    course: 'To finish',
    dishes: [
      {
        name: 'Burnt honey tart, crème fraîche',
        description: 'Honey taken just past the point most people would stop.',
        price: '12',
        tags: ['V'],
      },
      {
        name: 'Poached pear, walnut, blue cheese',
        description: 'Pears poached in the last of the autumn cider.',
        price: '13',
        tags: ['V'],
      },
      {
        name: 'Selection of British cheese',
        description: 'Three cheeses, oat crackers, quince paste.',
        price: '16',
        tags: ['V'],
      },
    ],
  },
]

export const changelog = [
  {
    version: '1.0.0',
    date: '2026-03-14',
    title: 'Foundry 1.0',
    summary:
      'The first complete release: four composition levels, one token system, one catalogue.',
    changes: [
      {
        kind: 'added' as const,
        text: '45 primitives across actions, forms, feedback, overlay, navigation, data and layout.',
      },
      {
        kind: 'added' as const,
        text: '15 navigation patterns, 12 header systems and 10 footer systems.',
      },
      {
        kind: 'added' as const,
        text: '28 form flows with real validation, first-invalid focus and live error announcement.',
      },
      {
        kind: 'added' as const,
        text: '112 sections spanning marketing, commerce, SaaS, content, application and state.',
      },
      {
        kind: 'added' as const,
        text: '10 complete page patterns and 10 multi-route starter products.',
      },
      {
        kind: 'added' as const,
        text: 'Theme playground with five palettes and three densities, persisted and flash-free.',
      },
    ],
  },
  {
    version: '0.9.0',
    date: '2026-02-20',
    title: 'Starters and the composition playground',
    summary: 'Level 4 arrives, and sections become assemblable rather than merely browsable.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Ten starter products, each with working navigation and its own 404 path.',
      },
      { kind: 'added' as const, text: 'URL-driven section composition playground.' },
      {
        kind: 'changed' as const,
        text: 'Catalogue records gained `relatedItems`, validated to resolve.',
      },
      {
        kind: 'fixed' as const,
        text: 'Drawer no longer restored focus to a detached element after route changes.',
      },
    ],
  },
  {
    version: '0.8.0',
    date: '2026-01-30',
    title: 'Density becomes an axis',
    summary: 'Compact, Default and Relaxed move out of component props and into the token layer.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Density tokens for control height, padding, stack rhythm and row height.',
      },
      {
        kind: 'changed' as const,
        text: 'Every control now derives its height from `--density-control-height`.',
      },
      {
        kind: 'removed' as const,
        text: 'Per-component `compact` props, superseded by the density axis.',
      },
    ],
  },
  {
    version: '0.7.0',
    date: '2026-01-12',
    title: 'Source registry',
    summary: 'Documentation is generated from the files it documents.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Build-time source registry; the viewer reads the real file.',
      },
      {
        kind: 'added' as const,
        text: 'Drift test that regenerates the registry and fails on mismatch.',
      },
      {
        kind: 'fixed' as const,
        text: 'Copy button now falls back to a hidden textarea on insecure origins.',
      },
    ],
  },
  {
    version: '0.6.0',
    date: '2025-12-04',
    title: 'Accessibility pass',
    summary: 'Focus management and live regions moved into the primitives.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Focus trap with guaranteed focus return across Dialog, Drawer and the palette.',
      },
      { kind: 'changed' as const, text: 'Tabs switched to manual activation.' },
      { kind: 'fixed' as const, text: 'Tooltips now open on keyboard focus, not only on hover.' },
      {
        kind: 'fixed' as const,
        text: 'Toast queue announces in order instead of interrupting itself.',
      },
    ],
  },
]

export const faqShort = faqs.slice(0, 4)

Demo source — adapt to your project. Foundry is not published as a package.

Usage

The icon map is explicit rather than dynamic, so bundlers keep only the six icons the section actually uses.

  • Six items is the sweet spot; nine starts to read as a list rather than a set.
  • Icons should differ in silhouette, not only in metaphor.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Three columns at lg
  • Two at sm
  • Explicit icon map

Accessibility

List semantics
Features are a ul, so the count is conveyed structurally.
Decorative icons
Every icon is aria-hidden; the heading carries the meaning.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.