--- baseline/PROGRESS.md
+++ candidate/PROGRESS.md
@@ -1,19 +1,27 @@
 # Progress
 
-Status: starter repository received
+Status: vertical slice implemented
 
 ## Completed
 
-- [ ] Responsive page composition
-- [ ] Filter and URL state
-- [ ] Service detail interaction
-- [ ] Accessible enquiry flow
-- [ ] Content/missing-media resilience
-- [ ] Reduced-motion treatment
-- [ ] Type check
-- [ ] Production build
+- [x] Responsive page composition — header, hero, service explorer, approach strip, closing action, footer; designed at 360 / 768 / 1440 with intermediate breakpoints (1099px, 900px, 760px, 619px).
+- [x] Filter and URL state — filter buttons set `?discipline=…` via `pushState`; a `popstate` listener restores the filter on back/forward; absent or unsupported values resolve to `all` (`src/components/ServiceExplorer.tsx`).
+- [x] Service detail interaction — each card exposes a native `<button>` (`aria-pressed`, `aria-controls`) that reveals description, deliverables and response note in the always-labelled `service-detail` region; the region receives focus on selection.
+- [x] Accessible enquiry flow — native `<dialog>` via `showModal()` (Escape close, focus containment, focus return to opener); native `<select>` for Project type; required-field, email-format and 20-non-whitespace-character summary validation with field-linked errors (`aria-describedby`/`aria-invalid`); exact supplied success message rendered with no network request.
+- [x] Content/missing-media resilience — `switchboard-modernisation` long title wraps untruncated; `power-quality` renders a designed dark fallback panel (`media-fallback`) carrying the supplied alt text; no horizontal overflow at target widths.
+- [x] Reduced-motion treatment — `useReducedMotion()` disables entrance/state animations in Motion components; a `prefers-reduced-motion` CSS block zeroes transitions and restores instant scrolling.
+- [x] Type check
+- [x] Production build
 
 ## Verification evidence
 
-Record commands and outcomes here. Keep this concise and factual.
+- `npm run check` — passes (tsc -b, no errors).
+- `npm run build` — passes: `dist/index.html` 0.54 kB, CSS 14.33 kB, JS 335.61 kB, "built in 207ms".
+- `npm run preview` + curl — `/`, `/assets/shutdown.svg` and `/?discipline=controls` all return 200.
+- Static testid audit of the built bundle confirmed all contract hooks present; `filter-*` and `service-card-*` values are constructed from `content.ts` data at render time.
+- No browser was available in this environment, so interactive behaviours (menu, filters, dialog, validation) were verified by code review rather than automated UI tests.
 
+## Notes
+
+- No packages, remote assets or fonts added; copy comes verbatim from `src/content.ts`.
+- Multiple `scope-review-open` triggers exist (header, mobile menu, hero, closing section), as the contract allows.

--- baseline/src/App.tsx
+++ candidate/src/App.tsx
@@ -1,42 +1,127 @@
+import { useState } from 'react'
+import { motion, useReducedMotion } from 'motion/react'
 import { content } from './content'
+import { Header } from './components/Header'
+import { ScopeDialog } from './components/ScopeDialog'
+import { ServiceExplorer } from './components/ServiceExplorer'
 
 function App() {
+  const [dialogOpen, setDialogOpen] = useState(false)
+  const reduceMotion = useReducedMotion()
+
+  const rise = (delay = 0) =>
+    reduceMotion
+      ? {}
+      : {
+          initial: { opacity: 0, y: 16 },
+          animate: { opacity: 1, y: 0 },
+          transition: { duration: 0.55, delay, ease: 'easeOut' as const },
+        }
+
+  const riseInView = reduceMotion
+    ? {}
+    : {
+        initial: { opacity: 0, y: 16 },
+        whileInView: { opacity: 1, y: 0 },
+        viewport: { once: true, margin: '-80px' },
+        transition: { duration: 0.5, ease: 'easeOut' as const },
+      }
+
   return (
-    <div className="site-shell">
-      <header className="site-header">
-        <a className="brand" href="#top" aria-label="Gridline Field Services home">
-          <strong>{content.brand}</strong>
-          <span>{content.descriptor}</span>
-        </a>
-        <nav aria-label="Primary navigation">
-          {content.nav.map((item) => <a key={item.href} href={item.href}>{item.label}</a>)}
-        </nav>
-        <button type="button" data-testid="scope-review-open">{content.hero.primaryAction}</button>
-      </header>
-
-      <main id="top">
-        <section className="hero">
-          <p className="eyebrow">{content.hero.eyebrow}</p>
-          <h1>{content.hero.title}</h1>
-          <p>{content.hero.body}</p>
-          <a href="#services">{content.hero.secondaryAction}</a>
+    <div className="site-shell" id="top">
+      <a className="skip-link" href="#services">
+        Skip to services
+      </a>
+
+      <Header onOpenDialog={() => setDialogOpen(true)} />
+
+      <main>
+        <section className="hero" aria-labelledby="hero-title">
+          <div className="hero-copy">
+            <motion.p className="eyebrow" {...rise(0)}>
+              {content.hero.eyebrow}
+            </motion.p>
+            <motion.h1 id="hero-title" {...rise(0.06)}>
+              {content.hero.title}
+            </motion.h1>
+            <motion.p className="hero-body" {...rise(0.12)}>
+              {content.hero.body}
+            </motion.p>
+            <motion.div className="hero-actions" {...rise(0.18)}>
+              <button
+                type="button"
+                className="btn btn-primary"
+                data-testid="scope-review-open"
+                onClick={() => setDialogOpen(true)}
+              >
+                {content.hero.primaryAction}
+              </button>
+              <a className="btn btn-ghost" href="#services">
+                {content.hero.secondaryAction}
+              </a>
+            </motion.div>
+            <motion.p className="hero-availability" {...rise(0.24)}>
+              <span className="availability-dot" aria-hidden="true" />
+              {content.hero.availability}
+            </motion.p>
+          </div>
+          <motion.figure className="hero-media" aria-hidden="true" {...rise(0.12)}>
+            <img src="/assets/shutdown.svg" alt="" />
+            <figcaption>
+              <span>Fig. 01</span>
+              <span>Shutdown workfront</span>
+              <span>37.8°S / 144.9°E</span>
+            </figcaption>
+          </motion.figure>
         </section>
 
-        <section id="services" className="services">
-          <p className="eyebrow">{content.servicesIntro.eyebrow}</p>
-          <h2>{content.servicesIntro.title}</h2>
-          <p>{content.servicesIntro.body}</p>
-          <div className="service-grid">
-            {content.services.map((service) => (
-              <article key={service.id} data-testid={`service-card-${service.id}`}>
-                <p>{service.discipline}</p>
-                <h3>{service.title}</h3>
-                <p>{service.summary}</p>
-              </article>
-            ))}
+        <ServiceExplorer />
+
+        <section id="approach" className="approach" aria-labelledby="approach-title">
+          <div className="section-head">
+            <p className="eyebrow">{content.approach.eyebrow}</p>
+            <h2 id="approach-title">{content.approach.title}</h2>
           </div>
+          <motion.ol className="principles" {...riseInView}>
+            {content.approach.principles.map((principle) => (
+              <li key={principle.number} className="principle">
+                <span className="principle-number" aria-hidden="true">
+                  {principle.number}
+                </span>
+                <h3>{principle.title}</h3>
+                <p>{principle.body}</p>
+              </li>
+            ))}
+          </motion.ol>
+        </section>
+
+        <section className="close" aria-labelledby="close-title">
+          <motion.div className="close-inner" {...riseInView}>
+            <p className="eyebrow">{content.close.eyebrow}</p>
+            <h2 id="close-title">{content.close.title}</h2>
+            <p className="close-body">{content.close.body}</p>
+            <button
+              type="button"
+              className="btn btn-primary"
+              data-testid="scope-review-open"
+              onClick={() => setDialogOpen(true)}
+            >
+              {content.hero.primaryAction}
+            </button>
+          </motion.div>
         </section>
       </main>
+
+      <footer className="site-footer">
+        <p className="footer-brand">
+          <strong>{content.brand}</strong> {content.descriptor}
+        </p>
+        <p>{content.footer.line}</p>
+        <p>{content.footer.region}</p>
+        <p className="footer-note">{content.footer.note}</p>
+      </footer>
+
+      <ScopeDialog open={dialogOpen} onClose={() => setDialogOpen(false)} />
     </div>
   )
 }

