--- baseline/src/App.tsx
+++ candidate/src/App.tsx
@@ -1,42 +1,53 @@
-import { content } from './content'
+import { useCallback, useRef, useState } from 'react'
+import { content, Service } from './content'
+import { useDisciplineFilter } from './hooks/useDisciplineFilter'
+import { Header } from './components/Header'
+import { Hero } from './components/Hero'
+import { ServiceExplorer } from './components/ServiceExplorer'
+import { ApproachStrip } from './components/ApproachStrip'
+import { CloseSection } from './components/CloseSection'
+import { Footer } from './components/Footer'
+import { ScopeReviewDialog } from './components/ScopeReviewDialog'
 
 function App() {
+  const { discipline, set: setDiscipline } = useDisciplineFilter()
+  const [dialogOpen, setDialogOpen] = useState(false)
+  const [selectedService, setSelectedService] = useState<Service | null>(content.services[0])
+  const returnFocusRef = useRef<HTMLElement | null>(null)
+
+  const handleOpenDialog = useCallback((event?: React.MouseEvent<HTMLElement>) => {
+    if (event?.currentTarget instanceof HTMLElement) {
+      returnFocusRef.current = event.currentTarget
+    }
+    setDialogOpen(true)
+  }, [])
+
+  const handleCloseDialog = useCallback(() => {
+    setDialogOpen(false)
+    const target = returnFocusRef.current
+    if (target && target.isConnected) {
+      requestAnimationFrame(() => target.focus())
+    }
+  }, [])
+
   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>
+      <Header onOpenDialog={handleOpenDialog} />
 
       <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>
-        </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>
-            ))}
-          </div>
-        </section>
+        <Hero />
+        <ServiceExplorer
+          discipline={discipline}
+          setDiscipline={setDiscipline}
+          selectedService={selectedService}
+          setSelectedService={setSelectedService}
+        />
+        <ApproachStrip />
+        <CloseSection onOpenDialog={handleOpenDialog} />
       </main>
+
+      <Footer />
+      <ScopeReviewDialog isOpen={dialogOpen} onClose={handleCloseDialog} />
     </div>
   )
 }

--- baseline/src/components/ApproachStrip.tsx
+++ candidate/src/components/ApproachStrip.tsx
@@ -0,0 +1,21 @@
+import { content } from '../content'
+
+export function ApproachStrip() {
+  return (
+    <section id="approach" className="approach" aria-label="Working method">
+      <div className="section-intro">
+        <p className="eyebrow">{content.approach.eyebrow}</p>
+        <h2>{content.approach.title}</h2>
+      </div>
+      <ol className="principle-list">
+        {content.approach.principles.map((p) => (
+          <li key={p.number} className="principle">
+            <span className="principle-number">{p.number}</span>
+            <h3>{p.title}</h3>
+            <p>{p.body}</p>
+          </li>
+        ))}
+      </ol>
+    </section>
+  )
+}

--- baseline/src/components/CloseSection.tsx
+++ candidate/src/components/CloseSection.tsx
@@ -0,0 +1,21 @@
+import { content } from '../content'
+
+export function CloseSection({ onOpenDialog }: { onOpenDialog: () => void }) {
+  return (
+    <section className="close" aria-label="Next steps">
+      <div className="close-inner">
+        <p className="eyebrow">{content.close.eyebrow}</p>
+        <h2>{content.close.title}</h2>
+        <p className="close-body">{content.close.body}</p>
+        <button
+          type="button"
+          className="button-primary"
+          data-testid="scope-review-open"
+          onClick={onOpenDialog}
+        >
+          {content.hero.primaryAction}
+        </button>
+      </div>
+    </section>
+  )
+}

--- baseline/src/components/Footer.tsx
+++ candidate/src/components/Footer.tsx
@@ -0,0 +1,11 @@
+import { content } from '../content'
+
+export function Footer() {
+  return (
+    <footer className="site-footer">
+      <p>{content.footer.line}</p>
+      <p>{content.footer.region}</p>
+      <p className="footer-note">{content.footer.note}</p>
+    </footer>
+  )
+}