--- baseline/src/components/Header.tsx
+++ candidate/src/components/Header.tsx
@@ -0,0 +1,103 @@
+import { useEffect, useRef, useState } from 'react'
+import { content } from '../content'
+
+type HeaderProps = {
+  onOpenDialog: () => void
+}
+
+export function Header({ onOpenDialog }: HeaderProps) {
+  const [menuOpen, setMenuOpen] = useState(false)
+  const toggleRef = useRef<HTMLButtonElement>(null)
+
+  // Escape closes the mobile menu and returns focus to the toggle.
+  useEffect(() => {
+    if (!menuOpen) return
+    const onKeyDown = (event: KeyboardEvent) => {
+      // Ignore Escape while the scope-review dialog is open — it belongs
+      // to the dialog, and the menu must stay put so focus can return to
+      // an opener that lives inside it.
+      if (event.key === 'Escape' && !document.querySelector('dialog[open]')) {
+        setMenuOpen(false)
+        toggleRef.current?.focus()
+      }
+    }
+    document.addEventListener('keydown', onKeyDown)
+    return () => document.removeEventListener('keydown', onKeyDown)
+  }, [menuOpen])
+
+  // Close the menu if the viewport grows past the mobile breakpoint.
+  useEffect(() => {
+    const query = window.matchMedia('(min-width: 761px)')
+    const onChange = () => {
+      if (query.matches) setMenuOpen(false)
+    }
+    query.addEventListener('change', onChange)
+    return () => query.removeEventListener('change', onChange)
+  }, [])
+
+  return (
+    <header className="site-header">
+      <div className="header-bar">
+        <a className="brand" href="#top" aria-label="Gridline Field Services — top of page">
+          <strong>{content.brand}</strong>
+          <span>{content.descriptor}</span>
+        </a>
+
+        <nav className="header-nav" aria-label="Primary">
+          {content.nav.map((item) => (
+            <a key={item.href} href={item.href}>
+              {item.label}
+            </a>
+          ))}
+        </nav>
+
+        <button
+          type="button"
+          className="btn btn-primary header-cta"
+          data-testid="scope-review-open"
+          onClick={onOpenDialog}
+        >
+          {content.hero.primaryAction}
+        </button>
+
+        <button
+          type="button"
+          className="menu-toggle"
+          ref={toggleRef}
+          data-testid="mobile-menu-toggle"
+          aria-expanded={menuOpen}
+          aria-controls="mobile-menu"
+          onClick={() => setMenuOpen((open) => !open)}
+        >
+          <span className="menu-toggle-glyph" aria-hidden="true">
+            {menuOpen ? '×' : '≡'}
+          </span>
+          Menu
+        </button>
+      </div>
+
+      <div
+        id="mobile-menu"
+        className="mobile-menu"
+        data-testid="mobile-menu"
+        hidden={!menuOpen}
+      >
+        <nav aria-label="Mobile">
+          {content.nav.map((item) => (
+            <a key={item.href} href={item.href} onClick={() => setMenuOpen(false)}>
+              {item.label}
+            </a>
+          ))}
+        </nav>
+        <button
+          type="button"
+          className="btn btn-primary"
+          data-testid="scope-review-open"
+          onClick={onOpenDialog}
+        >
+          {content.hero.primaryAction}
+        </button>
+      </div>
+    </header>
+  )
+}

--- baseline/src/components/ScopeDialog.tsx
+++ candidate/src/components/ScopeDialog.tsx
@@ -0,0 +1,216 @@
+import { useEffect, useRef, useState } from 'react'
+import type { FormEvent } from 'react'
+import { content } from '../content'
+
+type FieldName = 'name' | 'email' | 'projectType' | 'summary'
+type Errors = Partial<Record<FieldName, string>>
+
+const FIELD_ORDER: FieldName[] = ['name', 'email', 'projectType', 'summary']
+const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+
+function validate(formData: FormData): Errors {
+  const errors: Errors = {}
+  const name = String(formData.get('name') ?? '').trim()
+  const email = String(formData.get('email') ?? '').trim()
+  const projectType = String(formData.get('projectType') ?? '')
+  const summary = String(formData.get('summary') ?? '')
+
+  if (!name) errors.name = 'Enter your name.'
+  if (!email) {
+    errors.email = 'Enter your work email.'
+  } else if (!EMAIL_PATTERN.test(email)) {
+    errors.email = 'Enter a valid email address, like name@company.com.'
+  }
+  if (!projectType) errors.projectType = 'Choose a project type.'
+  const summaryLength = summary.replace(/\s/g, '').length
+  if (summaryLength === 0) {
+    errors.summary = 'Enter a project summary.'
+  } else if (summaryLength < 20) {
+    errors.summary = 'Add more detail — at least 20 characters, not counting spaces.'
+  }
+  return errors
+}
+
+type ScopeDialogProps = {
+  open: boolean
+  onClose: () => void
+}
+
+export function ScopeDialog({ open, onClose }: ScopeDialogProps) {
+  const dialogRef = useRef<HTMLDialogElement>(null)
+  const formRef = useRef<HTMLFormElement>(null)
+  const nameRef = useRef<HTMLInputElement>(null)
+  const [errors, setErrors] = useState<Errors>({})
+  const [submitted, setSubmitted] = useState(false)
+
+  // Keep the native <dialog> in sync with React state. showModal() gives us
+  // Escape handling, focus containment and focus return to the opener.
+  useEffect(() => {
+    const dialog = dialogRef.current
+    if (!dialog) return
+    if (open && !dialog.open) {
+      dialog.showModal()
+      nameRef.current?.focus()
+    } else if (!open && dialog.open) {
+      dialog.close()
+    }
+    if (!open) {
+      // Reset while closed so the next open presents a fresh form
+      // (and the name field exists to receive focus).
+      setErrors({})
+      setSubmitted(false)
+      formRef.current?.reset()
+    }
+  }, [open])
+
+  // Native close (Escape) must flow back into React state.
+  useEffect(() => {
+    const dialog = dialogRef.current
+    if (!dialog) return
+    const handleClose = () => onClose()
+    dialog.addEventListener('close', handleClose)
+    return () => dialog.removeEventListener('close', handleClose)
+  }, [onClose])
+
+  function handleSubmit(event: FormEvent<HTMLFormElement>) {
+    event.preventDefault()
+    const form = event.currentTarget
+    const nextErrors = validate(new FormData(form))
+    setErrors(nextErrors)
+    const firstInvalid = FIELD_ORDER.find((field) => nextErrors[field])
+    if (firstInvalid) {
+      const control = form.elements.namedItem(firstInvalid)
+      if (control instanceof HTMLElement) control.focus()
+      return
+    }
+    setSubmitted(true)
+  }
+
+  function describedBy(field: FieldName): string | undefined {
+    return errors[field] ? `scope-error-${field}` : undefined
+  }
+
+  return (
+    <dialog
+      ref={dialogRef}
+      className="scope-dialog"
+      data-testid="scope-review-dialog"
+      aria-labelledby="scope-dialog-title"
+    >
+      <div className="dialog-head">
+        <p className="dialog-kicker" aria-hidden="true">Gridline / scope request</p>
+        <button type="button" className="dialog-close" onClick={onClose}>
+          Close
+        </button>
+      </div>
+      <h2 id="scope-dialog-title">{content.form.title}</h2>
+      <p className="dialog-intro">{content.form.intro}</p>
+
+      {submitted ? (
+        <div className="dialog-success">
+          <p role="status" data-testid="scope-review-success">
+            {content.form.success}
+          </p>
+          <button type="button" className="btn btn-primary" onClick={onClose}>
+            Close
+          </button>
+        </div>
+      ) : (
+        <form
+          ref={formRef}
+          data-testid="scope-review-form"
+          noValidate
+          onSubmit={handleSubmit}
+        >
+          <div className="field">
+            <label htmlFor="scope-name">Name</label>
+            <input
+              id="scope-name"
+              ref={nameRef}
+              name="name"
+              type="text"
+              autoComplete="name"
+              required
+              aria-invalid={errors.name ? true : undefined}
+              aria-describedby={describedBy('name')}
+            />
+            {errors.name && (
+              <p className="field-error" id="scope-error-name" data-testid="error-name">
+                {errors.name}
+              </p>
+            )}
+          </div>
+
+          <div className="field">
+            <label htmlFor="scope-email">Work email</label>
+            <input
+              id="scope-email"
+              name="email"
+              type="email"
+              autoComplete="email"
+              required
+              aria-invalid={errors.email ? true : undefined}
+              aria-describedby={describedBy('email')}
+            />
+            {errors.email && (
+              <p className="field-error" id="scope-error-email" data-testid="error-email">
+                {errors.email}
+              </p>
+            )}
+          </div>
+
+          <div className="field">
+            <label htmlFor="scope-project-type">Project type</label>
+            <select
+              id="scope-project-type"
+              name="projectType"
+              required
+              defaultValue=""
+              aria-invalid={errors.projectType ? true : undefined}
+              aria-describedby={describedBy('projectType')}
+            >
+              <option value="" disabled>
+                Select a project type
+              </option>
+              {content.form.projectTypes.map((type) => (
+                <option key={type} value={type}>
+                  {type}
+                </option>
+              ))}
+            </select>
+            {errors.projectType && (
+              <p
+                className="field-error"
+                id="scope-error-projectType"
+                data-testid="error-projectType"
+              >
+                {errors.projectType}
+              </p>
+            )}
+          </div>
+
+          <div className="field">
+            <label htmlFor="scope-summary">Project summary</label>
+            <textarea
+              id="scope-summary"
+              name="summary"
+              rows={5}
+              required
+              aria-invalid={errors.summary ? true : undefined}
+              aria-describedby={describedBy('summary')}
+            />
+            {errors.summary && (
+              <p className="field-error" id="scope-error-summary" data-testid="error-summary">
+                {errors.summary}
+              </p>
+            )}
+          </div>
+
+          <button type="submit" className="btn btn-primary dialog-submit">
+            {content.form.submit}
+          </button>
+        </form>
+      )}
+    </dialog>
+  )
+}