--- baseline/src/components/Header.tsx
+++ candidate/src/components/Header.tsx
@@ -0,0 +1,88 @@
+import { useId, useState } from 'react'
+import { motion, AnimatePresence } from 'motion/react'
+import { content } from '../content'
+
+export function Header({ onOpenDialog }: { onOpenDialog: () => void }) {
+  const [open, setOpen] = useState(false)
+  const menuId = useId()
+
+  return (
+    <header className="site-header">
+      <div className="header-inner">
+        <a className="brand" href="#top" aria-label="Gridline Field Services home">
+          <strong>{content.brand}</strong>
+          <span>{content.descriptor}</span>
+        </a>
+
+        <nav className="desktop-nav" aria-label="Primary navigation">
+          {content.nav.map((item) => (
+            <a key={item.href} href={item.href}>
+              {item.label}
+            </a>
+          ))}
+        </nav>
+
+        <div className="header-actions">
+          <button
+            type="button"
+            className="button-primary"
+            data-testid="scope-review-open"
+            onClick={onOpenDialog}
+          >
+            {content.hero.primaryAction}
+          </button>
+          <button
+            type="button"
+            className="mobile-menu-toggle"
+            aria-label={open ? 'Close menu' : 'Open menu'}
+            aria-expanded={open}
+            aria-controls={menuId}
+            data-testid="mobile-menu-toggle"
+            onClick={() => setOpen((v) => !v)}
+          >
+            <span aria-hidden="true" className="menu-icon">
+              {open ? '×' : '☰'}
+            </span>
+          </button>
+        </div>
+      </div>
+
+      <AnimatePresence initial={false}>
+        {open && (
+          <motion.div
+            id={menuId}
+            className="mobile-menu"
+            data-testid="mobile-menu"
+            initial={{ height: 0, opacity: 0 }}
+            animate={{ height: 'auto', opacity: 1 }}
+            exit={{ height: 0, opacity: 0 }}
+            transition={{ duration: 0.2, ease: 'easeOut' }}
+          >
+            <nav aria-label="Mobile navigation">
+              {content.nav.map((item) => (
+                <a
+                  key={item.href}
+                  href={item.href}
+                  onClick={() => setOpen(false)}
+                >
+                  {item.label}
+                </a>
+              ))}
+              <button
+                type="button"
+                className="button-primary"
+                data-testid="scope-review-open"
+                onClick={() => {
+                  setOpen(false)
+                  onOpenDialog()
+                }}
+              >
+                {content.hero.primaryAction}
+              </button>
+            </nav>
+          </motion.div>
+        )}
+      </AnimatePresence>
+    </header>
+  )
+}

--- baseline/src/components/Hero.tsx
+++ candidate/src/components/Hero.tsx
@@ -0,0 +1,33 @@
+import { content } from '../content'
+
+export function Hero() {
+  return (
+    <section className="hero" aria-label="Introduction">
+      <div className="hero-copy">
+        <p className="eyebrow">{content.hero.eyebrow}</p>
+        <h1>{content.hero.title}</h1>
+        <p className="hero-body">{content.hero.body}</p>
+        <div className="hero-actions">
+          <a href="#services" className="button-secondary">
+            {content.hero.secondaryAction}
+          </a>
+          <p className="hero-availability">{content.hero.availability}</p>
+        </div>
+      </div>
+      <div className="hero-graphic" aria-hidden="true">
+        <img
+          src="/assets/controls.svg"
+          alt=""
+          width="1200"
+          height="800"
+          loading="eager"
+        />
+        <div className="hero-coordinates">
+          <span>37.8°S</span>
+          <span>144.9°E</span>
+          <span>VIC</span>
+        </div>
+      </div>
+    </section>
+  )
+}