--- baseline/src/components/ServiceExplorer.tsx
+++ candidate/src/components/ServiceExplorer.tsx
@@ -0,0 +1,197 @@
+import { useEffect, useRef, useState } from 'react'
+import { AnimatePresence, motion, useReducedMotion } from 'motion/react'
+import { content } from '../content'
+import type { Discipline, Service } from '../content'
+
+const SUPPORTED: Discipline[] = ['all', 'electrical', 'controls', 'reliability']
+
+function readDisciplineFromUrl(): Discipline {
+  const raw = new URLSearchParams(window.location.search).get('discipline')
+  return SUPPORTED.includes(raw as Discipline) ? (raw as Discipline) : 'all'
+}
+
+export function ServiceExplorer() {
+  const reduceMotion = useReducedMotion()
+  const [discipline, setDiscipline] = useState<Discipline>(() => readDisciplineFromUrl())
+  const [selectedId, setSelectedId] = useState<string | null>(null)
+  const detailRef = useRef<HTMLElement>(null)
+
+  // Browser back/forward restores the visible filter.
+  useEffect(() => {
+    const onPopState = () => setDiscipline(readDisciplineFromUrl())
+    window.addEventListener('popstate', onPopState)
+    return () => window.removeEventListener('popstate', onPopState)
+  }, [])
+
+  function applyFilter(next: Discipline) {
+    const url = new URL(window.location.href)
+    if (next === discipline) {
+      // Same filter re-activated: make sure the URL represents it without
+      // stacking a duplicate history entry.
+      if (url.searchParams.get('discipline') !== next) {
+        url.searchParams.set('discipline', next)
+        window.history.replaceState({ discipline: next }, '', url)
+      }
+      return
+    }
+    setDiscipline(next)
+    url.searchParams.set('discipline', next)
+    window.history.pushState({ discipline: next }, '', url)
+  }
+
+  const visibleServices = content.services.filter(
+    (service) => discipline === 'all' || service.discipline === discipline,
+  )
+  const selectedService =
+    visibleServices.find((service) => service.id === selectedId) ?? null
+
+  function selectService(service: Service) {
+    setSelectedId(service.id)
+    // The detail container is always mounted, so it can take focus right away.
+    requestAnimationFrame(() => {
+      const region = detailRef.current
+      if (!region) return
+      region.focus({ preventScroll: true })
+      region.scrollIntoView({
+        behavior: reduceMotion ? 'auto' : 'smooth',
+        block: 'nearest',
+      })
+    })
+  }
+
+  return (
+    <section id="services" className="services" aria-labelledby="services-title">
+      <div className="section-head">
+        <p className="eyebrow">{content.servicesIntro.eyebrow}</p>
+        <h2 id="services-title">{content.servicesIntro.title}</h2>
+        <p className="section-lede">{content.servicesIntro.body}</p>
+      </div>
+
+      <div className="explorer-controls">
+        <div
+          className="filter-group"
+          role="group"
+          aria-label="Filter services by discipline"
+        >
+          {content.filters.map((filter) => (
+            <button
+              key={filter.value}
+              type="button"
+              className="filter-btn"
+              data-testid={`filter-${filter.value}`}
+              aria-pressed={discipline === filter.value}
+              onClick={() => applyFilter(filter.value)}
+            >
+              {filter.label}
+            </button>
+          ))}
+        </div>
+        <p className="service-count" data-testid="service-count" aria-live="polite">
+          {visibleServices.length} of {content.services.length} services shown
+        </p>
+      </div>
+
+      <motion.ul
+        key={discipline}
+        className="service-grid"
+        initial={reduceMotion ? false : { opacity: 0, y: 10 }}
+        animate={{ opacity: 1, y: 0 }}
+        transition={{ duration: 0.35, ease: 'easeOut' }}
+      >
+        {visibleServices.map((service, index) => (
+          <li
+            key={service.id}
+            className="service-card"
+            data-testid={`service-card-${service.id}`}
+            data-selected={service.id === selectedService?.id || undefined}
+          >
+            <div className="card-media">
+              {service.image ? (
+                <img src={service.image} alt={service.imageAlt} loading="lazy" />
+              ) : (
+                <div className="media-fallback" data-testid="media-fallback">
+                  <span className="fallback-mark" aria-hidden="true">
+                    ∿ no signal capture
+                  </span>
+                  <p>{service.imageAlt}</p>
+                </div>
+              )}
+              <span className="card-index" aria-hidden="true">
+                {String(index + 1).padStart(2, '0')}
+              </span>
+            </div>
+            <div className="card-body">
+              <p className="card-discipline">{service.discipline}</p>
+              <h3>{service.title}</h3>
+              <p className="card-summary">{service.summary}</p>
+              <button
+                type="button"
+                className="card-inspect"
+                aria-pressed={service.id === selectedService?.id}
+                aria-controls="service-detail"
+                onClick={() => selectService(service)}
+              >
+                Inspect<span className="visually-hidden"> {service.title}</span>
+                <span aria-hidden="true"> ↓</span>
+              </button>
+            </div>
+          </li>
+        ))}
+      </motion.ul>
+
+      <section
+        id="service-detail"
+        className="service-detail"
+        data-testid="service-detail"
+        aria-label="Selected service detail"
+        tabIndex={-1}
+        ref={detailRef}
+      >
+        <p className="detail-kicker" aria-hidden="true">
+          Selected work package
+        </p>
+        <AnimatePresence mode="wait" initial={false}>
+          {selectedService ? (
+            <motion.div
+              key={selectedService.id}
+              className="detail-body"
+              initial={reduceMotion ? false : { opacity: 0, y: 12 }}
+              animate={{ opacity: 1, y: 0 }}
+              exit={reduceMotion ? undefined : { opacity: 0, y: -8 }}
+              transition={{ duration: 0.3, ease: 'easeOut' }}
+            >
+              <h3>{selectedService.title}</h3>
+              <p className="detail-description">{selectedService.description}</p>
+              <div className="detail-columns">
+                <div>
+                  <h4>Deliverables</h4>
+                  <ul>
+                    {selectedService.deliverables.map((item) => (
+                      <li key={item}>{item}</li>
+                    ))}
+                  </ul>
+                </div>
+                <div>
+                  <h4>Response note</h4>
+                  <p>{selectedService.responseNote}</p>
+                </div>
+              </div>
+            </motion.div>
+          ) : (
+            <motion.p
+              key="detail-empty"
+              className="detail-empty"
+              initial={reduceMotion ? false : { opacity: 0 }}
+              animate={{ opacity: 1 }}
+              exit={reduceMotion ? undefined : { opacity: 0 }}
+              transition={{ duration: 0.2 }}
+            >
+              Select “Inspect” on a service above to review its description,
+              deliverables and response note here.
+            </motion.p>
+          )}
+        </AnimatePresence>
+      </section>
+    </section>
+  )
+}