--- baseline/src/components/ScopeReviewDialog.tsx
+++ candidate/src/components/ScopeReviewDialog.tsx
@@ -0,0 +1,260 @@
+import { useEffect, useId, useRef, useState } from 'react'
+import { motion, AnimatePresence } from 'motion/react'
+import { content } from '../content'
+import { useFocusTrap } from '../hooks/useFocusTrap'
+
+interface FormErrors {
+  name?: string
+  email?: string
+  projectType?: string
+  summary?: string
+}
+
+interface ScopeReviewDialogProps {
+  isOpen: boolean
+  onClose: () => void
+}
+
+function validateEmail(value: string): boolean {
+  // Practical, forgiving RFC-ish check.
+  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())
+}
+
+function validateSummary(value: string): boolean {
+  return value.replace(/\s/g, '').length >= 20
+}
+
+export function ScopeReviewDialog({ isOpen, onClose }: ScopeReviewDialogProps) {
+  const dialogRef = useFocusTrap(isOpen)
+  const titleId = useId()
+  const descId = useId()
+  const formRef = useRef<HTMLFormElement>(null)
+
+  const [name, setName] = useState('')
+  const [email, setEmail] = useState('')
+  const [projectType, setProjectType] = useState('')
+  const [summary, setSummary] = useState('')
+  const [touched, setTouched] = useState<Record<string, boolean>>({})
+  const [errors, setErrors] = useState<FormErrors>({})
+  const [success, setSuccess] = useState(false)
+
+  useEffect(() => {
+    if (!isOpen) return
+    setSuccess(false)
+    setTouched({})
+    setName('')
+    setEmail('')
+    setProjectType('')
+    setSummary('')
+  }, [isOpen])
+
+  useEffect(() => {
+    const original = document.body.style.overflow
+    if (isOpen) {
+      document.body.style.overflow = 'hidden'
+    }
+    return () => {
+      document.body.style.overflow = original
+    }
+  }, [isOpen])
+
+  useEffect(() => {
+    function handleEscape(event: KeyboardEvent) {
+      if (event.key === 'Escape' && isOpen) {
+        onClose()
+      }
+    }
+    document.addEventListener('keydown', handleEscape)
+    return () => document.removeEventListener('keydown', handleEscape)
+  }, [isOpen, onClose])
+
+  function validate(values: { name: string; email: string; projectType: string; summary: string }): FormErrors {
+    const next: FormErrors = {}
+    if (!values.name.trim()) next.name = 'Name is required'
+    if (!values.email.trim()) {
+      next.email = 'Work email is required'
+    } else if (!validateEmail(values.email)) {
+      next.email = 'Enter a valid email address'
+    }
+    if (!values.projectType) next.projectType = 'Project type is required'
+    if (!values.summary.trim()) {
+      next.summary = 'Project summary is required'
+    } else if (!validateSummary(values.summary)) {
+      next.summary = 'Project summary must be at least 20 characters'
+    }
+    return next
+  }
+
+  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
+    event.preventDefault()
+    const values = { name, email, projectType, summary }
+    const nextErrors = validate(values)
+    setErrors(nextErrors)
+    setTouched({ name: true, email: true, projectType: true, summary: true })
+
+    if (Object.keys(nextErrors).length === 0) {
+      setSuccess(true)
+    }
+  }
+
+  function handleBlur(field: string) {
+    setTouched((prev) => ({ ...prev, [field]: true }))
+    const values = { name, email, projectType, summary }
+    setErrors(validate(values))
+  }
+
+  const fieldError = (field: keyof FormErrors): string | undefined => {
+    return touched[field] ? errors[field] : undefined
+  }
+
+  return (
+    <AnimatePresence>
+      {isOpen && (
+        <div
+          className="dialog-backdrop"
+          role="presentation"
+          onClick={(e) => {
+            if (e.target === e.currentTarget) onClose()
+          }}
+        >
+          <motion.div
+            ref={dialogRef}
+            className="dialog"
+            role="dialog"
+            aria-modal="true"
+            aria-labelledby={titleId}
+            aria-describedby={descId}
+            data-testid="scope-review-dialog"
+            initial={{ opacity: 0, y: 24 }}
+            animate={{ opacity: 1, y: 0 }}
+            exit={{ opacity: 0, y: 16 }}
+            transition={{ duration: 0.2 }}
+          >
+            <div className="dialog-header">
+              <div>
+                <h2 id={titleId}>{content.form.title}</h2>
+                <p id={descId} className="dialog-intro">
+                  {content.form.intro}
+                </p>
+              </div>
+              <button
+                type="button"
+                className="dialog-close"
+                aria-label="Close dialog"
+                onClick={onClose}
+              >
+                ×
+              </button>
+            </div>
+
+            {success ? (
+              <div
+                className="success-message"
+                data-testid="scope-review-success"
+                role="status"
+                aria-live="polite"
+              >
+                {content.form.success}
+              </div>
+            ) : (
+              <form ref={formRef} data-testid="scope-review-form" onSubmit={handleSubmit} noValidate>
+                <div className="field">
+                  <label htmlFor="scope-name">Name</label>
+                  <input
+                    id="scope-name"
+                    type="text"
+                    name="name"
+                    value={name}
+                    onChange={(e) => setName(e.target.value)}
+                    onBlur={() => handleBlur('name')}
+                    aria-invalid={fieldError('name') ? 'true' : undefined}
+                    aria-describedby={fieldError('name') ? 'error-name' : undefined}
+                  />
+                  {fieldError('name') && (
+                    <span className="field-error" id="error-name" data-testid="error-name">
+                      {fieldError('name')}
+                    </span>
+                  )}
+                </div>
+
+                <div className="field">
+                  <label htmlFor="scope-email">Work email</label>
+                  <input
+                    id="scope-email"
+                    type="email"
+                    name="email"
+                    value={email}
+                    onChange={(e) => setEmail(e.target.value)}
+                    onBlur={() => handleBlur('email')}
+                    aria-invalid={fieldError('email') ? 'true' : undefined}
+                    aria-describedby={fieldError('email') ? 'error-email' : undefined}
+                  />
+                  {fieldError('email') && (
+                    <span className="field-error" id="error-email" data-testid="error-email">
+                      {fieldError('email')}
+                    </span>
+                  )}
+                </div>
+
+                <div className="field">
+                  <label htmlFor="scope-projectType">Project type</label>
+                  <select
+                    id="scope-projectType"
+                    name="projectType"
+                    value={projectType}
+                    onChange={(e) => setProjectType(e.target.value)}
+                    onBlur={() => handleBlur('projectType')}
+                    aria-invalid={fieldError('projectType') ? 'true' : undefined}
+                    aria-describedby={fieldError('projectType') ? 'error-projectType' : undefined}
+                  >
+                    <option value="" disabled>
+                      Select a project type
+                    </option>
+                    {content.form.projectTypes.map((type) => (
+                      <option key={type} value={type}>
+                        {type}
+                      </option>
+                    ))}
+                  </select>
+                  {fieldError('projectType') && (
+                    <span className="field-error" id="error-projectType" data-testid="error-projectType">
+                      {fieldError('projectType')}
+                    </span>
+                  )}
+                </div>
+
+                <div className="field">
+                  <label htmlFor="scope-summary">Project summary</label>
+                  <textarea
+                    id="scope-summary"
+                    name="summary"
+                    rows={5}
+                    value={summary}
+                    onChange={(e) => setSummary(e.target.value)}
+                    onBlur={() => handleBlur('summary')}
+                    aria-invalid={fieldError('summary') ? 'true' : undefined}
+                    aria-describedby={fieldError('summary') ? 'error-summary' : undefined}
+                  />
+                  {fieldError('summary') && (
+                    <span className="field-error" id="error-summary" data-testid="error-summary">
+                      {fieldError('summary')}
+                    </span>
+                  )}
+                </div>
+
+                <div className="form-actions">
+                  <button type="submit" className="button-primary">
+                    {content.form.submit}
+                  </button>
+                  <button type="button" className="button-text" onClick={onClose}>
+                    Cancel
+                  </button>
+                </div>
+              </form>
+            )}
+          </motion.div>
+        </div>
+      )}
+    </AnimatePresence>
+  )
+}