--- baseline/src/styles.css
+++ candidate/src/styles.css
@@ -1,37 +1,1052 @@
+/* ------------------------------------------------------------------
+   Gridline Field Services — round 0 slice
+   Direction: field notebook meets control cabinet.
+   Warm paper ground, ink/steel structure, one citron accent.
+------------------------------------------------------------------- */
+
 :root {
-  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-  color: #18211f;
-  background: #f1efe7;
+  --paper: #f1efe7;
+  --paper-2: #e8e5d8;
+  --ink: #18211f;
+  --ink-deep: #222e2b;
+  --steel: #59645e;
+  --line: #b9bcb0;
+  --line-soft: #d2d1c4;
+  --citron: #d5ff43;
+  --error: #99351b;
+
+  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
+    "Helvetica Neue", Arial, sans-serif;
+  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
+    "Liberation Mono", monospace;
+
+  --gutter: clamp(1.25rem, 4vw, 3.5rem);
+  --measure: 44rem;
+
+  font-family: var(--font-sans);
+  color: var(--ink);
+  background: var(--paper);
   font-synthesis: none;
   text-rendering: optimizeLegibility;
 }
 
-* { box-sizing: border-box; }
-html { scroll-behavior: smooth; }
-body { margin: 0; min-width: 320px; }
-button, input, select, textarea { font: inherit; }
-a { color: inherit; }
-.site-shell { min-height: 100vh; }
-.site-header { display: flex; align-items: center; justify-content: space-between; gap: 2rem; padding: 1rem 4vw; border-bottom: 1px solid #b7bbb4; }
-.brand { display: grid; text-decoration: none; }
-.brand span, .eyebrow { font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; }
-nav { display: flex; gap: 1.5rem; }
-.hero, .services { padding: 5rem 6vw; }
-.hero { min-height: 55vh; display: grid; align-content: center; max-width: 74rem; }
-h1 { max-width: 10ch; margin: .25em 0; font-size: clamp(3rem, 9vw, 7rem); line-height: .9; letter-spacing: -.06em; }
-.hero > p:not(.eyebrow) { max-width: 42rem; font-size: 1.15rem; line-height: 1.6; }
-.services { border-top: 1px solid #b7bbb4; }
-.services > h2 { max-width: 18ch; font-size: clamp(2rem, 5vw, 4.5rem); line-height: 1; }
-.service-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin-top: 3rem; background: #aeb4ae; border: 1px solid #aeb4ae; }
-.service-grid article { min-width: 0; padding: 1.5rem; background: #f1efe7; }
+* {
+  box-sizing: border-box;
+}
+
+html {
+  scroll-behavior: smooth;
+  scroll-padding-top: 5rem;
+}
+
+body {
+  margin: 0;
+  min-width: 320px;
+  line-height: 1.5;
+}
+
+body:has(dialog[open]) {
+  overflow: hidden;
+}
+
+img {
+  max-width: 100%;
+  display: block;
+}
+
+button,
+input,
+select,
+textarea {
+  font: inherit;
+  color: inherit;
+}
+
+a {
+  color: inherit;
+}
+
+h1,
+h2,
+h3,
+h4,
+p,
+ul,
+ol,
+figure {
+  margin: 0;
+}
+
+:focus-visible {
+  outline: 2px solid var(--ink);
+  outline-offset: 2px;
+}
+
+.visually-hidden {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0 0 0 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+.skip-link {
+  position: absolute;
+  left: var(--gutter);
+  top: -4rem;
+  z-index: 50;
+  padding: 0.6rem 1rem;
+  background: var(--ink);
+  color: var(--paper);
+  font-family: var(--font-mono);
+  font-size: 0.8rem;
+  text-decoration: none;
+  transition: top 0.15s ease-out;
+}
+
+.skip-link:focus-visible {
+  top: 0.5rem;
+  outline-color: var(--citron);
+}
+
+.site-shell {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+main {
+  flex: 1;
+}
+
+/* --- shared vocabulary ------------------------------------------- */
+
+.eyebrow {
+  font-family: var(--font-mono);
+  font-size: 0.72rem;
+  letter-spacing: 0.14em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.eyebrow::before {
+  content: "";
+  display: inline-block;
+  width: 1.6rem;
+  height: 1px;
+  background: var(--steel);
+  vertical-align: middle;
+  margin-right: 0.6rem;
+}
+
+.section-head {
+  max-width: var(--measure);
+  display: grid;
+  gap: 0.9rem;
+}
+
+.section-head h2 {
+  font-size: clamp(1.7rem, 3.6vw, 2.75rem);
+  line-height: 1.05;
+  letter-spacing: -0.02em;
+  font-weight: 650;
+}
+
+.section-lede {
+  color: var(--steel);
+  max-width: 38rem;
+}
+
+.btn {
+  display: inline-flex;
+  align-items: center;
+  gap: 0.5rem;
+  padding: 0.75rem 1.3rem;
+  border: 1px solid var(--ink);
+  background: transparent;
+  font-size: 0.95rem;
+  font-weight: 550;
+  text-decoration: none;
+  cursor: pointer;
+  transition: background-color 0.15s ease-out, color 0.15s ease-out,
+    box-shadow 0.15s ease-out;
+}
+
+.btn-primary {
+  background: var(--citron);
+  color: var(--ink);
+  box-shadow: 3px 3px 0 0 var(--ink);
+}
+
+.btn-primary:hover {
+  background: var(--ink);
+  color: var(--citron);
+}
+
+.btn-primary:active {
+  box-shadow: 1px 1px 0 0 var(--ink);
+  translate: 2px 2px;
+}
+
+.btn-ghost:hover {
+  background: var(--ink);
+  color: var(--paper);
+}
+
+/* --- header ------------------------------------------------------- */
+
+.site-header {
+  position: sticky;
+  top: 0;
+  z-index: 40;
+  background: var(--paper);
+  border-bottom: 1px solid var(--ink);
+}
+
+.header-bar {
+  display: flex;
+  align-items: center;
+  gap: 2rem;
+  padding: 0.85rem var(--gutter);
+}
+
+.brand {
+  display: grid;
+  line-height: 1.15;
+  text-decoration: none;
+  margin-right: auto;
+}
+
+.brand strong {
+  font-size: 1.05rem;
+  letter-spacing: 0.18em;
+  font-weight: 750;
+}
+
+.brand span {
+  font-family: var(--font-mono);
+  font-size: 0.66rem;
+  letter-spacing: 0.22em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.header-nav {
+  display: flex;
+  gap: 1.75rem;
+}
+
+.header-nav a {
+  font-family: var(--font-mono);
+  font-size: 0.82rem;
+  letter-spacing: 0.08em;
+  text-transform: uppercase;
+  text-decoration: none;
+  padding: 0.35rem 0;
+  border-bottom: 2px solid transparent;
+  transition: border-color 0.15s ease-out;
+}
+
+.header-nav a:hover {
+  border-bottom-color: var(--ink);
+}
+
+.header-cta {
+  padding: 0.55rem 1.1rem;
+  font-size: 0.85rem;
+}
+
+.menu-toggle {
+  display: none;
+  align-items: center;
+  gap: 0.5rem;
+  padding: 0.5rem 0.9rem;
+  border: 1px solid var(--ink);
+  background: var(--paper);
+  font-family: var(--font-mono);
+  font-size: 0.8rem;
+  letter-spacing: 0.08em;
+  text-transform: uppercase;
+  cursor: pointer;
+}
+
+.menu-toggle[aria-expanded="true"] {
+  background: var(--ink);
+  color: var(--paper);
+}
+
+.menu-toggle-glyph {
+  font-size: 1rem;
+  line-height: 1;
+}
+
+.mobile-menu {
+  display: none;
+  border-top: 1px solid var(--ink);
+  background: var(--paper-2);
+  padding: 1.1rem var(--gutter) 1.4rem;
+}
+
+.mobile-menu nav {
+  display: grid;
+  gap: 0.15rem;
+  margin-bottom: 1.1rem;
+}
+
+.mobile-menu nav a {
+  font-family: var(--font-mono);
+  font-size: 0.95rem;
+  letter-spacing: 0.08em;
+  text-transform: uppercase;
+  text-decoration: none;
+  padding: 0.75rem 0.25rem;
+  border-bottom: 1px solid var(--line);
+}
+
+.mobile-menu nav a:hover {
+  background: var(--paper);
+}
+
+.mobile-menu .btn {
+  width: 100%;
+  justify-content: center;
+}
+
+/* --- hero ---------------------------------------------------------- */
+
+.hero {
+  display: grid;
+  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
+  gap: clamp(2rem, 5vw, 4.5rem);
+  align-items: end;
+  padding: clamp(3rem, 7vw, 6rem) var(--gutter) clamp(3rem, 6vw, 5rem);
+  border-bottom: 1px solid var(--ink);
+  background:
+    linear-gradient(var(--line-soft) 0 0) left var(--gutter) top 0 / 1px 100% no-repeat,
+    var(--paper);
+}
+
+.hero-copy {
+  display: grid;
+  gap: 1.4rem;
+  padding-left: clamp(0.9rem, 2vw, 1.6rem);
+}
+
+.hero h1 {
+  font-size: clamp(2.6rem, 7.5vw, 5.5rem);
+  line-height: 0.95;
+  letter-spacing: -0.035em;
+  font-weight: 700;
+  max-width: 12ch;
+}
+
+.hero-body {
+  font-size: clamp(1.02rem, 1.4vw, 1.2rem);
+  line-height: 1.6;
+  max-width: 34rem;
+  color: var(--ink);
+}
+
+.hero-actions {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.9rem;
+  margin-top: 0.4rem;
+}
+
+.hero-availability {
+  display: flex;
+  align-items: center;
+  gap: 0.6rem;
+  font-family: var(--font-mono);
+  font-size: 0.78rem;
+  letter-spacing: 0.06em;
+  color: var(--steel);
+}
+
+.availability-dot {
+  width: 0.55rem;
+  height: 0.55rem;
+  background: var(--citron);
+  border: 1px solid var(--ink);
+  flex-shrink: 0;
+}
+
+.hero-media {
+  display: grid;
+  gap: 0;
+  border: 1px solid var(--ink);
+  background: var(--ink-deep);
+}
+
+.hero-media img {
+  width: 100%;
+  aspect-ratio: 3 / 2;
+  object-fit: cover;
+}
+
+.hero-media figcaption {
+  display: flex;
+  justify-content: space-between;
+  gap: 1rem;
+  flex-wrap: wrap;
+  padding: 0.55rem 0.8rem;
+  border-top: 1px solid var(--ink);
+  background: var(--paper);
+  font-family: var(--font-mono);
+  font-size: 0.68rem;
+  letter-spacing: 0.1em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+/* --- service explorer ---------------------------------------------- */
+
+.services {
+  padding: clamp(3rem, 6vw, 5.5rem) var(--gutter);
+}
+
+.explorer-controls {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 1rem;
+  margin-top: 2.2rem;
+  padding-bottom: 0.9rem;
+  border-bottom: 1px solid var(--ink);
+}
+
+.filter-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.5rem;
+}
+
+.filter-btn {
+  padding: 0.5rem 1.05rem;
+  border: 1px solid var(--ink);
+  background: var(--paper);
+  font-family: var(--font-mono);
+  font-size: 0.8rem;
+  letter-spacing: 0.08em;
+  text-transform: uppercase;
+  cursor: pointer;
+  transition: background-color 0.15s ease-out, color 0.15s ease-out;
+}
+
+.filter-btn:hover {
+  background: var(--paper-2);
+}
+
+.filter-btn[aria-pressed="true"] {
+  background: var(--ink);
+  color: var(--citron);
+}
+
+.service-count {
+  font-family: var(--font-mono);
+  font-size: 0.78rem;
+  letter-spacing: 0.08em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.service-grid {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 1px;
+  background: var(--line);
+  border: 1px solid var(--line);
+  border-top: 0;
+}
+
+.service-card {
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  background: var(--paper);
+  position: relative;
+}
+
+.service-card[data-selected] {
+  box-shadow: inset 0 0 0 2px var(--ink);
+}
+
+.service-card[data-selected]::after {
+  content: "";
+  position: absolute;
+  inset: 0 0 auto 0;
+  height: 4px;
+  background: var(--citron);
+}
+
+.card-media {
+  position: relative;
+  border-bottom: 1px solid var(--line);
+}
+
+.card-media img {
+  width: 100%;
+  aspect-ratio: 5 / 3;
+  object-fit: cover;
+  filter: saturate(0.92);
+}
+
+.card-index {
+  position: absolute;
+  top: 0.55rem;
+  left: 0.55rem;
+  padding: 0.15rem 0.45rem;
+  background: var(--paper);
+  border: 1px solid var(--ink);
+  font-family: var(--font-mono);
+  font-size: 0.68rem;
+  letter-spacing: 0.12em;
+}
+
+.media-fallback {
+  aspect-ratio: 5 / 3;
+  display: grid;
+  align-content: center;
+  gap: 0.6rem;
+  padding: 1.2rem 1.4rem;
+  background:
+    repeating-linear-gradient(
+      -45deg,
+      transparent 0 14px,
+      rgba(213, 255, 67, 0.07) 14px 15px
+    ),
+    var(--ink-deep);
+  color: var(--paper);
+}
+
+.fallback-mark {
+  font-family: var(--font-mono);
+  font-size: 0.7rem;
+  letter-spacing: 0.16em;
+  text-transform: uppercase;
+  color: var(--citron);
+}
+
+.media-fallback p {
+  font-family: var(--font-mono);
+  font-size: 0.82rem;
+  line-height: 1.5;
+  color: var(--paper);
+  opacity: 0.85;
+}
+
+.card-body {
+  display: flex;
+  flex-direction: column;
+  gap: 0.7rem;
+  padding: 1.2rem 1.4rem 1.4rem;
+  flex: 1;
+}
+
+.card-discipline {
+  font-family: var(--font-mono);
+  font-size: 0.68rem;
+  letter-spacing: 0.16em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.service-card h3 {
+  font-size: 1.16rem;
+  line-height: 1.25;
+  letter-spacing: -0.01em;
+  font-weight: 650;
+  overflow-wrap: break-word;
+}
+
+.card-summary {
+  font-size: 0.92rem;
+  line-height: 1.55;
+  color: var(--steel);
+  flex: 1;
+}
+
+.card-inspect {
+  align-self: start;
+  padding: 0.45rem 0;
+  border: 0;
+  border-bottom: 2px solid var(--ink);
+  background: none;
+  font-family: var(--font-mono);
+  font-size: 0.8rem;
+  letter-spacing: 0.1em;
+  text-transform: uppercase;
+  cursor: pointer;
+  transition: color 0.15s ease-out, border-color 0.15s ease-out;
+}
+
+.card-inspect:hover {
+  color: var(--steel);
+}
+
+.card-inspect[aria-pressed="true"] {
+  border-bottom-color: transparent;
+  background: var(--ink);
+  color: var(--citron);
+  padding: 0.45rem 0.7rem;
+}
+
+/* --- detail region -------------------------------------------------- */
+
+.service-detail {
+  margin-top: clamp(1.75rem, 3vw, 2.5rem);
+  border: 1px solid var(--ink);
+  background: var(--ink-deep);
+  color: var(--paper);
+  padding: clamp(1.4rem, 3vw, 2.4rem);
+}
+
+.service-detail:focus-visible {
+  outline: 2px solid var(--citron);
+  outline-offset: 3px;
+}
+
+.detail-kicker {
+  font-family: var(--font-mono);
+  font-size: 0.7rem;
+  letter-spacing: 0.18em;
+  text-transform: uppercase;
+  color: var(--citron);
+  margin-bottom: 1.1rem;
+}
+
+.detail-kicker::after {
+  content: "";
+  display: block;
+  height: 1px;
+  margin-top: 0.8rem;
+  background: linear-gradient(to right, var(--citron) 0 3rem, rgba(241, 239, 231, 0.25) 3rem 100%);
+}
+
+.detail-empty {
+  font-family: var(--font-mono);
+  font-size: 0.88rem;
+  line-height: 1.6;
+  color: rgba(241, 239, 231, 0.75);
+  max-width: 40rem;
+}
+
+.detail-body {
+  display: grid;
+  gap: 1.2rem;
+}
+
+.detail-body h3 {
+  font-size: clamp(1.35rem, 2.4vw, 1.9rem);
+  line-height: 1.15;
+  letter-spacing: -0.015em;
+  max-width: 30ch;
+  overflow-wrap: break-word;
+}
+
+.detail-description {
+  max-width: 46rem;
+  line-height: 1.65;
+  color: rgba(241, 239, 231, 0.92);
+}
+
+.detail-columns {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
+  gap: 1.5rem;
+  padding-top: 1.2rem;
+  border-top: 1px solid rgba(241, 239, 231, 0.25);
+}
+
+.detail-columns h4 {
+  font-family: var(--font-mono);
+  font-size: 0.7rem;
+  letter-spacing: 0.16em;
+  text-transform: uppercase;
+  color: var(--citron);
+  margin-bottom: 0.7rem;
+}
+
+.detail-columns ul {
+  list-style: none;
+  padding: 0;
+  display: grid;
+  gap: 0.45rem;
+}
+
+.detail-columns li {
+  padding-left: 1.2rem;
+  position: relative;
+  line-height: 1.5;
+}
+
+.detail-columns li::before {
+  content: "—";
+  position: absolute;
+  left: 0;
+  color: var(--citron);
+}
+
+.detail-columns p {
+  line-height: 1.6;
+  color: rgba(241, 239, 231, 0.92);
+}
+
+/* --- approach -------------------------------------------------------- */
+
+.approach {
+  padding: clamp(3rem, 6vw, 5.5rem) var(--gutter);
+  border-top: 1px solid var(--ink);
+  background: var(--paper-2);
+}
+
+.principles {
+  list-style: none;
+  margin: 2.4rem 0 0;
+  padding: 0;
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: clamp(1.5rem, 3vw, 2.5rem);
+}
+
+.principle {
+  border-top: 2px solid var(--ink);
+  padding-top: 1.1rem;
+  display: grid;
+  gap: 0.55rem;
+  align-content: start;
+}
+
+.principle-number {
+  font-family: var(--font-mono);
+  font-size: 0.78rem;
+  letter-spacing: 0.14em;
+  color: var(--steel);
+}
+
+.principle h3 {
+  font-size: 1.15rem;
+  font-weight: 650;
+  letter-spacing: -0.01em;
+}
+
+.principle p {
+  font-size: 0.94rem;
+  line-height: 1.6;
+  color: var(--steel);
+}
+
+/* --- closing action --------------------------------------------------- */
+
+.close {
+  border-top: 1px solid var(--ink);
+  background: var(--ink);
+  color: var(--paper);
+}
+
+.close-inner {
+  padding: clamp(3rem, 6vw, 5rem) var(--gutter);
+  display: grid;
+  gap: 1.1rem;
+  justify-items: start;
+}
+
+.close .eyebrow {
+  color: var(--citron);
+}
+
+.close .eyebrow::before {
+  background: var(--citron);
+}
+
+.close h2 {
+  font-size: clamp(1.7rem, 3.8vw, 2.9rem);
+  line-height: 1.05;
+  letter-spacing: -0.02em;
+  max-width: 22ch;
+}
+
+.close-body {
+  max-width: 38rem;
+  line-height: 1.65;
+  color: rgba(241, 239, 231, 0.85);
+}
+
+.close .btn-primary {
+  margin-top: 0.6rem;
+  box-shadow: 3px 3px 0 0 var(--citron);
+  border-color: var(--citron);
+}
+
+.close .btn-primary:hover {
+  background: var(--paper);
+  color: var(--ink);
+}
+
+.close :focus-visible {
+  outline-color: var(--citron);
+}
+
+/* --- footer ------------------------------------------------------------ */
+
+.site-footer {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.4rem 2.5rem;
+  align-items: baseline;
+  padding: 1.4rem var(--gutter) 1.8rem;
+  border-top: 1px solid var(--line);
+  font-family: var(--font-mono);
+  font-size: 0.74rem;
+  letter-spacing: 0.06em;
+  color: var(--steel);
+}
+
+.footer-brand strong {
+  color: var(--ink);
+  letter-spacing: 0.16em;
+}
+
+.footer-note {
+  flex-basis: 100%;
+}
+
+/* --- dialog ------------------------------------------------------------- */
+
+.scope-dialog {
+  width: min(34rem, calc(100vw - 2rem));
+  max-height: calc(100dvh - 2rem);
+  border: 1px solid var(--ink);
+  padding: clamp(1.25rem, 3vw, 2rem);
+  background: var(--paper);
+  color: var(--ink);
+  box-shadow: 6px 6px 0 0 var(--ink);
+}
+
+.scope-dialog::backdrop {
+  background: rgba(24, 33, 31, 0.6);
+}
+
+.dialog-head {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 1rem;
+  margin-bottom: 1.1rem;
+}
+
+.dialog-kicker {
+  font-family: var(--font-mono);
+  font-size: 0.68rem;
+  letter-spacing: 0.16em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.dialog-close {
+  border: 1px solid var(--ink);
+  background: var(--paper);
+  padding: 0.35rem 0.8rem;
+  font-family: var(--font-mono);
+  font-size: 0.74rem;
+  letter-spacing: 0.1em;
+  text-transform: uppercase;
+  cursor: pointer;
+}
+
+.dialog-close:hover {
+  background: var(--ink);
+  color: var(--paper);
+}
+
+.scope-dialog h2 {
+  font-size: clamp(1.4rem, 3vw, 1.8rem);
+  letter-spacing: -0.015em;
+  line-height: 1.1;
+}
+
+.dialog-intro {
+  margin-top: 0.7rem;
+  margin-bottom: 1.4rem;
+  color: var(--steel);
+  line-height: 1.55;
+  font-size: 0.94rem;
+}
+
+.scope-dialog form {
+  display: grid;
+  gap: 1.1rem;
+}
+
+.field {
+  display: grid;
+  gap: 0.4rem;
+}
+
+.field label {
+  font-family: var(--font-mono);
+  font-size: 0.74rem;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+}
+
+.field input,
+.field select,
+.field textarea {
+  width: 100%;
+  padding: 0.65rem 0.75rem;
+  border: 1px solid var(--ink);
+  border-radius: 0;
+  background: #fbfaf5;
+}
+
+.field textarea {
+  resize: vertical;
+  min-height: 6.5rem;
+}
+
+.field select {
+  appearance: none;
+  background-image: linear-gradient(45deg, transparent 49%, var(--ink) 50%),
+    linear-gradient(-45deg, transparent 49%, var(--ink) 50%);
+  background-position: calc(100% - 1.15rem) 55%, calc(100% - 0.75rem) 55%;
+  background-size: 0.4rem 0.4rem;
+  background-repeat: no-repeat;
+}
+
+.field [aria-invalid="true"] {
+  border-color: var(--error);
+  box-shadow: inset 2px 0 0 0 var(--error);
+}
+
+.field-error {
+  font-size: 0.85rem;
+  line-height: 1.4;
+  color: var(--error);
+}
+
+.field-error::before {
+  content: "▲ ";
+  font-size: 0.65rem;
+  vertical-align: 0.1em;
+}
+
+.dialog-submit {
+  justify-self: start;
+  margin-top: 0.2rem;
+}
+
+.dialog-success {
+  display: grid;
+  gap: 1.2rem;
+  justify-items: start;
+  border: 1px solid var(--ink);
+  border-left: 4px solid var(--citron);
+  background: var(--paper-2);
+  padding: 1.2rem 1.3rem;
+}
+
+.dialog-success p {
+  line-height: 1.55;
+  font-weight: 550;
+}
+
+/* --- responsive --------------------------------------------------------- */
+
+@media (max-width: 1099px) {
+  .service-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (max-width: 900px) {
+  .hero {
+    grid-template-columns: 1fr;
+    align-items: start;
+  }
+
+  .hero-media {
+    max-width: 34rem;
+  }
+}
 
 @media (max-width: 760px) {
-  .site-header nav { display: none; }
-  .site-header > button { display: none; }
-  .service-grid { grid-template-columns: 1fr; }
+  .header-nav,
+  .header-cta {
+    display: none;
+  }
+
+  .menu-toggle {
+    display: inline-flex;
+  }
+
+  .mobile-menu {
+    display: block;
+  }
+
+  .mobile-menu[hidden] {
+    display: none;
+  }
+
+  .hero-copy {
+    padding-left: 0;
+  }
+
+  .hero {
+    background: var(--paper);
+  }
+
+  .explorer-controls {
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .service-count {
+    text-align: right;
+  }
+}
+
+@media (max-width: 619px) {
+  .service-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .principles {
+    grid-template-columns: 1fr;
+  }
+
+  .hero-actions .btn {
+    width: 100%;
+    justify-content: center;
+  }
+
+  .site-footer {
+    flex-direction: column;
+    align-items: flex-start;
+  }
 }
 
+/* --- reduced motion ------------------------------------------------------ */
+
 @media (prefers-reduced-motion: reduce) {
-  html { scroll-behavior: auto; }
-  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
+  html {
+    scroll-behavior: auto;
+  }
+
+  *,
+  *::before,
+  *::after {
+    animation-duration: 0.01ms !important;
+    animation-iteration-count: 1 !important;
+    transition-duration: 0.01ms !important;
+  }
 }