--- baseline/src/components/ServiceExplorer.tsx
+++ candidate/src/components/ServiceExplorer.tsx
@@ -0,0 +1,144 @@
+import { useEffect, useMemo } from 'react'
+import { motion, AnimatePresence } from 'motion/react'
+import { content, Service, Discipline } from '../content'
+
+interface ServiceExplorerProps {
+  discipline: Discipline
+  setDiscipline: (value: Discipline) => void
+  selectedService: Service | null
+  setSelectedService: (service: Service | null) => void
+}
+
+export function ServiceExplorer({
+  discipline,
+  setDiscipline,
+  selectedService,
+  setSelectedService,
+}: ServiceExplorerProps) {
+  const services = useMemo(() => {
+    return discipline === 'all'
+      ? content.services
+      : content.services.filter((s) => s.discipline === discipline)
+  }, [discipline])
+
+  // Keep a selected service that is visible; default to first visible on filter change.
+  useEffect(() => {
+    if (services.length === 0) {
+      setSelectedService(null)
+      return
+    }
+    if (!selectedService || !services.find((s) => s.id === selectedService.id)) {
+      setSelectedService(services[0])
+    }
+  }, [services, selectedService, setSelectedService])
+
+  const detail = selectedService && services.find((s) => s.id === selectedService.id)
+    ? selectedService
+    : services[0]
+
+  return (
+    <section id="services" className="services">
+      <div className="section-intro">
+        <p className="eyebrow">{content.servicesIntro.eyebrow}</p>
+        <h2>{content.servicesIntro.title}</h2>
+        <p className="section-body">{content.servicesIntro.body}</p>
+      </div>
+
+      <div className="service-toolbar">
+        <div className="filter-group" role="group" aria-label="Filter services by discipline">
+          {content.filters.map((filter) => (
+            <button
+              key={filter.value}
+              type="button"
+              className={`filter-button ${discipline === filter.value ? 'is-active' : ''}`}
+              data-testid={`filter-${filter.value}`}
+              aria-pressed={discipline === filter.value}
+              onClick={() => setDiscipline(filter.value)}
+            >
+              {filter.label}
+            </button>
+          ))}
+        </div>
+        <p className="result-count" aria-live="polite" data-testid="service-count">
+          {services.length} {services.length === 1 ? 'result' : 'results'}
+        </p>
+      </div>
+
+      <div className="service-layout">
+        <div className="service-grid" role="list">
+          <AnimatePresence mode="popLayout">
+            {services.map((service) => (
+              <motion.article
+                layout
+                key={service.id}
+                role="listitem"
+                className={`service-card ${detail?.id === service.id ? 'is-selected' : ''}`}
+                data-testid={`service-card-${service.id}`}
+                initial={{ opacity: 0, y: 12 }}
+                animate={{ opacity: 1, y: 0 }}
+                exit={{ opacity: 0, scale: 0.98 }}
+                transition={{ duration: 0.25 }}
+              >
+                <button
+                  type="button"
+                  className="service-card-button"
+                  aria-pressed={detail?.id === service.id}
+                  onClick={() => setSelectedService(service)}
+                >
+                  <span className="sr-only">Inspect {service.title}</span>
+                  <span className="service-media" aria-hidden="true">
+                    {service.image ? (
+                      <img src={service.image} alt="" width="1200" height="800" loading="lazy" />
+                    ) : (
+                      <span className="media-fallback" data-testid="media-fallback">
+                        <span className="fallback-label">No field image supplied</span>
+                        <span className="fallback-id">{service.id.replace(/-/g, ' ')}</span>
+                      </span>
+                    )}
+                  </span>
+                  <span className="service-meta">
+                    <span className="discipline-pill">{service.discipline}</span>
+                    <span className="service-title">{service.title}</span>
+                    <span className="service-summary">{service.summary}</span>
+                  </span>
+                </button>
+              </motion.article>
+            ))}
+          </AnimatePresence>
+        </div>
+
+        {detail && (
+          <div
+            className="service-detail"
+            data-testid="service-detail"
+            aria-live="polite"
+            aria-label={`${detail.title} detail`}
+          >
+            <AnimatePresence mode="wait" initial={false}>
+              <motion.div
+                key={detail.id}
+                initial={{ opacity: 0, x: 16 }}
+                animate={{ opacity: 1, x: 0 }}
+                exit={{ opacity: 0, x: -16 }}
+                transition={{ duration: 0.2 }}
+              >
+                <p className="eyebrow">Work package detail</p>
+                <h3>{detail.title}</h3>
+                <p>{detail.description}</p>
+                <h4>Deliverables</h4>
+                <ul>
+                  {detail.deliverables.map((item) => (
+                    <li key={item}>{item}</li>
+                  ))}
+                </ul>
+                <p className="response-note">
+                  <strong>Response note:</strong> {detail.responseNote}
+                </p>
+              </motion.div>
+            </AnimatePresence>
+          </div>
+        )}
+      </div>
+    </section>
+  )
+}

--- baseline/src/hooks/useDisciplineFilter.ts
+++ candidate/src/hooks/useDisciplineFilter.ts
@@ -0,0 +1,40 @@
+import { useCallback, useEffect, useState } from 'react'
+import { content } from '../content'
+
+type DisciplineOption = typeof content.filters[number]['value']
+
+const SUPPORTED = content.filters.map((f) => f.value)
+
+function readDiscipline(): DisciplineOption {
+  if (typeof window === 'undefined') return 'all'
+  const params = new URLSearchParams(window.location.search)
+  const value = params.get('discipline')
+  return SUPPORTED.includes(value as DisciplineOption) ? (value as DisciplineOption) : 'all'
+}
+
+export function useDisciplineFilter() {
+  const [discipline, setDiscipline] = useState<DisciplineOption>(readDiscipline)
+
+  useEffect(() => {
+    const handlePopState = () => {
+      setDiscipline(readDiscipline())
+    }
+    window.addEventListener('popstate', handlePopState)
+    return () => window.removeEventListener('popstate', handlePopState)
+  }, [])
+
+  const set = useCallback((next: DisciplineOption) => {
+    const params = new URLSearchParams(window.location.search)
+    if (next === 'all') {
+      params.delete('discipline')
+    } else {
+      params.set('discipline', next)
+    }
+    const query = params.toString()
+    const url = query ? `${window.location.pathname}?${query}` : window.location.pathname
+    window.history.pushState(null, '', url)
+    setDiscipline(next)
+  }, [])
+
+  return { discipline, set }
+}

--- baseline/src/hooks/useFocusTrap.ts
+++ candidate/src/hooks/useFocusTrap.ts
@@ -0,0 +1,51 @@
+import { useEffect, useRef } from 'react'
+
+export function useFocusTrap(active: boolean) {
+  const containerRef = useRef<HTMLDivElement>(null)
+
+  useEffect(() => {
+    if (!active || !containerRef.current) return
+
+    const container = containerRef.current
+    const focusableSelectors = [
+      'button:not([disabled])',
+      'a[href]',
+      'input:not([disabled])',
+      'select:not([disabled])',
+      'textarea:not([disabled])',
+      '[tabindex]:not([tabindex="-1"])',
+    ].join(', ')
+
+    const getFocusable = () => Array.from(container.querySelectorAll<HTMLElement>(focusableSelectors))
+
+    const first = getFocusable()[0]
+    if (first) {
+      // Delay so the dialog is rendered and screen readers announce it
+      const id = requestAnimationFrame(() => first.focus())
+      return () => cancelAnimationFrame(id)
+    }
+
+    function handleKeyDown(event: KeyboardEvent) {
+      if (event.key !== 'Tab' || !container) return
+      const focusable = getFocusable()
+      if (focusable.length === 0) {
+        event.preventDefault()
+        return
+      }
+      const firstEl = focusable[0]
+      const lastEl = focusable[focusable.length - 1]
+      if (event.shiftKey && document.activeElement === firstEl) {
+        event.preventDefault()
+        lastEl.focus()
+      } else if (!event.shiftKey && document.activeElement === lastEl) {
+        event.preventDefault()
+        firstEl.focus()
+      }
+    }
+
+    document.addEventListener('keydown', handleKeyDown)
+    return () => document.removeEventListener('keydown', handleKeyDown)
+  }, [active])
+
+  return containerRef
+}

--- baseline/src/styles.css
+++ candidate/src/styles.css
@@ -1,37 +1,865 @@
 :root {
-  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-  color: #18211f;
-  background: #f1efe7;
+  --bg: #f1efe7;
+  --bg-subtle: #eeeade;
+  --ink: #18211f;
+  --steel: #24312e;
+  --rule: #b7bbb4;
+  --rule-strong: #aeb4ae;
+  --citron: #d5ff43;
+  --surface: #ffffff;
+  --focus: #0b6e4f;
+
+  font-family:
+    ui-sans-serif,
+    system-ui,
+    -apple-system,
+    BlinkMacSystemFont,
+    "Segoe UI",
+    Roboto,
+    "Helvetica Neue",
+    Arial,
+    sans-serif;
+  color: var(--ink);
+  background: var(--bg);
   font-synthesis: none;
   text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  line-height: 1.5;
 }
 
 * { box-sizing: border-box; }
 html { scroll-behavior: smooth; }
-body { margin: 0; min-width: 320px; }
-button, input, select, textarea { font: inherit; }
+body {
+  margin: 0;
+  min-width: 320px;
+  background: var(--bg);
+  color: var(--ink);
+}
+
+button, input, select, textarea {
+  font: inherit;
+  color: 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; }
-
-@media (max-width: 760px) {
-  .site-header nav { display: none; }
-  .site-header > button { display: none; }
-  .service-grid { grid-template-columns: 1fr; }
+
+:focus-visible {
+  outline: 3px solid var(--focus);
+  outline-offset: 2px;
+  border-radius: 2px;
+}
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+.site-shell {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+main { flex: 1 0 auto; }
+
+/* Header */
+.site-header {
+  position: relative;
+  z-index: 100;
+  border-bottom: 1px solid var(--rule);
+  background: var(--bg);
+}
+
+.header-inner {
+  display: grid;
+  grid-template-columns: auto 1fr auto;
+  align-items: center;
+  gap: 1rem;
+  max-width: 90rem;
+  margin: 0 auto;
+  padding: 0.875rem 4vw;
+}
+
+.brand {
+  display: grid;
+  gap: 0.125rem;
+  text-decoration: none;
+  line-height: 1;
+}
+
+.brand strong {
+  font-size: 1.25rem;
+  letter-spacing: 0.04em;
+}
+
+.brand span {
+  font-size: 0.6875rem;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.desktop-nav {
+  display: flex;
+  gap: 1.75rem;
+  justify-self: center;
+  font-size: 0.9375rem;
+}
+
+.desktop-nav a {
+  text-decoration: none;
+  padding: 0.25rem 0;
+  border-bottom: 1px solid transparent;
+  transition: border-color 0.15s ease;
+}
+
+.desktop-nav a:hover {
+  border-bottom-color: var(--ink);
+}
+
+.header-actions {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+}
+
+.mobile-menu-toggle {
+  display: none;
+  align-items: center;
+  justify-content: center;
+  width: 2.5rem;
+  height: 2.5rem;
+  padding: 0;
+  border: 1px solid var(--rule);
+  background: transparent;
+  border-radius: 0;
+  cursor: pointer;
+  font-size: 1.25rem;
+  line-height: 1;
+}
+
+.menu-icon {
+  display: inline-block;
+  transform: translateY(-1px);
+}
+
+.mobile-menu {
+  overflow: hidden;
+  border-top: 1px solid var(--rule);
+  background: var(--bg-subtle);
+}
+
+.mobile-menu nav {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+  padding: 1rem 4vw 1.5rem;
+}
+
+.mobile-menu a,
+.mobile-menu button {
+  display: block;
+  width: 100%;
+  text-align: left;
+  padding: 0.75rem 0;
+  text-decoration: none;
+  background: none;
+  border: none;
+}
+
+.mobile-menu a {
+  border-bottom: 1px solid var(--rule);
+}
+
+/* Buttons */
+.button-primary,
+.button-secondary,
+.button-text {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 0.5rem;
+  padding: 0.625rem 1.25rem;
+  font-size: 0.9375rem;
+  font-weight: 500;
+  line-height: 1.4;
+  text-decoration: none;
+  border: 1px solid transparent;
+  cursor: pointer;
+  transition: transform 0.1s ease, background-color 0.15s ease, border-color 0.15s ease;
+}
+
+.button-primary {
+  background: var(--ink);
+  color: var(--bg);
+  border-color: var(--ink);
+}
+
+.button-primary:hover {
+  background: var(--steel);
+  border-color: var(--steel);
+}
+
+.button-secondary {
+  background: transparent;
+  color: var(--ink);
+  border-color: var(--rule-strong);
+}
+
+.button-secondary:hover {
+  border-color: var(--ink);
+}
+
+.button-text {
+  background: transparent;
+  color: var(--steel);
+  border-color: transparent;
+  padding-left: 0.5rem;
+  padding-right: 0.5rem;
+}
+
+.button-text:hover {
+  text-decoration: underline;
+}
+
+.button-primary:active,
+.button-secondary:active {
+  transform: translateY(1px);
+}
+
+/* Hero */
+.hero {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  gap: 2.5rem;
+  align-items: center;
+  max-width: 90rem;
+  margin: 0 auto;
+  padding: 3rem 4vw 4rem;
+}
+
+.hero-copy {
+  max-width: 60rem;
+}
+
+.eyebrow {
+  margin: 0 0 0.75rem;
+  font-size: 0.75rem;
+  font-weight: 600;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--steel);
+}
+
+.hero h1 {
+  margin: 0 0 1rem;
+  font-size: clamp(2.75rem, 10vw, 6.5rem);
+  line-height: 0.92;
+  letter-spacing: -0.05em;
+  max-width: 14ch;
+}
+
+.hero-body {
+  margin: 0 0 1.75rem;
+  font-size: clamp(1.05rem, 2.2vw, 1.25rem);
+  line-height: 1.55;
+  max-width: 46rem;
+}
+
+.hero-actions {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 1rem;
+}
+
+.hero-availability {
+  margin: 0;
+  font-size: 0.8125rem;
+  color: var(--steel);
+}
+
+.hero-graphic {
+  position: relative;
+  width: 100%;
+  max-width: 100%;
+}
+
+.hero-graphic img {
+  display: block;
+  width: 100%;
+  height: auto;
+  border: 1px solid var(--rule);
+}
+
+.hero-coordinates {
+  position: absolute;
+  right: 0.75rem;
+  bottom: 0.75rem;
+  display: flex;
+  gap: 0.75rem;
+  padding: 0.375rem 0.625rem;
+  background: rgba(241, 239, 231, 0.92);
+  border: 1px solid var(--rule);
+  font-size: 0.6875rem;
+  letter-spacing: 0.06em;
+  text-transform: uppercase;
+}
+
+/* Section shared */
+.section-intro {
+  max-width: 60rem;
+  margin-bottom: 2rem;
+}
+
+.section-intro h2 {
+  margin: 0 0 0.75rem;
+  font-size: clamp(2rem, 5vw, 3.5rem);
+  line-height: 1;
+  letter-spacing: -0.03em;
+}
+
+.section-body,
+.close-body {
+  margin: 0;
+  font-size: 1.05rem;
+  line-height: 1.55;
+  max-width: 44rem;
+}
+
+/* Services */
+.services {
+  max-width: 90rem;
+  margin: 0 auto;
+  padding: 4rem 4vw 5rem;
+  border-top: 1px solid var(--rule);
+}
+
+.service-toolbar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 1rem;
+  margin-bottom: 1.5rem;
+}
+
+.filter-group {
+  display: inline-flex;
+  flex-wrap: wrap;
+  border: 1px solid var(--rule-strong);
+  background: var(--bg-subtle);
+}
+
+.filter-button {
+  position: relative;
+  padding: 0.5rem 1rem;
+  font-size: 0.875rem;
+  font-weight: 500;
+  background: transparent;
+  border: none;
+  border-right: 1px solid var(--rule-strong);
+  cursor: pointer;
+  color: var(--steel);
+}
+
+.filter-button:last-child {
+  border-right: none;
+}
+
+.filter-button.is-active {
+  background: var(--ink);
+  color: var(--bg);
+}
+
+.filter-button:hover:not(.is-active) {
+  background: rgba(24, 33, 31, 0.06);
+}
+
+.result-count {
+  margin: 0;
+  font-size: 0.875rem;
+  color: var(--steel);
+}
+
+.service-layout {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  gap: 2rem;
+}
+
+.service-grid {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  gap: 1rem;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.service-card {
+  position: relative;
+  border: 1px solid var(--rule);
+  background: var(--surface);
+  transition: border-color 0.15s ease, box-shadow 0.15s ease;
+  min-width: 0;
+}
+
+.service-card.is-selected {
+  border-color: var(--ink);
+  box-shadow: 0 0 0 1px var(--ink);
+}
+
+.service-card-button {
+  display: grid;
+  width: 100%;
+  text-align: left;
+  padding: 0;
+  margin: 0;
+  background: none;
+  border: none;
+  cursor: pointer;
+}
+
+.service-media {
+  display: block;
+  aspect-ratio: 16 / 10;
+  overflow: hidden;
+  background: var(--bg-subtle);
+  border-bottom: 1px solid var(--rule);
+}
+
+.service-media img {
+  display: block;
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  transition: transform 0.3s ease;
+}
+
+.service-card-button:hover .service-media img,
+.service-card-button:focus-visible .service-media img {
+  transform: scale(1.03);
+}
+
+.media-fallback {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  background: repeating-linear-gradient(
+    45deg,
+    var(--bg-subtle),
+    var(--bg-subtle) 12px,
+    rgba(24, 33, 31, 0.04) 12px,
+    rgba(24, 33, 31, 0.04) 24px
+  );
+  text-align: center;
+  padding: 1rem;
+}
+
+.fallback-label {
+  font-size: 0.8125rem;
+  font-weight: 600;
+  color: var(--steel);
+}
+
+.fallback-id {
+  font-size: 0.75rem;
+  text-transform: uppercase;
+  letter-spacing: 0.06em;
+  color: var(--rule-strong);
+  margin-top: 0.25rem;
+}
+
+.service-meta {
+  display: grid;
+  gap: 0.5rem;
+  padding: 1rem;
+  min-width: 0;
+}
+
+.discipline-pill {
+  justify-self: start;
+  padding: 0.125rem 0.5rem;
+  font-size: 0.6875rem;
+  font-weight: 600;
+  letter-spacing: 0.08em;
+  text-transform: uppercase;
+  color: var(--steel);
+  background: var(--bg);
+  border: 1px solid var(--rule);
+}
+
+.service-title {
+  font-size: 1.05rem;
+  font-weight: 600;
+  line-height: 1.25;
+  color: var(--ink);
+  overflow-wrap: break-word;
+}
+
+.service-summary {
+  font-size: 0.9rem;
+  line-height: 1.45;
+  color: var(--steel);
+}
+
+/* Service detail */
+.service-detail {
+  border: 1px solid var(--rule);
+  padding: 1.5rem;
+  background: var(--surface);
+  align-self: start;
+  min-width: 0;
+}
+
+.service-detail h3 {
+  margin: 0.5rem 0 1rem;
+  font-size: clamp(1.5rem, 3vw, 2rem);
+  line-height: 1.1;
+  letter-spacing: -0.02em;
+  overflow-wrap: break-word;
+}
+
+.service-detail p,
+.service-detail ul {
+  font-size: 1rem;
+  line-height: 1.6;
+}
+
+.service-detail ul {
+  margin: 0.75rem 0 1.25rem;
+  padding-left: 1.25rem;
+}
+
+.service-detail li {
+  margin-bottom: 0.375rem;
+}
+
+.service-detail h4 {
+  margin: 1.5rem 0 0.5rem;
+  font-size: 0.8125rem;
+  font-weight: 700;
+  letter-spacing: 0.1em;
+  text-transform: uppercase;
+}
+
+.response-note {
+  padding: 1rem;
+  background: var(--bg);
+  border-left: 3px solid var(--citron);
+}
+
+.response-note strong {
+  color: var(--steel);
+}
+
+/* Approach */
+.approach {
+  max-width: 90rem;
+  margin: 0 auto;
+  padding: 4rem 4vw 5rem;
+  border-top: 1px solid var(--rule);
+}
+
+.principle-list {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  gap: 1.5rem;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+  counter-reset: principle;
+}
+
+.principle {
+  position: relative;
+  padding: 1.5rem;
+  background: var(--surface);
+  border: 1px solid var(--rule);
+  min-width: 0;
+}
+
+.principle-number {
+  position: absolute;
+  top: 1.25rem;
+  right: 1.25rem;
+  font-size: 0.75rem;
+  font-weight: 700;
+  letter-spacing: 0.1em;
+  color: var(--rule-strong);
+}
+
+.principle h3 {
+  margin: 0 0 0.5rem;
+  padding-right: 2.5rem;
+  font-size: 1.25rem;
+  line-height: 1.2;
+}
+
+.principle p {
+  margin: 0;
+  color: var(--steel);
+  line-height: 1.55;
+}
+
+/* Close */
+.close {
+  background: var(--steel);
+  color: var(--bg);
+}
+
+.close-inner {
+  max-width: 90rem;
+  margin: 0 auto;
+  padding: 4rem 4vw;
+}
+
+.close .eyebrow {
+  color: var(--citron);
+}
+
+.close h2 {
+  margin: 0 0 1rem;
+  font-size: clamp(2rem, 5vw, 3.5rem);
+  line-height: 1;
+  letter-spacing: -0.03em;
+  max-width: 20ch;
+}
+
+.close .button-primary {
+  margin-top: 1.25rem;
+  background: var(--citron);
+  color: var(--ink);
+  border-color: var(--citron);
+}
+
+.close .button-primary:hover {
+  background: #e0ff6b;
+}
+
+/* Footer */
+.site-footer {
+  border-top: 1px solid var(--rule);
+  padding: 1.5rem 4vw;
+  text-align: center;
+  font-size: 0.8125rem;
+  color: var(--steel);
+}
+
+.site-footer p {
+  margin: 0.25rem 0;
+}
+
+.footer-note {
+  color: var(--rule-strong);
+}
+
+/* Dialog */
+.dialog-backdrop {
+  position: fixed;
+  inset: 0;
+  z-index: 200;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 1rem;
+  background: rgba(24, 33, 31, 0.65);
+}
+
+.dialog {
+  width: 100%;
+  max-width: 32rem;
+  max-height: calc(100vh - 2rem);
+  overflow: auto;
+  background: var(--bg);
+  border: 1px solid var(--rule);
+  box-shadow: 0 24px 60px rgba(24, 33, 31, 0.25);
+}
+
+.dialog-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 1rem;
+  padding: 1.25rem 1.5rem 0.75rem;
+  border-bottom: 1px solid var(--rule);
+}
+
+.dialog-header h2 {
+  margin: 0 0 0.25rem;
+  font-size: 1.375rem;
+  letter-spacing: -0.02em;
+}
+
+.dialog-intro {
+  margin: 0;
+  font-size: 0.875rem;
+  color: var(--steel);
+}
+
+.dialog-close {
+  flex: 0 0 auto;
+  width: 2rem;
+  height: 2rem;
+  padding: 0;
+  border: 1px solid var(--rule);
+  background: transparent;
+  font-size: 1.5rem;
+  line-height: 1;
+  cursor: pointer;
+}
+
+.dialog form {
+  padding: 1.25rem 1.5rem 1.5rem;
+}
+
+.field {
+  margin-bottom: 1rem;
+}
+
+.field label {
+  display: block;
+  margin-bottom: 0.375rem;
+  font-size: 0.875rem;
+  font-weight: 500;
+}
+
+.field input,
+.field select,
+.field textarea {
+  width: 100%;
+  padding: 0.625rem 0.75rem;
+  border: 1px solid var(--rule-strong);
+  background: var(--surface);
+  border-radius: 0;
+}
+
+.field textarea {
+  resize: vertical;
+  min-height: 6rem;
+}
+
+.field input:focus,
+.field select:focus,
+.field textarea:focus {
+  border-color: var(--ink);
+  outline: none;
+}
+
+.field input[aria-invalid="true"],
+.field select[aria-invalid="true"],
+.field textarea[aria-invalid="true"] {
+  border-color: #a61b1b;
+}
+
+.field-error {
+  display: block;
+  margin-top: 0.375rem;
+  font-size: 0.8125rem;
+  color: #a61b1b;
+}
+
+.form-actions {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  margin-top: 1.5rem;
+}
+
+.success-message {
+  padding: 1.25rem 1.5rem 1.5rem;
+  font-size: 1rem;
+  line-height: 1.5;
+  border-left: 3px solid var(--citron);
+  background: var(--bg-subtle);
+  margin: 1.25rem 1.5rem 1.5rem;
+}
+
+/* Tablet */
+@media (min-width: 48rem) {
+  .hero {
+    grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
+    gap: 3rem;
+    padding-top: 4rem;
+    padding-bottom: 5rem;
+  }
+
+  .service-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .principle-list {
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+  }
+}
+
+/* Desktop */
+@media (min-width: 64rem) {
+  .hero h1 {
+    max-width: 12ch;
+  }
+
+  .service-layout {
+    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
+    align-items: start;
+    gap: 2.5rem;
+  }
+
+  .service-grid {
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+  }
+
+  .service-detail {
+    position: sticky;
+    top: 1rem;
+    padding: 2rem;
+  }
+}
+
+/* Wide desktop */
+@media (min-width: 90rem) {
+  .header-inner,
+  .hero,
+  .services,
+  .approach,
+  .close-inner {
+    padding-left: 6vw;
+    padding-right: 6vw;
+  }
+}
+
+/* Mobile menu toggle visibility */
+@media (max-width: 47.9375rem) {
+  .desktop-nav,
+  .header-actions > .button-primary {
+    display: none;
+  }
+
+  .mobile-menu-toggle {
+    display: inline-flex;
+  }
 }
 
+/* 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; }
+  *,
+  *::before,
+  *::after {
+    animation-duration: 0.01ms !important;
+    animation-iteration-count: 1 !important;
+    transition-duration: 0.01ms !important;
+    scroll-behavior: auto !important;
+  }
 }
