diff --git a/BUILD-NOTES.md b/BUILD-NOTES.md
index 8cb3c8e..0b517cd 100644
--- a/BUILD-NOTES.md
+++ b/BUILD-NOTES.md
@@ -653,3 +653,107 @@ source/thumbnail hashes and first-five live preservation. The integration remain
 retain the original design approval provenance and distinguish fresh final-source
 checks from reused unchanged-source evidence. No runtime, schema, billing, sandbox,
 launch, infrastructure or dependency change is included.
+
+## Accepted decision-oriented templates — Batch02 integration (Astra)
+
+This separate four-template adoption is based on the deployed PR41 merge
+`1b48644469a8a392263482cd31ceb8db8ac7ab2a`. The first eight redesigned HTML sources
+and their thumbnails remain byte-for-byte unchanged. Seven additional accepted
+technical/editorial designs are still queued; this PR does not finish the section.
+
+- **Meeting recap:** six owned action rows lead beside the complete decision minutes,
+  followed by the explicit deferral, open questions and meeting handover.
+- **Decision brief:** a compact recommendation above the complete five-criterion
+  option bench, then alternatives, explicit asks and the deadline.
+- **Spec:** scope pairing and ruled native requirement records, keeping every ID,
+  priority, acceptance clause and blocking question relationship.
+- **Research Brief:** the answer qualified by confidence and method, an evidence and
+  provenance register, grouped sources, answer-changing conditions and unknowns.
+
+Catalog descriptions reflect these compositions. The unchanged canonical thumbnail
+pipeline uses 1600px source viewports for Meeting recap and Decision brief (all six
+owned actions / all five comparison criteria visible), and 1440px for Spec and
+Research Brief (scope/requirements and answer/confidence/evidence visible). The
+actual gallery cards were reviewed at 1440px and 390px. No global capture hack,
+manual PNG edit, other thumbnail change, slug/category/slot or API contract change.
+The catalog stays at nineteen HTML examples plus the Markdown One-pager.
+
+### Exact accepted-source mapping
+
+| Template | Accepted SHA-256 | Canonical SHA-256 |
+| --- | --- | --- |
+| meeting-recap | `e8ff4f5203c169c60bd559eea0c8998bd4a629a5904e58c3d4915d2a4d1c8b28` | `be10b99a09fa0e589619bf5f2b07f3027722e0e9f8ad5c5e9e01c50416fee495` |
+| decision-brief | `18b8f4f8bf9eddc649e17de08bd932257f5da7163654a69cc1e3eded1b26eda6` | `77da9c704f717c821792d30f2276742f277d3a13f57977a43fa1040e6cc9d68f` |
+| spec | `1cc6c161b5e158ea6692f5b4219fa1f9e1077127e6272a2953a2a2df8e601451` | same, byte-for-byte |
+| research-brief | `2ba837d4421fffb84c551813e3462e7bc67c25c041e139f02677ec4175316945` | same, byte-for-byte |
+
+The only source differences are one approved `noRedundantRoles`-only compatibility
+comment at the beginning of Meeting recap and Decision brief. No CSS, body, title,
+tag, ID, scope, role, headers reference, text, data, code, link or SVG change. Both
+already have native scoped headers; there is no cell-tag correction in this batch.
+The accepted previews and the earlier public evidence remain immutable.
+
+These two responsive tables retain explicit table roles because their mobile CSS
+uses block/grid display and visually clips column headings. [MDN's table display
+accessibility warning](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/display#tables)
+provides the compatibility rationale. This adds only these two named files to the
+local exception list; it is not permission for other files, rules or global config.
+
+| Explicit role | Meeting recap | Decision brief |
+| --- | ---: | ---: |
+| table | 1 | 1 |
+| rowgroup | 2 | 2 |
+| row | 7 | 6 |
+| columnheader | 4 | 4 |
+| rowheader | 0 | 5 |
+| cell | 24 | 15 |
+
+The existing scoped unit test now bounds all four approved exception inventories.
+Its two new cases fail against the accepted pre-comment files and pass with the
+exact approved candidates. Existing browser tests are extended, not duplicated:
+the new cases traverse the real app sandbox and verify native `headers` IDs, scope,
+correct table/row/column membership, accessible names and phone overflow. Disposable
+DOM red controls must reject dangling IDs and existing-but-wrong row/column headers,
+then recover when original references are restored. No production data is involved.
+
+Eight accepted/candidate pairs at 1440/768/390/320 have equal PNG bytes and ARIA
+snapshots; their candidate hashes equal the final canonical hashes. Eight real
+local app sandbox measurements record table hierarchy and header associations with
+actual child-frame widths. Evidence is Chromium-only: not a Safari/Firefox or
+screen-reader navigation certification. Case-insensitive accessible-name comparison
+accounts only for visual CSS uppercase versus authored ARIA snapshot case; IDs,
+scopes, relationships and original names remain recorded without normalization.
+
+### Validation, provenance and warning disposition
+
+Independent content parity retains all 298 text nodes, 159 numeric occurrences,
+70 keyed groups and the link/code/ordinal relationships. Final source identity is
+proved by the exact allowed transform, not by screenshots alone. The earlier
+16 main plus 24 actual/prior breakpoint captures, 58 detailed semantic width checks,
+12 fixtures, no-JS/growth checks and actual gallery images remain valid evidence of
+unchanged rendered source, explicitly labelled as reused where appropriate. Final
+checks, build, complete unit/browser suites and final-head CI are reported separately.
+
+Accepted-source lint: **71 errors → 0** through only the two approved comments.
+Warnings: **31 → 37**, with all 31 baseline warnings unchanged. Meeting recap adds
+five specificity diagnostics (`caption b`, two `.open-section`, two `.opens`) and
+one mobile `.num` spacing `!important` warning. The caption and facts selectors
+address different elements; scoped meeting-grid rules intentionally outrank generic
+section/list fallbacks. The `!important` priority appears unnecessary in the current
+stylesheet, but no rendering/accessibility defect was observed. Accepted CSS is
+preserved rather than rewritten merely to quiet diagnostics; no warnings suppressed.
+
+[Final integrated review and source-bound evidence](https://anacreon.ai/downloads/aa-template-revamp-1788686289312/integrated-batch02/final/index.html)
+links to the immutable earlier approval/candidate evidence and separates fresh tests
+from unchanged-source reuse. This PR is **not merged or deployed** pending internal
+integrated review. No runtime, dependencies, schema, billing, sandbox, launch,
+credentials or infrastructure change is included.
+
+A separate **test-fixture-only** correction was necessary for reliable full-suite
+validation: the existing returning-reader analytics test used real time plus one
+hour while promising the same UTC day. It fails on unchanged main after 23:00 UTC,
+when the production privacy salt correctly rotates. This one test now fixes
+`Date.now()` at noon using Vitest's automatically restored spy. Its assertions,
+tomorrow/rotation coverage and all production analytics code remain unchanged.
+The unchanged-base failure and corrected fourteen-test pass are recorded in the
+review; no runtime clock, customer data, analytics behavior or financial change.
diff --git a/public/assets/template-thumbs/decision-brief.png b/public/assets/template-thumbs/decision-brief.png
index b93ffbc..fa492db 100644
Binary files a/public/assets/template-thumbs/decision-brief.png and b/public/assets/template-thumbs/decision-brief.png differ
diff --git a/public/assets/template-thumbs/meeting-recap.png b/public/assets/template-thumbs/meeting-recap.png
index 8f85dfd..081b850 100644
Binary files a/public/assets/template-thumbs/meeting-recap.png and b/public/assets/template-thumbs/meeting-recap.png differ
diff --git a/public/assets/template-thumbs/research-brief.png b/public/assets/template-thumbs/research-brief.png
index 842d803..b4332de 100644
Binary files a/public/assets/template-thumbs/research-brief.png and b/public/assets/template-thumbs/research-brief.png differ
diff --git a/public/assets/template-thumbs/spec.png b/public/assets/template-thumbs/spec.png
index 2a30099..886f594 100644
Binary files a/public/assets/template-thumbs/spec.png and b/public/assets/template-thumbs/spec.png differ
diff --git a/templates/decision-brief.html b/templates/decision-brief.html
index 6cb97e2..e70ea48 100644
--- a/templates/decision-brief.html
+++ b/templates/decision-brief.html
@@ -1,132 +1,19 @@
+<!-- biome-ignore-all lint/a11y/noRedundantRoles: Explicit table roles retain semantics when mobile CSS uses block/grid display. -->
 <!doctype html>
 <html lang="en">
 <head>
-<meta charset="utf-8" />
-<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
 <title>Decision brief — how we bill for overage from January</title>
-<!--
-  TEMPLATE: decision-brief  ·  CATEGORY: decisions
-  VOICE: humanist sans (system-ui stack), tight measure   SCHEME: near-white paper, deep amber accent
-  Rewriting this? Replace the content, keep the structure and the styling.
-  THE RULE THIS TEMPLATE EXISTS FOR: every option is scored on the SAME criteria, in the same
-  order, with real values. An option with a blank cell has not been evaluated — either fill it
-  or drop the option. Two to four options; past four this is a survey, not a decision.
-  DELETE, DON'T EMPTY: nothing to say against the alternatives? remove "Why not the others"
-  entirely, heading included. Never leave a heading with nothing under it.
--->
 <style>
-:root{
-  --bg:#faf9f7; --card:#fff; --tint:#f4f2ee;
-  --ink:#1c1a16; --muted:#55504a; --subtle:#6a645c;
-  --line:#e8e4de; --line-2:#d6d0c7;
-  --amber:#96560a; --amber-soft:#fdf1e0; --amber-line:#eed6ae;
-  --sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
-  --shadow:0 1px 2px rgb(28 26 22/.05),0 12px 32px -16px rgb(28 26 22/.3);
-}
-*,*::before,*::after{box-sizing:border-box}
-html{-webkit-text-size-adjust:100%}
-body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased}
-h1,h2,h3{margin:0;line-height:1.2;letter-spacing:-.015em}
-p,dl,dd{margin:0}
-ul,ol{margin:0;padding:0;list-style:none}
-.page{max-width:56rem;margin-inline:auto;padding:2.5rem 1.5rem 3rem}
-.measure{max-width:40rem}
+*,*::before,*::after{box-sizing:border-box}html{-webkit-text-size-adjust:100%}:root{--paper:#fcfaf5;--ink:#30281d;--muted:#665b49;--line:#c9beab;--light:#e1d9cc;--amber:#83500e;--tint:#f2e5c9;--sans:"Helvetica Neue",Helvetica,Arial,sans-serif;--serif:Georgia,"Times New Roman",serif;--mono:ui-monospace,Menlo,Consolas,"Liberation Mono",monospace}body{margin:0;padding:30px 32px 48px;background:var(--paper);color:var(--ink);font:16px/1.65 var(--sans);overflow-wrap:anywhere;-webkit-font-smoothing:antialiased}.page{max-width:1180px;margin:auto}h1,h2,h3,p,ol,ul{margin:0}ul,ol{padding:0;list-style:none}h1,h2,h3{line-height:1.25}.mast{display:grid;grid-template-columns:minmax(0,1fr) 315px;gap:0 46px;padding-bottom:24px;border-bottom:1px solid var(--line)}.eyebrow{grid-column:1;display:block;font-size:13px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--amber);margin-bottom:12px}.mast h1{grid-column:1;font:400 clamp(30px,3.2vw,39px)/1.16 var(--serif);letter-spacing:-.028em;max-width:30ch}.lead{grid-column:1;margin-top:16px;color:var(--muted);font-size:17px;line-height:1.6;max-width:72ch}.facts{grid-column:2;grid-row:1/4;align-self:end;display:flex;flex-direction:column;gap:14px;font-size:16px;line-height:1.6;color:var(--muted)}.facts b{display:block;font-size:14px;line-height:1.5;color:var(--ink);font-weight:700}.rec{display:grid;grid-template-columns:155px minmax(0,1fr);gap:22px;align-items:start;margin-top:26px}.badge{font:700 12px/1.5 var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--amber);padding:6px 10px;border:1px solid var(--amber);width:fit-content}.rec h2{font:600 21px/1.35 var(--sans);letter-spacing:-.025em}.rec p{margin-top:9px;max-width:84ch;font-size:16px;line-height:1.65;color:var(--muted)}.option-section{margin-top:28px}.sec{font:600 23px/1.3 var(--serif);letter-spacing:-.018em;margin-bottom:18px}.matrix{min-width:0}table{width:100%;border-collapse:collapse;table-layout:fixed}caption{text-align:left}caption .sec{font:600 19px/1.3 var(--sans);margin-bottom:18px;letter-spacing:-.015em}thead th{text-align:left;vertical-align:top;font-size:17px;line-height:1.45;font-weight:650;padding:17px 20px;background:#e9e4da;border-top:2px solid var(--ink);border-bottom:1px solid var(--line)}thead th:first-child{width:23%;font-size:14px;font-weight:600;color:var(--muted)}thead th.pick{background:var(--tint);border-top-color:var(--amber)}.tag{display:block;font:700 12px/1.5 var(--sans);text-transform:uppercase;letter-spacing:.07em;color:var(--amber);margin-top:8px}tbody th,tbody td{border-bottom:1px solid var(--light);padding:20px;vertical-align:top;text-align:left}tbody th{font-size:16px;line-height:1.6;font-weight:600;color:var(--muted)}tbody td{font:500 17px/1.6 var(--mono);font-variant-numeric:tabular-nums}tbody tr:first-child td{font-size:28px;font-weight:650;line-height:1.4;letter-spacing:-.04em}tbody td.pick{background:var(--tint)}.risk{display:flex;align-items:center;gap:10px}.risk b{font-size:18px;font-family:var(--sans);font-weight:650}.risk i{display:flex;gap:3px;flex:none}.risk i span{display:block;width:6px;height:14px;border:1px solid var(--muted)}.risk i span.on{background:var(--muted)}.risk-note{display:block;font:400 16px/1.5 var(--sans);color:var(--muted);margin-top:6px}.tnote{font-size:16px;line-height:1.65;color:var(--muted);padding:18px 0;border-bottom:1px solid var(--line);max-width:100ch}.rejections{margin-top:34px}.why{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:38px}.why h3{font-size:19px;font-weight:700;line-height:1.4}.why h3 span{font-weight:500}.why p{font-size:16px;line-height:1.65;color:var(--muted);margin-top:12px}.why p b{color:var(--ink);font-weight:650}.request-section{margin-top:34px;padding-top:24px;border-top:2px solid var(--ink)}.ask{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(0,1fr);gap:44px}.ask ol{list-style:decimal;padding-left:25px}.ask li{padding-left:9px;font-size:16px;line-height:1.65}.ask li+li{margin-top:14px}.ask li::marker{font:500 16px/1.5 var(--mono);color:var(--amber)}.deadline{align-self:start;display:flex;flex-direction:column;gap:10px;padding:20px 24px;background:var(--tint)}.deadline .k{font-size:14px;font-weight:600;color:var(--muted)}.deadline .v{font:400 30px/1.25 var(--serif);color:var(--amber)}.deadline .n{font-size:16px;line-height:1.65;color:var(--muted)}.colophon{margin-top:34px;padding-top:20px;border-top:1px solid var(--line);font-size:16px;line-height:1.65;color:var(--muted);max-width:100ch}a{color:var(--amber)}a:focus-visible{outline:3px solid var(--amber);outline-offset:4px}
+@media(max-width:900px){body{padding:26px 24px 40px}.mast{display:block}.facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;margin-top:22px}.facts b{margin-bottom:5px}.rec{grid-template-columns:135px minmax(0,1fr);gap:20px}thead th{padding:16px 12px;font-size:16px}tbody th,tbody td{padding:18px 12px}tbody td{font-size:16px}tbody tr:first-child td{font-size:23px}.why{gap:28px}.ask{gap:30px;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr)}}
+@media(max-width:640px){body{padding:22px 16px 36px}.mast{padding-bottom:22px}.mast h1{font-size:32px}.facts{display:flex;gap:12px;margin-top:20px}.facts b{display:inline;margin:0}.rec{display:block;margin-top:24px}.badge{display:block;margin-bottom:12px}.rec h2{font-size:21px}.rec p{margin-top:12px}.option-section{margin-top:26px}table,tbody{display:block}caption{display:block}thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}tbody tr{display:block;border-top:2px solid var(--line);padding:20px 0}tbody th{display:block;border:0;padding:0;font-size:18px;font-weight:700;color:var(--ink);margin-bottom:14px}tbody td{display:block;border:0;padding:12px 14px;font-size:17px}tbody td+td{margin-top:8px}tbody tr:first-child td{font-size:26px}tbody td::before{content:attr(data-label);display:block;font:600 14px/1.5 var(--sans);color:var(--muted);letter-spacing:0;margin-bottom:6px}tbody td.pick::before{color:var(--amber)}.tnote{padding:20px 0;border-top:1px solid var(--line)}.risk-note{font-size:16px}.why{grid-template-columns:1fr;gap:24px}.why li+li{border-top:1px solid var(--line);padding-top:24px}.rejections{margin-top:28px}.request-section{margin-top:30px}.ask{display:block}.deadline{margin-top:24px;padding:20px}.sec{font-size:24px}.colophon{margin-top:28px}}
 
-.eyebrow{display:inline-flex;align-items:center;gap:.5rem;color:var(--amber);font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}
-.eyebrow::before{content:"";width:1.25rem;height:.1875rem;background:var(--amber)}
-.mast h1{margin:.625rem 0 0;font-size:clamp(1.75rem,3.6vw,2.375rem);font-weight:700}
-.mast .lead{margin-top:.5rem;color:var(--muted);font-size:1.0625rem}
-.facts{display:flex;flex-wrap:wrap;gap:.375rem 1.25rem;margin-top:1.125rem;padding-top:.875rem;border-top:1px solid var(--line-2);font-size:.8125rem;color:var(--subtle)}
-.facts b{color:var(--muted);font-weight:600}
-
-/* recommendation */
-.rec{display:grid;grid-template-columns:auto minmax(0,1fr);gap:.75rem 1rem;align-items:start;margin-top:1.25rem;padding:1rem 1.125rem;background:var(--amber-soft);border:1px solid var(--amber-line);border-radius:.625rem}
-.rec{font-size:.9375rem}
-.rec .badge{padding:.125rem .5rem;background:var(--amber);color:#fff;border-radius:999px;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap}
-.rec h2{font-size:1.125rem;font-weight:700;color:#5c3403}
-.rec p{margin-top:.25rem;color:#6b3e04}
-
-h2.sec{margin-top:2.25rem;font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--subtle)}
-h2.sec+*{margin-top:.75rem}
-
-/* comparison matrix */
-.matrix{background:var(--card);border:1px solid var(--line-2);border-radius:.75rem;box-shadow:var(--shadow);overflow:hidden}
-table{width:100%;border-collapse:collapse;font-size:.9375rem}
-thead th{padding:.75rem .875rem;background:var(--tint);border-bottom:1px solid var(--line-2);text-align:left;vertical-align:top;font-size:.8125rem;font-weight:700;color:var(--ink)}
-thead th:first-child{font-size:.6875rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--subtle);width:9.5rem}
-thead th.pick{background:var(--amber-soft);border-bottom-color:var(--amber-line);color:#5c3403}
-thead th .tag{display:block;margin-top:.1875rem;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--amber)}
-tbody th{padding:.75rem .875rem;border-top:1px solid var(--line);text-align:left;vertical-align:top;font-weight:600;color:var(--muted);font-size:.875rem}
-tbody td{padding:.75rem .875rem;border-top:1px solid var(--line);vertical-align:top;color:var(--ink);font-variant-numeric:tabular-nums}
-tbody td.pick{background:var(--amber-soft)}
-tbody tr:first-child th,tbody tr:first-child td{border-top:0}
-.risk{display:flex;align-items:center;gap:.4375rem}
-.risk i{display:flex;gap:.125rem;flex:none}
-.risk i span{width:.375rem;height:.75rem;border-radius:1px;box-shadow:inset 0 0 0 1px #b8b0a2}
-.risk i span.on{background:var(--muted);box-shadow:none}
-.risk b{font-weight:650}
-.risk-note{display:block;margin-top:.1875rem;color:var(--subtle);font-size:.8125rem}
-.tnote{padding:.75rem .875rem;border-top:1px solid var(--line);background:var(--tint);color:var(--subtle);font-size:.8125rem}
-
-/* why not */
-.why li{padding-block:1rem;border-top:1px solid var(--line-2);font-size:.9375rem}
-.why li:first-child{border-top:0;padding-top:.25rem}
-.why h3{font-size:1rem;font-weight:650}
-.why h3 span{color:var(--subtle);font-weight:600}
-.why p{margin-top:.25rem;color:var(--muted);max-width:42rem}
-.why p b{color:var(--ink);font-weight:650}
-
-/* the ask */
-.ask{padding:1.125rem 1.25rem;background:var(--card);border:1px solid var(--line-2);border-radius:.75rem;box-shadow:var(--shadow)}
-.ask ol{counter-reset:a}
-.ask li{position:relative;counter-increment:a;padding-block:.5rem .5rem;padding-left:1.75rem;color:var(--ink);font-size:.9375rem}
-.ask li+li{border-top:1px solid var(--line)}
-.ask li::before{content:counter(a);position:absolute;left:0;top:.5rem;color:var(--amber);font-size:.8125rem;font-weight:700;font-variant-numeric:tabular-nums}
-.deadline{display:flex;flex-wrap:wrap;align-items:baseline;gap:.375rem .75rem;margin-top:.875rem;padding-top:.875rem;border-top:1px solid var(--line-2)}
-.deadline .k{color:var(--subtle);font-size:.6875rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase}
-.deadline .v{color:var(--amber);font-size:1.25rem;font-weight:700;letter-spacing:-.02em}
-.deadline .n{color:var(--muted);font-size:.875rem;flex:1 1 16rem}
-
-.colophon{margin-top:2rem;padding-top:1rem;border-top:1px solid var(--line-2);color:var(--subtle);font-size:.8125rem}
-
-@media (max-width:34rem){
-  .page{padding:1.875rem 1rem 2rem}
-
-  .mast .lead{font-size:1rem}
-  .rec{grid-template-columns:minmax(0,1fr);gap:.5rem}
-  .rec .badge{justify-self:start}
-  .matrix{border-radius:.625rem}
-  table,tbody{display:block}
-  caption{display:block}
-  thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
-  tbody tr{display:block;border-top:1px solid var(--line-2)}
-  tbody tr:first-child{border-top:0}
-  tbody th,tbody td{display:block;padding:0;border-top:0}
-  tbody th{color:var(--ink);font-size:.9375rem;font-weight:700}
-  tbody td{border-radius:.3125rem;font-size:.9375rem}
-  tbody td::before{content:attr(data-label);display:block;color:var(--subtle);font-size:.6875rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
-  tbody td.pick::before{color:var(--amber)}
-  .ask{padding:1rem}
-  /* height tightening — spacing only, no content removed */
-  .facts{margin-top:1rem;padding-top:.75rem}
-  .rec{margin-top:1rem;padding:.875rem 1rem}
-  h2.sec{margin-top:1.375rem}
-  h2.sec+*{margin-top:.625rem}
-  tbody tr{padding:.625rem .875rem}
-  tbody th{margin-bottom:.3125rem}
-  tbody td{padding:.25rem .5rem}
-  tbody td+td{margin-top:.1875rem}
-  .tnote{padding:.625rem .875rem}
-  .why li{padding-block:.875rem}
-  .ask li{padding-block:.4375rem .4375rem;padding-left:1.75rem}
-  .ask li::before{top:.4375rem}
-  .deadline{margin-top:.75rem;padding-top:.75rem}
-  .colophon{margin-top:1.25rem;padding-top:.875rem}
-}
 </style>
 </head>
 <body>
-<div class="page">
+<main class="page">
 
   <header class="mast">
     <span class="eyebrow">Decision brief</span>
@@ -147,56 +34,56 @@ tbody tr:first-child th,tbody tr:first-child td{border-top:0}
     </div>
   </div>
 
-  <section>
-    <h2 class="sec">The options, on the same criteria</h2>
+  <section class="option-section">
     <div class="matrix">
-      <table>
-        <thead>
-          <tr>
-            <th scope="col">Criterion</th>
-            <th scope="col">A · Hard cap with upgrade prompt</th>
-            <th scope="col" class="pick">B · Metered overage<span class="tag">Recommended</span></th>
-            <th scope="col">C · Soft cap with 7-day grace</th>
+      <table role="table" aria-describedby="model-note">
+        <caption><h2 class="sec">The options, on the same criteria</h2>
+    </caption><thead role="rowgroup">
+          <tr role="row">
+            <th scope="col" id="option-col-0" role="columnheader">Criterion</th>
+            <th scope="col" id="option-col-1" role="columnheader">A · Hard cap with upgrade prompt</th>
+            <th scope="col" class="pick" id="option-col-2" role="columnheader">B · Metered overage<span class="tag">Recommended</span></th>
+            <th scope="col" id="option-col-3" role="columnheader">C · Soft cap with 7-day grace</th>
           </tr>
         </thead>
-        <tbody>
-          <tr>
-            <th scope="row">Modelled revenue, FY27</th>
-            <td data-label="A · Hard cap">+$180k</td>
-            <td class="pick" data-label="B · Metered overage">+$310k</td>
-            <td data-label="C · Soft cap">+$240k</td>
+        <tbody role="rowgroup">
+          <tr role="row">
+            <th scope="row" id="criterion-0" role="rowheader">Modelled revenue, FY27</th>
+            <td data-label="A · Hard cap" headers="criterion-0 option-col-1" role="cell">+$180k</td>
+            <td class="pick" data-label="B · Metered overage · Recommended" headers="criterion-0 option-col-2" role="cell">+$310k</td>
+            <td data-label="C · Soft cap" headers="criterion-0 option-col-3" role="cell">+$240k</td>
           </tr>
-          <tr>
-            <th scope="row">Support load</th>
-            <td data-label="A · Hard cap">+40 tickets / month</td>
-            <td class="pick" data-label="B · Metered overage">+12 tickets / month</td>
-            <td data-label="C · Soft cap">+25 tickets / month</td>
+          <tr role="row">
+            <th scope="row" id="criterion-1" role="rowheader">Support load</th>
+            <td data-label="A · Hard cap" headers="criterion-1 option-col-1" role="cell">+40 tickets / month</td>
+            <td class="pick" data-label="B · Metered overage · Recommended" headers="criterion-1 option-col-2" role="cell">+12 tickets / month</td>
+            <td data-label="C · Soft cap" headers="criterion-1 option-col-3" role="cell">+25 tickets / month</td>
           </tr>
-          <tr>
-            <th scope="row">Time to ship</th>
-            <td data-label="A · Hard cap">3 weeks</td>
-            <td class="pick" data-label="B · Metered overage">5 weeks</td>
-            <td data-label="C · Soft cap">4 weeks</td>
+          <tr role="row">
+            <th scope="row" id="criterion-2" role="rowheader">Time to ship</th>
+            <td data-label="A · Hard cap" headers="criterion-2 option-col-1" role="cell">3 weeks</td>
+            <td class="pick" data-label="B · Metered overage · Recommended" headers="criterion-2 option-col-2" role="cell">5 weeks</td>
+            <td data-label="C · Soft cap" headers="criterion-2 option-col-3" role="cell">4 weeks</td>
           </tr>
-          <tr>
-            <th scope="row">Churn risk</th>
-            <td data-label="A · Hard cap"><span class="risk"><i aria-hidden="true"><span class="on"></span><span class="on"></span><span class="on"></span></i><b>High</b></span><span class="risk-note">Stops work mid-task</span></td>
-            <td class="pick" data-label="B · Metered overage"><span class="risk"><i aria-hidden="true"><span class="on"></span><span></span><span></span></i><b>Low</b></span><span class="risk-note">Nothing is interrupted</span></td>
-            <td data-label="C · Soft cap"><span class="risk"><i aria-hidden="true"><span class="on"></span><span class="on"></span><span></span></i><b>Medium</b></span><span class="risk-note">The grace period ends</span></td>
+          <tr role="row">
+            <th scope="row" id="criterion-3" role="rowheader">Churn risk</th>
+            <td data-label="A · Hard cap" headers="criterion-3 option-col-1" role="cell"><span class="risk"><i aria-hidden="true"><span class="on"></span><span class="on"></span><span class="on"></span></i><b>High</b></span><span class="risk-note">Stops work mid-task</span></td>
+            <td class="pick" data-label="B · Metered overage · Recommended" headers="criterion-3 option-col-2" role="cell"><span class="risk"><i aria-hidden="true"><span class="on"></span><span></span><span></span></i><b>Low</b></span><span class="risk-note">Nothing is interrupted</span></td>
+            <td data-label="C · Soft cap" headers="criterion-3 option-col-3" role="cell"><span class="risk"><i aria-hidden="true"><span class="on"></span><span class="on"></span><span></span></i><b>Medium</b></span><span class="risk-note">The grace period ends</span></td>
           </tr>
-          <tr>
-            <th scope="row">Time to revert</th>
-            <td data-label="A · Hard cap">1 week</td>
-            <td class="pick" data-label="B · Metered overage">2 weeks</td>
-            <td data-label="C · Soft cap">3 weeks</td>
+          <tr role="row">
+            <th scope="row" id="criterion-4" role="rowheader">Time to revert</th>
+            <td data-label="A · Hard cap" headers="criterion-4 option-col-1" role="cell">1 week</td>
+            <td class="pick" data-label="B · Metered overage · Recommended" headers="criterion-4 option-col-2" role="cell">2 weeks</td>
+            <td data-label="C · Soft cap" headers="criterion-4 option-col-3" role="cell">3 weeks</td>
           </tr>
         </tbody>
       </table>
-      <p class="tnote">Revenue is modelled on the June–August usage distribution and carries ±18% at the 80% interval. Support estimates scale from the 2025 seat-cap launch, our closest comparable.</p>
+      <p class="tnote" id="model-note">Revenue is modelled on the June–August usage distribution and carries ±18% at the 80% interval. Support estimates scale from the 2025 seat-cap launch, our closest comparable.</p>
     </div>
   </section>
 
-  <section>
+  <section class="rejections">
     <h2 class="sec">Why not the others</h2>
     <ul class="why">
       <li>
@@ -210,7 +97,7 @@ tbody tr:first-child th,tbody tr:first-child td{border-top:0}
     </ul>
   </section>
 
-  <section>
+  <section class="request-section">
     <h2 class="sec">What we are asking for</h2>
     <div class="ask">
       <ol>
@@ -223,6 +110,6 @@ tbody tr:first-child th,tbody tr:first-child td{border-top:0}
   </section>
 
   <footer class="colophon">Prepared by Ines Arriaga, Pricing. Modelling reviewed by Sam Oyelaran, Data. Sources: usage export 1 June – 31 August 2026; 2025 seat-cap retrospective.</footer>
-</div>
+</main>
 </body>
 </html>
diff --git a/templates/manifest.ts b/templates/manifest.ts
index 3f62c61..2bec6a3 100644
--- a/templates/manifest.ts
+++ b/templates/manifest.ts
@@ -24,21 +24,23 @@ export const starterTemplateManifest = [
         "slug": "meeting-recap",
         "category": "meetings",
         "name": "Meeting recap",
-        "description": "What was decided, who owes what by when, and what is still open. A numbered decision ledger over an owned action table with per-item status.",
+        "description": "A working meeting handover: six owned actions lead beside complete decision minutes, with compact outcome counts, explicit deferrals and open questions that keep their owners and deadlines.",
         "type": "html",
         "content_file": "meeting-recap.html",
         "thumbnail": "/assets/template-thumbs/meeting-recap.png",
-        "slots": []
+        "slots": [],
+        "thumbnail_viewport": 1600
       },
       {
         "slug": "decision-brief",
         "category": "decisions",
         "name": "Decision brief",
-        "description": "Two to four options scored on identical criteria, a recommendation up front, an honest account of why the alternatives lose, and the ask with a dated deadline.",
+        "description": "An evidence-first option bench: a compact recommendation above the full five-criterion comparison, the selected option marked in words, then rejected alternatives, explicit asks and the decision deadline.",
         "type": "html",
         "content_file": "decision-brief.html",
         "thumbnail": "/assets/template-thumbs/decision-brief.png",
-        "slots": []
+        "slots": [],
+        "thumbnail_viewport": 1600
       },
       {
         "slug": "one-pager",
@@ -81,11 +83,12 @@ export const starterTemplateManifest = [
         "slug": "spec",
         "category": "decisions",
         "name": "Spec",
-        "description": "A PRD that opens on scope itself — what the release does, beside what it deliberately does not — then numbered requirements each carrying a priority and an acceptance line, and the open questions still blocking the build.",
+        "description": "A technical requirements folio: in-and-out scope, ruled ID and priority records pairing each requirement with its acceptance clause, then blocking questions with their owners and timings.",
         "type": "html",
         "content_file": "spec.html",
         "thumbnail": "/assets/template-thumbs/spec.png",
-        "slots": []
+        "slots": [],
+        "thumbnail_viewport": 1440
       },
       {
         "slug": "case-study",
@@ -123,11 +126,12 @@ export const starterTemplateManifest = [
         "slug": "research-brief",
         "category": "research",
         "name": "Research Brief",
-        "description": "A question answered up front, then the evidence that earns it: numbered findings each carrying its own citation and a primary/secondary tag, a confidence rating with its reasoning, a source list, and an honest section on what is still unknown.",
+        "description": "An evidence register: the question and answer qualified immediately by confidence and method, five claims beside their provenance, a grouped source register, and the conditions and unknowns that could change the answer.",
         "type": "html",
         "content_file": "research-brief.html",
         "thumbnail": "/assets/template-thumbs/research-brief.png",
-        "slots": []
+        "slots": [],
+        "thumbnail_viewport": 1440
       },
       {
         "slug": "metrics-dashboard",
diff --git a/templates/meeting-recap.html b/templates/meeting-recap.html
index 5ad3a62..94d719d 100644
--- a/templates/meeting-recap.html
+++ b/templates/meeting-recap.html
@@ -1,178 +1,19 @@
+<!-- biome-ignore-all lint/a11y/noRedundantRoles: Explicit table roles retain semantics when mobile CSS uses block/grid display. -->
 <!doctype html>
 <html lang="en">
 <head>
-<meta charset="utf-8" />
-<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
 <title>Q4 platform priorities — meeting recap</title>
-<!--
-  TEMPLATE: meeting-recap  ·  CATEGORY: meetings
-  VOICE: humanist sans (system-ui stack)   SCHEME: cool grey-blue paper, teal accent
-  Rewriting this? Replace the content, keep the structure and the styling.
-  STATUS = A WORD AND A SHAPE, never colour alone: filled disc = done, diamond = in progress,
-  hollow ring = not started. Green and amber are the pair deutan vision collapses, so the shape
-  is what carries the state in greyscale and when read aloud. Do not "simplify" them to dots.
-  DELETE, DON'T EMPTY: if you have no open questions, remove the whole <section>,
-  heading included. Never leave a heading with nothing under it. A short recap
-  should be a short page — do not pad it.
--->
 <style>
-:root{
-  --bg:#eef1f5; --card:#fff; --tint:#f7f9fb;
-  --ink:#18242f; --muted:#4c5b69; --subtle:#5c6a78;
-  --line:#e2e7ec; --line-2:#cbd4dd;
-  --teal:#0d6a68; --teal-soft:#e2efee;
-  --done:#1f6f43; --wip:#8a5a00; --todo:#5c6a78;
-  --sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
-  --shadow:0 1px 2px rgb(24 36 47/.04),0 12px 32px -16px rgb(24 36 47/.28);
-}
-*,*::before,*::after{box-sizing:border-box}
-html{-webkit-text-size-adjust:100%}
-body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased}
-h1,h2,h3{margin:0;line-height:1.2;letter-spacing:-.015em}
-p,ul,ol,dl,dd{margin:0}
-ul{padding:0;list-style:none}
-a{color:var(--teal)}
-.page{max-width:64rem;margin-inline:auto;padding:2.75rem 1.5rem 3.5rem}
-
-/* masthead */
-.eyebrow{display:inline-flex;align-items:center;gap:.5rem;color:var(--teal);font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}
-.eyebrow::before{content:"";width:1.25rem;height:.1875rem;background:var(--teal)}
-.mast h1{margin:.75rem 0 0;font-size:clamp(1.75rem,4vw,2.5rem);font-weight:700}
-.mast p{margin-top:.625rem;max-width:44rem;color:var(--muted);font-size:1.0625rem}
-.facts{display:flex;flex-wrap:wrap;gap:.375rem 1.25rem;margin-top:1.25rem;padding-top:1rem;border-top:1px solid var(--line-2);font-size:.8125rem;color:var(--subtle)}
-.facts b{color:var(--muted);font-weight:600}
-
-/* count strip */
-.strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr));gap:.75rem;margin-top:1.5rem}
-.strip>li{padding:.875rem 1rem;background:var(--card);border:1px solid var(--line);border-radius:.625rem}
-.strip .n{display:block;font-size:1.75rem;font-weight:700;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
-.strip .k{display:block;margin-top:.125rem;color:var(--subtle);font-size:.8125rem}
-
-h2.sec{margin-top:2.5rem;font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--subtle)}
-h2.sec+*{margin-top:.875rem}
-
-/* decision ledger */
-.ledger>li{display:grid;grid-template-columns:2.75rem minmax(0,1fr);gap:1rem;padding-block:1.125rem;border-top:1px solid var(--line-2)}
-.ledger>li:first-child{border-top:0;padding-top:.25rem}
-.ledger .num{color:var(--teal);font-size:1.5rem;font-weight:700;line-height:1.1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
-.ledger h3{font-size:1.125rem;font-weight:650}
-.ledger .why{margin-top:.375rem;color:var(--muted);font-size:.9375rem}
-.ledger .why b{color:var(--ink);font-weight:600}
-.tags{display:flex;flex-wrap:wrap;gap:.375rem .5rem;margin-top:.625rem;font-size:.8125rem;color:var(--subtle)}
-.tags span{padding:.125rem .5rem;background:var(--tint);border:1px solid var(--line);border-radius:999px}
-.tags .dissent{background:#fdf0ed;border-color:#f3cfc7;color:#8f2f22}
-
-/* actions table */
-.tablecard{background:var(--card);border:1px solid var(--line);border-radius:.75rem;box-shadow:var(--shadow);overflow:hidden}
-table{width:100%;border-collapse:collapse;font-size:.9375rem}
-caption{text-align:left;padding:.875rem 1.125rem;border-bottom:1px solid var(--line);color:var(--subtle);font-size:.8125rem}
-.tablecard caption b{color:var(--ink);font-weight:650;font-size:.9375rem}
-th{text-align:left;font-size:.6875rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--subtle);padding:.625rem 1.125rem;background:var(--tint);border-bottom:1px solid var(--line)}
-td{padding:.875rem 1.125rem;border-top:1px solid var(--line);vertical-align:top;color:var(--muted)}
-tbody tr:first-child td{border-top:0}
-.who{display:flex;align-items:center;gap:.625rem;min-width:0}
-.av{flex:none;display:grid;place-items:center;width:2rem;height:2rem;border-radius:999px;background:#dde4ec;color:#33424f;font-size:.75rem;font-weight:700;letter-spacing:.02em}
-.who span{color:var(--ink);font-weight:600;white-space:nowrap}
-td.task{color:var(--ink)}
-td.due{white-space:nowrap;font-variant-numeric:tabular-nums}
-.st{display:inline-flex;align-items:center;gap:.4375rem;white-space:nowrap;font-size:.8125rem;font-weight:600}
-.st::before{content:"";width:.5rem;height:.5rem;border-radius:999px;background:currentColor}
-.st.done{color:var(--done)}
-.st.wip{color:var(--wip)}
-.st.todo{color:var(--todo)}
-.st.wip::before{width:.4375rem;height:.4375rem;border-radius:1px;transform:rotate(45deg)}
-.st.todo::before{background:none;box-shadow:inset 0 0 0 1.5px currentColor}
-
-/* open questions */
-.opens{display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:.75rem}
-.opens{font-size:.8125rem}
-.opens li{padding:1rem 1.125rem;background:var(--card);border:1px solid var(--line);border-radius:.625rem}
-.opens h3{font-size:1rem;font-weight:650}
-.opens p{margin-top:.375rem;color:var(--subtle)}
-.opens p b{color:var(--muted);font-weight:600}
-
-/* deferred note */
-.note{margin-top:.75rem;padding:.875rem 1.125rem;background:var(--teal-soft);border-radius:.5rem;color:#0b4f4d;font-size:.9375rem}
-.note b{font-weight:650}
-
-/* footer */
-.foot{margin-top:2.5rem;padding-top:1.125rem;border-top:1px solid var(--line-2);display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:.75rem 2rem;color:var(--subtle);font-size:.8125rem}
-.foot b{display:block;color:var(--muted);font-weight:600;margin-bottom:.125rem}
-
-@media (max-width:40rem){
-  .page{padding:1.875rem 1rem 1.75rem}
-  .mast p{font-size:1rem}
-  .ledger h3{font-size:1.0625rem}
-
-
-
-
-
-  .note{padding:.75rem .875rem}
-  .ledger>li{grid-template-columns:1.75rem minmax(0,1fr);gap:.75rem}
-  .ledger .num{font-size:1.0625rem;line-height:1.45}
-  .strip{gap:.5rem;grid-template-columns:repeat(auto-fit,minmax(8rem,1fr))}
-  .strip>li{padding:.625rem .75rem}
-  .strip .n{font-size:1.375rem}
-  caption{display:block}
-  thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
-  table,tbody{display:block;width:100%}
-  tr{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.25rem .75rem;border-top:1px solid var(--line)}
-  tbody tr:first-child{border-top:0}
-  td{display:block;padding:0;border-top:0}
-  td.owner{grid-column:1;grid-row:1;align-self:center}
-  td.status{grid-column:2;grid-row:1;justify-self:end;align-self:center}
-  td.task{grid-column:1/-1}
-  td.due{grid-column:1/-1;font-size:.8125rem;color:var(--subtle)}
-  td.due::before{content:"Due ";font-weight:600}
-  /* height tightening — spacing only, no content removed */
-  .facts{margin-top:.75rem;padding-top:.625rem}
-  h2.sec{margin-top:1.125rem}
-  h2.sec+*{margin-top:.75rem}
-  .ledger>li{padding-block:.6875rem}
-  .tags{margin-top:.375rem}
-  .note{margin-top:.625rem}
-  caption{padding:.625rem 1rem}
-  tr{padding:.625rem .875rem}
-  td.task{margin-top:.3125rem}
-  .opens{gap:.625rem}
-  .opens li{padding:.625rem .75rem}
-  .foot{margin-top:1.25rem;padding-top:.875rem}
-}
-
-@media (max-width:24rem){
-  /* 320-384: the card collapse is already on, and the document is still over budget because every
-     line wraps more. Spend width and spacing, never content: drop the numeral gutter so the decision
-     text gets the full measure, and tighten the vertical rhythm. */
-  .page{padding:1rem .625rem 1.125rem}
-  /* The h1 clamp floors at 1.75rem, which is three lines at 320. 1.5rem is still far above every
-     type floor in the contract (13px prose / 12px metadata / 11px labels) and buys a line. */
-  .mast h1{font-size:1.5rem;line-height:1.2}
-  .mast p{margin-top:.5rem}
-  .ledger>li{grid-template-columns:minmax(0,1fr);gap:.1875rem;padding-block:.5rem}
-  .ledger .num{font-size:1rem;line-height:1.3}
-  .ledger .why{margin-top:.25rem}
-  .tags{margin-top:.3125rem}
-  .strip{margin-top:.875rem;gap:.375rem}
-  .strip>li{padding:.4375rem .5rem}
-  .facts{margin-top:.625rem;padding-top:.5rem}
-  .opens li{padding:.5rem .625rem}
-  .note{margin-top:.4375rem;padding:.5rem .625rem}
-  .foot{margin-top:1.125rem;padding-top:.75rem}
-  /* the action table is 7 collapsed cards; every row pays the same padding, so this is the
-     largest spacing lever in the document at this width. Nothing structural is removed. */
-  tr{padding:.5rem .75rem;gap:.125rem .5rem}
-  td.task{margin-top:.1875rem}
-  caption{padding:.5rem .75rem}
-  h2.sec{margin-top:1rem}
-  h2.sec+*{margin-top:.625rem}
-  .opens{gap:.5rem}
-}
+*,*::before,*::after{box-sizing:border-box}html{-webkit-text-size-adjust:100%}:root{--paper:#f6f9fa;--white:#fff;--ink:#172e35;--muted:#4e636b;--line:#bacbd0;--light:#dbe4e6;--teal:#125b60;--tint:#eaf1f2;--done:#1d6843;--wip:#845300;--todo:#52636a;--human:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;--grotesque:"Helvetica Neue",Helvetica,Arial,sans-serif;--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace}body{margin:0;padding:32px 32px 48px;background:var(--paper);color:var(--ink);font:16px/1.65 var(--human);overflow-wrap:anywhere;-webkit-font-smoothing:antialiased}.page{max-width:1240px;margin:auto}h1,h2,h3,p,ul,ol{margin:0}ul,ol{padding:0;list-style:none}h1,h2,h3{line-height:1.3}h1{font:700 clamp(30px,3.2vw,40px)/1.12 var(--grotesque);letter-spacing:-.04em}.mast{display:grid;grid-template-columns:minmax(0,1fr) 380px;column-gap:40px;padding-bottom:24px;border-bottom:3px solid var(--teal)}.eyebrow{font:700 13px/1.5 var(--grotesque);text-transform:uppercase;letter-spacing:.13em;color:var(--teal);margin-bottom:12px}.mast h1{grid-column:1}.mast>p{grid-column:1;margin-top:14px;max-width:66ch;font-size:17px;line-height:1.6;color:var(--muted)}.facts{grid-column:2;grid-row:1/4;align-self:end;display:flex;flex-direction:column;gap:5px;color:var(--muted);font-size:16px;line-height:1.6}.facts b{color:var(--ink);font-weight:650}.strip{display:flex;flex-wrap:wrap;gap:20px 38px;padding:22px 0;border-bottom:1px solid var(--line)}.strip>li{display:flex;align-items:baseline;gap:10px}.n{font:650 24px/1.2 var(--grotesque);font-variant-numeric:tabular-nums;letter-spacing:-.03em}.k{font-size:15px;color:var(--muted)}.meeting-grid{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(0,1fr);gap:40px;align-items:start;margin-top:32px}.meeting-grid{grid-template-rows:auto 1fr}.actions{grid-column:1;grid-row:1}.minutes{grid-column:2;grid-row:1/3}.meeting-grid>.open-section{grid-column:1;grid-row:2;margin-top:0}.meeting-grid>.open-section .opens,.meeting-grid>.open-section .opens{grid-template-columns:1fr;gap:24px}.meeting-grid>.open-section .opens li+li{padding-top:24px;border-top:1px solid var(--line)}.meeting-grid>.open-section .opens li+li{padding-top:22px;border-top:1px solid var(--line)}.meeting-grid>.actions:last-child{grid-column:1/-1}.sec{font:700 23px/1.25 var(--grotesque);letter-spacing:-.025em;margin-bottom:18px}.actions,.minutes{min-width:0}.minutes{background:var(--tint);padding:22px 24px}.ledger>li{padding:24px 0;border-top:1px solid var(--line)}.ledger>li:first-child{padding-top:0;border:0}.ledger>li:last-child{padding-bottom:0}.num{font:600 15px/1.5 var(--mono);color:var(--teal);display:block;margin-bottom:8px}.ledger h3{font-size:19px;font-weight:650;line-height:1.4}.why{font-size:16px;color:var(--muted);line-height:1.65;margin-top:12px}.why b{color:var(--ink)}.tags{display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin-top:14px;font-size:16px;line-height:1.6}.tags span:first-child{font-weight:650;color:var(--ink)}.tags .dissent{color:#8b302c}.note{padding-top:22px;border-top:1px solid var(--line);margin-top:26px;color:var(--teal);font-size:16px;line-height:1.65}.note b{display:block;font-weight:700;margin-bottom:8px}.tablecard{min-width:0;background:var(--white)}table{width:100%;border-collapse:collapse;table-layout:fixed}caption{text-align:left;font-size:16px;line-height:1.6;padding:0 0 18px;background:var(--paper);color:var(--muted)}caption b{color:var(--ink)}th{text-align:left;padding:12px 10px;background:var(--teal);color:var(--white);font-size:14px;line-height:1.5;font-weight:650}th:first-child{width:25%}th:nth-child(2){width:44%}th:nth-child(3){width:11%}th:nth-child(4){width:20%}td{padding:20px 10px;vertical-align:top;border-bottom:1px solid var(--light);font-size:16px;line-height:1.65}.who{display:flex;align-items:flex-start;gap:8px}.who>span:last-child{font-weight:650;min-width:0}.av{flex:none;font:600 11px/1.5 var(--mono);padding:3px;background:var(--tint);color:var(--muted);margin-top:2px}.due{font-family:var(--mono);font-size:14px;line-height:1.65;font-weight:600}.st{display:inline-flex;gap:7px;align-items:center;max-width:100%;font-size:14px;line-height:1.5;font-weight:650}.st::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}.done{color:var(--done)}.wip{color:var(--wip)}.todo{color:var(--todo)}.wip::before{border-radius:0;transform:rotate(45deg)}.todo::before{background:none;border:1.5px solid currentColor}.open-section{margin-top:34px;border-top:2px solid var(--ink);padding-top:24px}.opens{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:40px}.opens h3{font-size:20px;font-weight:650}.opens p{font-size:16px;line-height:1.65;color:var(--muted);margin-top:12px}.opens b{color:var(--ink)}.foot{margin-top:36px;padding-top:24px;border-top:1px solid var(--line);display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:32px;color:var(--muted);font-size:16px;line-height:1.65}.foot b{display:block;color:var(--ink);margin-bottom:8px}a{color:var(--teal)}a:focus-visible{outline:3px solid var(--teal);outline-offset:4px}
+@media(max-width:1080px){body{padding:28px 24px 40px}.mast{grid-template-columns:minmax(0,1fr) 280px;gap:0 28px}.meeting-grid{display:flex;flex-direction:column;gap:30px}.meeting-grid>section{width:100%}.meeting-grid>.open-section .opens{grid-template-columns:repeat(2,minmax(0,1fr))}.meeting-grid>.open-section .opens li+li{border:0;padding-top:0}.minutes{padding:26px}.ledger>li{display:grid;grid-template-columns:36px minmax(0,1fr);gap:20px}.ledger .num{margin:0}.ledger h3{font-size:20px}.tags{flex-direction:row;flex-wrap:wrap;gap:8px 24px}.ledger .why{max-width:85ch}.note{max-width:90ch}.foot{grid-template-columns:1fr 1fr}.foot>p:first-child{grid-column:1/-1}.strip{gap:16px 28px}}
+@media(max-width:640px){body{padding:22px 16px 36px}.mast{display:block;padding-bottom:22px}.eyebrow{display:block;margin-bottom:12px}.mast>p{font-size:17px;margin-top:14px}.facts{margin-top:20px;gap:5px}.strip{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 18px;padding:20px 0}.strip>li{align-items:flex-start;gap:8px}.n{font-size:23px}.k{font-size:14px;line-height:1.5}.meeting-grid{margin-top:26px}.sec{font-size:23px}.tablecard{background:transparent}caption{display:block;padding-bottom:16px}table,tbody{display:block;width:100%}thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}tbody tr{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px 16px;padding:22px 0;border-top:1px solid var(--line)}tbody tr:last-child{border-bottom:1px solid var(--line)}td{display:block;padding:0;border:0}.owner{grid-column:1;grid-row:1}.task{grid-column:1/-1;grid-row:2}.due{grid-column:2;grid-row:1;font-size:15px;white-space:nowrap;color:var(--muted)}.due::before{content:"Due ";font:600 14px/1.5 var(--human)}.status{grid-column:1/-1;grid-row:3}.st{font-size:15px}.who{align-items:baseline}.av{padding:2px 3px}.minutes{padding:22px 20px}.ledger>li{display:block;padding:24px 0}.num{margin-bottom:10px!important}.ledger h3{font-size:20px}.tags{font-size:16px;display:flex;flex-direction:column}.note{margin-top:24px}.opens,.meeting-grid>.open-section .opens{grid-template-columns:1fr;gap:24px}.meeting-grid>.open-section .opens li+li{padding-top:24px;border-top:1px solid var(--line)}.opens li+li{padding-top:24px;border-top:1px solid var(--line)}.foot{grid-template-columns:1fr;gap:22px;margin-top:28px}.foot>p:first-child{grid-column:auto}.open-section{margin-top:30px}}
 
 </style>
 </head>
 <body>
-<div class="page">
+<main class="page">
 
   <header class="mast">
     <span class="eyebrow">Meeting recap</span>
@@ -193,7 +34,62 @@ td.due{white-space:nowrap;font-variant-numeric:tabular-nums}
     <li><span class="n">1</span><span class="k">deferred to 11 Sep</span></li>
   </ul>
 
-  <section>
+  <div class="meeting-grid"><section class="actions">
+    <h2 class="sec">Who owes what</h2>
+    <div class="tablecard">
+      <table role="table">
+        <caption><b>Action items</b> — 6 assigned, 2 closed. Dates are end of day.</caption>
+        <thead role="rowgroup">
+          <tr role="row">
+            <th scope="col" id="action-col-0" role="columnheader">Owner</th>
+            <th scope="col" id="action-col-1" role="columnheader">Action</th>
+            <th scope="col" id="action-col-2" role="columnheader">Due</th>
+            <th scope="col" id="action-col-3" role="columnheader">Status</th>
+          </tr>
+        </thead>
+        <tbody role="rowgroup">
+          <tr role="row">
+            <td class="owner" headers="action-col-0" role="cell"><span class="who"><span class="av" aria-hidden="true">PR</span><span>Priya Raman</span></span></td>
+            <td class="task" headers="action-col-1" role="cell">Write the tracing rollout plan: one request id across four log systems.</td>
+            <td class="due" headers="action-col-2" role="cell">11 Sep</td>
+            <td class="status" headers="action-col-3" role="cell"><span class="st wip">In progress</span></td>
+          </tr>
+          <tr role="row">
+            <td class="owner" headers="action-col-0" role="cell"><span class="who"><span class="av" aria-hidden="true">MF</span><span>Marcus Feld</span></span></td>
+            <td class="task" headers="action-col-1" role="cell">Post the go/no-go once the error budget number is in.</td>
+            <td class="due" headers="action-col-2" role="cell">8 Sep</td>
+            <td class="status" headers="action-col-3" role="cell"><span class="st todo">Not started</span></td>
+          </tr>
+          <tr role="row">
+            <td class="owner" headers="action-col-0" role="cell"><span class="who"><span class="av" aria-hidden="true">DW</span><span>Dana Whitfield</span></span></td>
+            <td class="task" headers="action-col-1" role="cell">Send Northwind the SAML timeline and the assertions it covers.</td>
+            <td class="due" headers="action-col-2" role="cell">5 Sep</td>
+            <td class="status" headers="action-col-3" role="cell"><span class="st done">Done</span></td>
+          </tr>
+          <tr role="row">
+            <td class="owner" headers="action-col-0" role="cell"><span class="who"><span class="av" aria-hidden="true">TL</span><span>Tomas Lindqvist</span></span></td>
+            <td class="task" headers="action-col-1" role="cell">Write down the conditions under which scoped SAML gets replaced.</td>
+            <td class="due" headers="action-col-2" role="cell">18 Sep</td>
+            <td class="status" headers="action-col-3" role="cell"><span class="st todo">Not started</span></td>
+          </tr>
+          <tr role="row">
+            <td class="owner" headers="action-col-0" role="cell"><span class="who"><span class="av" aria-hidden="true">NO</span><span>Nadia Osei</span></span></td>
+            <td class="task" headers="action-col-1" role="cell">Add ingest ownership to the 11 September agenda.</td>
+            <td class="due" headers="action-col-2" role="cell">5 Sep</td>
+            <td class="status" headers="action-col-3" role="cell"><span class="st done">Done</span></td>
+          </tr>
+          <tr role="row">
+            <td class="owner" headers="action-col-0" role="cell"><span class="who"><span class="av" aria-hidden="true">SK</span><span>Sofia Kaur</span></span></td>
+            <td class="task" headers="action-col-1" role="cell">Cost the read-replica work for a Q1 re-proposal.</td>
+            <td class="due" headers="action-col-2" role="cell">25 Sep</td>
+            <td class="status" headers="action-col-3" role="cell"><span class="st todo">Not started</span></td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </section>
+
+  <section class="minutes">
     <h2 class="sec">What we decided</h2>
     <ol class="ledger">
       <li>
@@ -224,62 +120,7 @@ td.due{white-space:nowrap;font-variant-numeric:tabular-nums}
     <p class="note"><b>Deferred.</b> Who owns ingest was raised twice and settled neither time. Moved to the architecture review on 11 September.</p>
   </section>
 
-  <section>
-    <h2 class="sec">Who owes what</h2>
-    <div class="tablecard">
-      <table>
-        <caption><b>Action items</b> — 6 assigned, 2 closed. Dates are end of day.</caption>
-        <thead>
-          <tr>
-            <th scope="col">Owner</th>
-            <th scope="col">Action</th>
-            <th scope="col">Due</th>
-            <th scope="col">Status</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr>
-            <td class="owner"><span class="who"><span class="av" aria-hidden="true">PR</span><span>Priya Raman</span></span></td>
-            <td class="task">Write the tracing rollout plan: one request id across four log systems.</td>
-            <td class="due">11 Sep</td>
-            <td class="status"><span class="st wip">In progress</span></td>
-          </tr>
-          <tr>
-            <td class="owner"><span class="who"><span class="av" aria-hidden="true">MF</span><span>Marcus Feld</span></span></td>
-            <td class="task">Post the go/no-go once the error budget number is in.</td>
-            <td class="due">8 Sep</td>
-            <td class="status"><span class="st todo">Not started</span></td>
-          </tr>
-          <tr>
-            <td class="owner"><span class="who"><span class="av" aria-hidden="true">DW</span><span>Dana Whitfield</span></span></td>
-            <td class="task">Send Northwind the SAML timeline and the assertions it covers.</td>
-            <td class="due">5 Sep</td>
-            <td class="status"><span class="st done">Done</span></td>
-          </tr>
-          <tr>
-            <td class="owner"><span class="who"><span class="av" aria-hidden="true">TL</span><span>Tomas Lindqvist</span></span></td>
-            <td class="task">Write down the conditions under which scoped SAML gets replaced.</td>
-            <td class="due">18 Sep</td>
-            <td class="status"><span class="st todo">Not started</span></td>
-          </tr>
-          <tr>
-            <td class="owner"><span class="who"><span class="av" aria-hidden="true">NO</span><span>Nadia Osei</span></span></td>
-            <td class="task">Add ingest ownership to the 11 September agenda.</td>
-            <td class="due">5 Sep</td>
-            <td class="status"><span class="st done">Done</span></td>
-          </tr>
-          <tr>
-            <td class="owner"><span class="who"><span class="av" aria-hidden="true">SK</span><span>Sofia Kaur</span></span></td>
-            <td class="task">Cost the read-replica work for a Q1 re-proposal.</td>
-            <td class="due">25 Sep</td>
-            <td class="status"><span class="st todo">Not started</span></td>
-          </tr>
-        </tbody>
-      </table>
-    </div>
-  </section>
-
-  <section>
+  <section class="open-section">
     <h2 class="sec">Still open</h2>
     <ul class="opens">
       <li>
@@ -293,12 +134,12 @@ td.due{white-space:nowrap;font-variant-numeric:tabular-nums}
     </ul>
   </section>
 
-  <footer class="foot">
+  </div><footer class="foot">
     <p><b>Present</b>Nadia Osei (chair), Priya Raman, Marcus Feld, Dana Whitfield, Tomas Lindqvist, Sofia Kaur, Ben Achterberg</p>
     <p><b>Apologies</b>Grace Lim — written comments on decision 03, read into the record</p>
     <p><b>Next</b>Architecture review, 11 September, 15:00 UTC</p>
   </footer>
 
-</div>
+</main>
 </body>
 </html>
diff --git a/templates/research-brief.html b/templates/research-brief.html
index 386fe0b..eba4fae 100644
--- a/templates/research-brief.html
+++ b/templates/research-brief.html
@@ -1,166 +1,20 @@
 <!doctype html>
 <html lang="en">
 <head>
-<meta charset="utf-8" />
-<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
 <title>Do teams keep paying for AI meeting notes after month three?</title>
-<!--
-  Agent Artifacts template · research / research-brief
-  VOICE  editorial serif (ui-serif, Charter, Iowan Old Style, Georgia)
-  SCHEME warm paper #f0eae0 · rust accent #a8452a · petrol data hue #2c5670
-
-  REWRITING THIS ARTIFACT
-  · The title is the QUESTION. The answer block directly under it is the answer,
-    stated in the first sentence. Never bury it.
-  · DELETE, don't empty. No sources? Remove the rail. Nothing unknown? Remove
-    that section. Never a heading with nothing under it.
-  · Numbered evidence: three findings is a finished brief, eight is a long one.
-  · Never pin the root to the viewport height, and never give html or body a
-    percentage height; no fixed or sticky positioning. The host frame measures this
-    document and does not scroll.
-  · Nothing external: no web fonts, images, scripts or stylesheets.
--->
 <style>
-*,*::before,*::after{box-sizing:border-box}
-html{-webkit-text-size-adjust:100%}
-:root{
-  --bg:#f0eae0;--panel:#fbf7f1;--paper:#fffdf9;
-  --ink:#231e1a;--muted:#5b5249;--subtle:#72685f;
-  --line:#e2d9cc;--line2:#cfc3b3;
-  --rust:#a8452a;--rust-soft:#fbf0e9;--rust-line:#e8c6b5;
-  --data:#2c5670;--track:#ddd5c8;
-  --serif:ui-serif,Charter,"Bitstream Charter","Iowan Old Style",Georgia,serif;
-  --mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
-  --s1:.25rem;--s2:.5rem;--s3:.75rem;--s4:1rem;--s5:1.5rem;--s6:2rem;--s7:2.75rem;--s8:4rem;
-}
-body{margin:0;padding:var(--s7) var(--s5) var(--s8);background:var(--bg);color:var(--ink);
-  font-family:var(--serif);font-size:1.0625rem;line-height:1.62;-webkit-font-smoothing:antialiased}
-h1,h2,h3{margin:0;line-height:1.2;letter-spacing:-.012em;font-weight:600}
-p{margin:0}
-ul,ol{margin:0;padding:0;list-style:none}
-.wrap{max-width:68rem;margin-inline:auto}
-.kicker{color:var(--subtle);font-family:var(--mono);font-size:.6875rem;font-weight:600;
-  letter-spacing:.12em;text-transform:uppercase}
-
-/* MASTHEAD */
-.mast{padding-bottom:var(--s5);border-bottom:2px solid var(--ink)}
-.eyebrow{display:inline-flex;align-items:center;gap:var(--s2);color:var(--rust);
-  font-family:var(--mono);font-size:.6875rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase}
-.eyebrow::before{content:"";width:.4375rem;height:.4375rem;border-radius:99px;background:var(--rust)}
-.q{max-width:24ch;margin-top:var(--s4);font-size:clamp(1.875rem,4.6vw,3rem);letter-spacing:-.024em;
-  line-height:1.14}
-.meta{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5);margin-top:var(--s5);
-  color:var(--muted);font-size:.8125rem}
-.meta b{font-weight:600;color:var(--ink)}
-
-/* ANSWER — the one thing a reader is allowed to take away */
-.answer{margin-top:var(--s5);padding:var(--s5);border:1px solid var(--rust-line);
-  border-radius:.75rem;background:var(--rust-soft)}
-.answer h2{color:var(--rust);font-family:var(--mono);font-size:.6875rem;font-weight:600;
-  letter-spacing:.1em;text-transform:uppercase}
-.answer p{margin-top:var(--s3);font-size:1.25rem;line-height:1.45}
-.answer p+p{font-size:1.0625rem;line-height:1.6;color:var(--muted)}
-
-/* TWO-COLUMN BODY: evidence + a sources rail */
-.cols{display:grid;grid-template-columns:minmax(0,1fr) 16.5rem;gap:var(--s7);margin-top:var(--s7);
-  align-items:start}
-.h-sec{padding-bottom:var(--s2);border-bottom:1px solid var(--line2);
-  font-family:var(--mono);font-size:.6875rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;
-  color:var(--subtle)}
-
-/* EVIDENCE — a serif numeral in the gutter, hairline between items */
-.ev{margin-top:var(--s5)}
-.ev li{display:grid;grid-template-columns:2.75rem minmax(0,1fr);gap:var(--s4);
-  padding-bottom:var(--s5)}
-.ev li+li{padding-top:var(--s5);border-top:1px solid var(--line)}
-.ev li:last-child{padding-bottom:0}
-.ev__n{color:var(--rust);font-family:var(--mono);font-size:.8125rem;font-weight:600;letter-spacing:.06em;
-  padding-top:.2rem}
-.ev h3{font-size:1.1875rem}
-.ev p{margin-top:var(--s2);color:var(--muted)}
-.cite{display:block;margin-top:var(--s3);color:var(--subtle);font-size:.8125rem;line-height:1.5}
-.cite code{font-family:var(--mono);font-size:.8125rem;color:var(--muted)}
-.pill{display:inline-block;margin-right:.4em;padding:0 .4em;border:1px solid var(--line2);
-  border-radius:.25rem;font-family:var(--mono);font-size:.6875rem;font-weight:600;letter-spacing:.08em;
-  text-transform:uppercase;color:var(--muted)}
-.pill--p{border-color:var(--rust-line);background:var(--rust-soft);color:var(--rust)}
+*,*::before,*::after{box-sizing:border-box}html{-webkit-text-size-adjust:100%}:root{--paper:#fafcfc;--ink:#252b2e;--muted:#526169;--line:#bdcbd1;--light:#dce4e7;--tint:#eef3f5;--blue:#274f68;--track:#cdd8dd;--serif:Georgia,"Times New Roman",serif;--sans:"Helvetica Neue",Helvetica,Arial,"Liberation Sans",sans-serif;--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace}body{margin:0;padding:34px 32px 48px;background:var(--paper);color:var(--ink);font:17px/1.65 var(--sans);overflow-wrap:anywhere;-webkit-font-smoothing:antialiased}.wrap{max-width:1170px;margin:auto}h1,h2,h3,p,ul,ol{margin:0}ul,ol{list-style:none;padding:0}h1,h2,h3{line-height:1.3}.opening{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:48px;align-items:start}.intro{min-width:0}.eyebrow{font:600 12px/1.5 var(--mono);text-transform:uppercase;letter-spacing:.14em;display:block;color:var(--blue);margin-bottom:18px}.q{font:400 clamp(32px,3.8vw,46px)/1.12 var(--serif);letter-spacing:-.027em;max-width:26ch}.meta{display:flex;flex-wrap:wrap;gap:10px 22px;font-size:16px;line-height:1.6;color:var(--muted);padding-top:20px;margin-top:22px;border-top:1px solid var(--line)}.meta b{color:var(--ink);font-weight:600}.answer{padding-top:24px}.answer h2{font:600 14px/1.5 var(--sans);color:var(--blue);margin-bottom:12px}.answer>p{font-size:20px;line-height:1.65;max-width:65ch}.answer>p+p{font-size:17px;color:var(--muted);line-height:1.65;margin-top:18px}.answer strong{font-weight:650}.context{background:var(--tint);padding:24px 26px;display:grid;gap:26px;margin-top:36px;border:1px solid var(--line)}.context>.method{padding-top:24px;border-top:1px solid var(--line)}.kicker{font:600 14px/1.5 var(--sans);letter-spacing:0;color:var(--muted)}.conf{font:500 27px/1.3 var(--sans);color:var(--ink);margin-top:12px}.scale{display:flex;gap:5px;max-width:185px;margin-top:16px}.scale>span{height:7px;flex:1;background:var(--track);border:1px solid #6a8593}.scale>.on{background:var(--blue);border-color:var(--blue)}.confidence>p:last-child,.method>p{font-size:16px;line-height:1.65;margin-top:16px;color:var(--muted)}.evidence-register{margin-top:36px;border-top:2px solid var(--ink);padding-top:22px}.h-sec{font:600 25px/1.3 var(--sans);letter-spacing:-.03em}.ev{margin-top:8px}.ev>li{display:grid;grid-template-columns:44px minmax(0,1fr);gap:24px;padding:26px 0;border-bottom:1px solid var(--line)}.ev__n{font:500 14px/1.5 var(--mono);color:var(--blue);padding-top:4px}.ev>li>div{display:grid;grid-template-columns:minmax(0,1fr) 310px;gap:12px 44px}.ev h3{font-size:21px;font-weight:650;line-height:1.4;grid-column:1}.claim-copy>p{margin-top:12px}.ev p{font-size:17px;line-height:1.65;color:var(--muted);grid-column:1}.cite{grid-column:2;display:block;font-size:16px;line-height:1.65;color:var(--muted);align-self:start}.cite code{font:400 14px/1.65 var(--mono);color:var(--blue);overflow-wrap:anywhere}.pill{font:600 12px/1.5 var(--mono);text-transform:uppercase;letter-spacing:.05em;color:var(--blue);display:table;padding:3px 6px;border:1px solid var(--line);margin-bottom:10px}.pill--p{color:var(--blue);border-color:var(--blue)}.source-register{margin-top:32px}.source-register>.kicker{font:600 24px/1.3 var(--sans);letter-spacing:-.025em;color:var(--ink);margin-bottom:18px}.srcs>li{display:grid;grid-template-columns:68px minmax(0,1fr);gap:26px;padding:16px 0;border-top:1px solid var(--light)}.srcs b{font:500 14px/1.6 var(--mono);color:var(--blue)}.srcs>li>div{display:grid;grid-template-columns:minmax(0,1fr) 250px;gap:30px}.source-name{font-size:16px;line-height:1.65;color:var(--ink)}.source-meta{font-size:16px;line-height:1.65;font-style:normal;color:var(--muted)}.closers{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:44px;margin-top:34px;border-top:2px solid var(--ink);padding-top:26px}.closers h2{font:400 25px/1.3 var(--serif);letter-spacing:-.015em}.bul{margin-top:18px}.bul>li{position:relative;font-size:16px;line-height:1.65;color:var(--muted);padding-left:18px}.bul>li+li{margin-top:18px}.bul>li::before{content:"";position:absolute;left:0;top:10px;width:5px;height:5px;background:var(--blue);border-radius:50%}.bul strong{color:var(--ink);font-weight:650}.colophon{font-size:16px;line-height:1.65;color:var(--muted);margin-top:32px;padding-top:20px;border-top:1px solid var(--line);max-width:100ch}a{color:var(--blue)}a:focus-visible{outline:3px solid var(--blue);outline-offset:4px}
+@media(max-width:960px){body{padding:26px 24px 40px}.opening{grid-template-columns:1fr;gap:26px}.q{max-width:30ch}.answer>p{max-width:80ch}.context{margin:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:32px;padding:22px 24px}.context>.method{padding:0;border:0}.ev>li{gap:20px}.ev>li>div{display:block}.ev h3{font-size:22px}.ev p{margin-top:12px}.cite{margin-top:16px;max-width:80ch}.pill{display:inline-block;margin:0 8px 0 0}.srcs>li>div{grid-template-columns:minmax(0,1fr) 190px;gap:26px}.closers{gap:32px}}
+@media(max-width:640px){body{padding:22px 16px 36px}.eyebrow{margin-bottom:14px}.q{font-size:34px;line-height:1.16}.meta{display:flex;flex-direction:column;gap:7px;margin-top:22px;padding-top:18px;font-size:16px}.answer{padding-top:22px}.answer>p{font-size:19px;line-height:1.65}.answer>p+p{font-size:17px;margin-top:18px}.context{grid-template-columns:1fr;gap:22px;padding:22px 20px}.context>.method{padding-top:22px;border-top:1px solid var(--line)}.evidence-register{margin-top:30px;padding-top:22px}.h-sec{font-size:25px}.ev>li{display:block;padding:24px 0}.ev__n{display:block;padding:0;margin-bottom:10px}.ev h3{font-size:21px;line-height:1.4}.ev p{font-size:17px}.cite{font-size:16px;line-height:1.65}.pill{display:table;margin-bottom:10px}.source-register{margin-top:28px}.srcs>li{grid-template-columns:54px minmax(0,1fr);gap:14px;padding:18px 0}.srcs>li>div{display:block}.source-meta{display:block;margin-top:8px}.closers{grid-template-columns:1fr;gap:26px;margin-top:28px;padding-top:24px}.closers>section+section{padding-top:24px;border-top:1px solid var(--line)}.closers h2{font-size:25px}.bul>li+li{margin-top:16px}.colophon{margin-top:28px}}
 
-/* RAIL */
-.rail{display:grid;gap:var(--s5)}
-.card{padding:var(--s5);border:1px solid var(--line);border-radius:.75rem;background:var(--panel)}
-.card>p.conf{margin-top:var(--s3);font-size:1.375rem;font-weight:600;color:var(--ink);line-height:1.3}
-.scale{display:flex;gap:3px;margin-top:var(--s3)}
-.scale span{flex:1 1 0;height:.5rem;border-radius:2px;background:var(--track)}
-.scale span.on{background:var(--data)}
-.card>p{margin-top:var(--s3);color:var(--muted);font-size:.875rem;line-height:1.5}
-.srcs{margin-top:var(--s4)}
-.srcs li{display:grid;grid-template-columns:2.2rem minmax(0,1fr);gap:var(--s2);
-  color:var(--muted);font-size:.8125rem;line-height:1.45}
-.srcs li+li{margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--line)}
-.srcs b{color:var(--subtle);font-family:var(--mono);font-size:.8125rem;font-weight:600;white-space:nowrap}
-.srcs em{display:block;font-style:normal;color:var(--subtle);font-family:var(--mono);font-size:.75rem;
-  margin-top:.15rem}
-
-/* CLOSERS */
-.closers{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(17rem,100%),1fr));gap:var(--s5);
-  margin-top:var(--s7);padding-top:var(--s5);border-top:2px solid var(--ink)}
-.closers h2{font-size:1.1875rem}
-.bul{margin-top:var(--s4)}
-.bul li{position:relative;padding-left:var(--s5);color:var(--muted);font-size:.9375rem;line-height:1.55}
-.bul li+li{margin-top:var(--s3)}
-.bul li::before{content:"";position:absolute;top:.66rem;left:var(--s2);width:.3125rem;height:.3125rem;
-  border-radius:99px;background:var(--rust)}
-.bul strong{color:var(--ink);font-weight:600}
-.colophon{margin-top:var(--s6);padding-top:var(--s4);border-top:1px solid var(--line);
-  color:var(--subtle);font-size:.8125rem;line-height:1.55}
-
-@media (max-width:62rem){
-  .cols{grid-template-columns:minmax(0,1fr)}
-  .rail{grid-template-columns:repeat(auto-fit,minmax(min(15rem,100%),1fr))}
-
-  :root{--s3:.625rem;--s4:.875rem;--s5:1rem;--s6:1.125rem;--s7:1.25rem;--s8:1.5rem}
-  body{padding:var(--s5) var(--s4) var(--s8)}
-  .mast{padding-bottom:var(--s4)}
-  .meta{margin-top:var(--s4);gap:var(--s1) var(--s4)}
-  .answer{padding:var(--s4)}
-  .answer p{margin-top:var(--s2)}
-  .answer p+p{margin-top:var(--s2)}
-  .cols{margin-top:var(--s5);gap:var(--s5)}
-  .h-sec{padding-bottom:var(--s1)}
-  .ev__n{padding-top:.1rem}
-  .ev p{margin-top:var(--s1)}
-  .ev li{gap:var(--s3);padding-bottom:var(--s3)}
-  .ev li+li{padding-top:var(--s3)}
-  .cite{margin-top:var(--s2)}
-  .card{padding:var(--s4)}
-  .card>p{margin-top:var(--s2)}
-  .scale{margin-top:var(--s2)}
-  .srcs li+li{margin-top:var(--s2);padding-top:var(--s2)}
-  .rail{gap:var(--s4)}
-  .closers{margin-top:var(--s5);padding-top:var(--s3);gap:var(--s4)}
-  .bul li+li{margin-top:var(--s2)}
-  .colophon{margin-top:var(--s5);padding-top:var(--s3)}
-}
-@media (max-width:34rem){
-  /* Spacing compaction now starts where the layout becomes one column, not here: between
-     the two the document ran desktop spacing in a narrow column and jumped hundreds of px
-     across one pixel of viewport. Same values, earlier start; type stays here. */
-  body{font-size:.9375rem;line-height:1.46}
-  .q{line-height:1.1}
-  .answer p{font-size:1.0625rem;line-height:1.34}
-  .ev p,.bul li,.card>p,.srcs li,.cite{line-height:1.36}
-  .ev h3{font-size:1rem;line-height:1.16}
-  .ev li{grid-template-columns:1.75rem minmax(0,1fr)}
-  .conf{line-height:1.2}
-  .rail{grid-template-columns:repeat(2,minmax(0,1fr))}
-  .rail>section:nth-child(2){grid-column:1/-1;order:1}
-}
 </style>
 </head>
 <body>
-<div class="wrap">
+<main class="wrap">
 
-  <header class="mast">
+  <div class="opening"><div class="intro"><header class="mast">
     <span class="eyebrow">Research brief</span>
     <h1 class="q">Do teams keep paying for AI meeting notes after month three?</h1>
     <p class="meta">
@@ -181,96 +35,91 @@ ul,ol{margin:0;padding:0;list-style:none}
       predicting renewal once it clears roughly 90%.</p>
   </section>
 
-  <div class="cols">
-    <main>
+  </div><aside class="context"><section class="confidence" aria-labelledby="cf">
+        <h2 class="kicker" id="cf">Confidence</h2>
+        <p class="conf">Moderate</p>
+        <p class="scale" role="img" aria-label="Confidence: moderate, 3 of 5">
+          <span class="on"></span><span class="on"></span><span class="on"></span><span></span><span></span>
+        </p>
+        <p>Three of five. The direction is consistent across nine sources; the size of the effect
+          rests on two vendor cohorts that are not audited.</p>
+      </section>
+
+      <section class="method" aria-labelledby="mt">
+        <h2 class="kicker" id="mt">Method</h2>
+        <p>Desk review of published cohort curves, then two internal datasets and twenty-two
+          interviews against our own base. Retention is measured on revenue, not logos.</p>
+      </section>
+    </aside></div><section class="evidence-register">
       <h2 class="h-sec">Evidence</h2>
       <ol class="ev">
         <li>
           <span class="ev__n">01</span>
           <div>
-            <h3>Month-six retention clusters in the low twenties</h3>
+            <div class="claim-copy"><h3>Month-six retention clusters in the low twenties</h3>
             <p>Across six vendors that disclose cohort curves, median month-six retention is 24%; best 34%,
 worst 18%. Month three is where the curve bends, not month one.</p>
-            <span class="cite"><span class="pill">Secondary</span>Bracken Research, <em>State of Meeting Intelligence 2026</em>, June 2026 · n=6
+            </div><span class="cite"><span class="pill">Secondary</span>Bracken Research, <em>State of Meeting Intelligence 2026</em>, June 2026 · n=6
               vendor cohorts · <code>bracken-research.com/smi-2026</code></span>
           </div>
         </li>
         <li>
           <span class="ev__n">02</span>
           <div>
-            <h3>A connected destination is the single strongest correlate</h3>
+            <div class="claim-copy"><h3>A connected destination is the single strongest correlate</h3>
             <p>Workspaces that connected a CRM, tracker or wiki within fourteen days retained at 71% by month
 six, against 22% for those that never did — a gap that holds after controlling for seat count
 and plan.</p>
-            <span class="cite"><span class="pill pill--p">Primary</span>Internal cohort analysis,
+            </div><span class="cite"><span class="pill pill--p">Primary</span>Internal cohort analysis,
               4,182 workspaces activated Jan–Mar 2026 · queried 11 Sep 2026</span>
           </div>
         </li>
         <li>
           <span class="ev__n">03</span>
           <div>
-            <h3>Summary quality is table stakes, not a differentiator</h3>
+            <div class="claim-copy"><h3>Summary quality is table stakes, not a differentiator</h3>
             <p>Buyers rank accuracy in the top three reasons to <em>buy</em> and outside the top five
               reasons to <em>renew</em>. Every product in the set was "good enough" on transcription.</p>
-            <span class="cite"><span class="pill pill--p">Primary</span>Buyer interviews, 22
+            </div><span class="cite"><span class="pill pill--p">Primary</span>Buyer interviews, 22
               accounts, 20–120 seats, Jul–Aug 2026 · transcripts on file</span>
           </div>
         </li>
         <li>
           <span class="ev__n">04</span>
           <div>
-            <h3>Churn happens at the renewal date, not during the trial</h3>
+            <div class="claim-copy"><h3>Churn happens at the renewal date, not during the trial</h3>
             <p>Trial-to-paid is stable at 38%, but 81% of lost revenue leaves at the first annual renewal —
 which is why quarterly dashboards read this market as healthier than it is.</p>
-            <span class="cite"><span class="pill pill--p">Primary</span>Internal billing extract,
+            </div><span class="cite"><span class="pill pill--p">Primary</span>Internal billing extract,
               FY2025–26 · plus two vendor disclosures reporting the same shape</span>
           </div>
         </li>
         <li>
           <span class="ev__n">05</span>
           <div>
-            <h3>Price is not the stated reason — dormant seats are</h3>
+            <div class="claim-copy"><h3>Price is not the stated reason — dormant seats are</h3>
             <p>Three of twenty-two churned accounts named price. Fourteen named seats they paid for and never
 used — the same complaint under another name.</p>
-            <span class="cite"><span class="pill pill--p">Primary</span>Exit interviews, 22 accounts
+            </div><span class="cite"><span class="pill pill--p">Primary</span>Exit interviews, 22 accounts
               · coded independently by two analysts, agreement 0.84</span>
           </div>
         </li>
       </ol>
-    </main>
-
-    <aside class="rail">
-      <section class="card" aria-labelledby="cf">
-        <h2 class="kicker" id="cf">Confidence</h2>
-        <p class="conf">Moderate</p>
-        <p class="scale" role="img" aria-label="Confidence: moderate, 3 of 5">
-          <span class="on"></span><span class="on"></span><span class="on"></span><span></span><span></span>
-        </p>
-        <p>Three of five. The direction is consistent across nine sources; the size of the effect
-          rests on two vendor cohorts that are not audited.</p>
-      </section>
+    </section>
 
-      <section class="card" aria-labelledby="sr">
+    <section class="source-register" aria-labelledby="sr">
         <h2 class="kicker" id="sr">Sources · 9</h2>
         <ol class="srcs">
-          <li><b>01</b><div>Bracken Research, State of Meeting Intelligence 2026<em>Jun 2026 · secondary</em></div></li>
-          <li><b>02</b><div>Internal cohort analysis, 4,182 workspaces<em>Sep 2026 · primary</em></div></li>
-          <li><b>03</b><div>Buyer interviews, 22 accounts, 20–120 seats<em>Jul–Aug 2026 · primary</em></div></li>
-          <li><b>04–05</b><div>Internal billing extract FY2025–26; exit interviews, 22 accounts,
-            double-coded<em>Aug–Sep 2026 · primary</em></div></li>
-          <li><b>06–09</b><div>Four vendor investor updates and pricing pages, read 8–10 Sep<em>2026 · secondary</em></div></li>
+          <li><b>01</b><div><span class="source-name">Bracken Research, State of Meeting Intelligence 2026</span><em class="source-meta">Jun 2026 · secondary</em></div></li>
+          <li><b>02</b><div><span class="source-name">Internal cohort analysis, 4,182 workspaces</span><em class="source-meta">Sep 2026 · primary</em></div></li>
+          <li><b>03</b><div><span class="source-name">Buyer interviews, 22 accounts, 20–120 seats</span><em class="source-meta">Jul–Aug 2026 · primary</em></div></li>
+          <li><b>04–05</b><div><span class="source-name">Internal billing extract FY2025–26; exit interviews, 22 accounts,
+            double-coded</span><em class="source-meta">Aug–Sep 2026 · primary</em></div></li>
+          <li><b>06–09</b><div><span class="source-name">Four vendor investor updates and pricing pages, read 8–10 Sep</span><em class="source-meta">2026 · secondary</em></div></li>
         </ol>
       </section>
 
-      <section class="card" aria-labelledby="mt">
-        <h2 class="kicker" id="mt">Method</h2>
-        <p>Desk review of published cohort curves, then two internal datasets and twenty-two
-          interviews against our own base. Retention is measured on revenue, not logos.</p>
-      </section>
-    </aside>
-  </div>
-
-  <div class="closers">
+      <div class="closers">
     <section aria-labelledby="ch">
       <h2 id="ch">What would change this answer</h2>
       <ul class="bul">
@@ -297,6 +146,6 @@ too wide to quote.</li>
 were read 8–10 September; internal figures were queried on 11 September and will drift.
 Transcripts and the cohort query are on file with Research.</footer>
 
-</div>
+</main>
 </body>
 </html>
diff --git a/templates/spec.html b/templates/spec.html
index 5761209..bbe9cd4 100644
--- a/templates/spec.html
+++ b/templates/spec.html
@@ -1,111 +1,18 @@
 <!doctype html>
 <html lang="en">
 <head>
-<meta charset="utf-8" />
-<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
 <title>Spec — saved views for the ticket queue (1.9)</title>
-<!--
-  TEMPLATE: spec  ·  CATEGORY: decisions
-  VOICE: humanist sans (system-ui stack), tight measure   SCHEME: near-white paper, deep amber accent
-  Rewriting this? Replace the content, keep the structure and the styling.
-  THE OUT COLUMN IS NOT DECORATION. A spec with goals and no non-goals has not been written yet —
-  the non-goals are what stop the scope moving, so give them the same care as the goals.
-  EVERY REQUIREMENT NEEDS AN ACCEPTANCE LINE. If you cannot say how you would know it was met,
-  it is a wish, not a requirement: cut it or move it to the open questions.
-  DELETE, DON'T EMPTY: no open questions left? remove the section, heading included. Renumber
-  requirements so the IDs stay contiguous.
--->
 <style>
-:root{
-  --bg:#faf9f7; --card:#fff; --tint:#f4f2ee;
-  --ink:#1c1a16; --muted:#55504a; --subtle:#6a645c;
-  --line:#e8e4de; --line-2:#d6d0c7;
-  --amber:#96560a; --amber-soft:#fdf1e0; --amber-line:#eed6ae;
-  --sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
-  --shadow:0 1px 2px rgb(28 26 22/.05),0 12px 32px -16px rgb(28 26 22/.3);
-}
-*,*::before,*::after{box-sizing:border-box}
-html{-webkit-text-size-adjust:100%}
-body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased}
-h1,h2,h3{margin:0;line-height:1.2;letter-spacing:-.015em}
-p,dl,dd{margin:0}
-ul,ol{margin:0;padding:0;list-style:none}
-.page{max-width:54rem;margin-inline:auto;padding:2.5rem 1.5rem 3rem}
-.measure{max-width:40rem}
+*,*::before,*::after{box-sizing:border-box}html{-webkit-text-size-adjust:100%}:root{--paper:#f8f7f2;--white:#fff;--ink:#242722;--muted:#586052;--line:#bec4b7;--light:#dfe2d9;--tint:#eeefe7;--amber:#835218;--pale:#f2e7d5;--human:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace}body{margin:0;padding:28px 32px 48px;background:var(--paper);color:var(--ink);font:16px/1.65 var(--human);overflow-wrap:anywhere;-webkit-font-smoothing:antialiased}.page{max-width:1170px;margin:auto}h1,h2,h3,p,ol,ul{margin:0}ul,ol{list-style:none;padding:0}h1,h2,h3{line-height:1.35}.mast{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:0 48px;padding-bottom:28px;border-bottom:2px solid var(--ink)}.eyebrow{font:600 13px/1.5 var(--mono);letter-spacing:.06em;margin-bottom:14px;display:block}.mast h1{font:600 clamp(27px,2.65vw,34px)/1.25 var(--mono);letter-spacing:-.05em;max-width:30ch;grid-column:1}.lead{grid-column:1;max-width:62ch;font-size:17px;line-height:1.65;color:var(--muted);margin-top:18px}.facts{grid-column:2;grid-row:1/4;align-self:start;display:grid;gap:9px;padding:18px 20px;border:1px solid var(--line);background:var(--white);font-size:15px;line-height:1.6;color:var(--muted)}.facts b{color:var(--ink);font-weight:650}.state{display:flex;align-items:center;gap:8px;font:600 13px/1.5 var(--mono);color:var(--amber);background:var(--pale);padding:5px 8px;width:fit-content;margin-bottom:4px}.state::before{content:"";width:7px;height:7px;border:1.5px solid currentColor;border-radius:50%;flex:none}.sec{font:600 20px/1.4 var(--mono);letter-spacing:-.025em;margin:28px 0 18px}.count{font:400 16px/1.6 var(--human);color:var(--muted);letter-spacing:0}.scope{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:38px}.scope>.in:last-child{grid-column:1/-1}.scope>div{min-width:0}.scope h3{font:600 15px/1.5 var(--mono);padding:9px 14px;margin-bottom:14px;border:1px solid var(--line)}.in h3{color:var(--ink);background:var(--tint)}.out h3{color:var(--muted);background:transparent}.scope li{position:relative;padding-left:19px;font-size:16px;line-height:1.6}.scope li+li{margin-top:12px}.scope li::before{content:"";position:absolute;left:0;top:9px;width:6px;height:6px;flex:none}.in li::before{background:var(--ink)}.out li::before{border:1px solid var(--muted)}.out li{color:var(--muted)}.reqs{border-top:2px solid var(--ink)}.reqs>li{display:grid;grid-template-columns:94px minmax(0,1fr) minmax(0,1.12fr);gap:30px;padding:24px 0;border-bottom:1px solid var(--line)}.r-head{display:flex;flex-direction:column;align-items:flex-start;gap:12px}.rid{font:650 15px/1.5 var(--mono)}.pri{width:fit-content;padding:3px 6px;font:600 12px/1.5 var(--mono);letter-spacing:.02em}.must{background:var(--ink);color:var(--white)}.should{border:1px solid var(--muted);color:var(--muted)}.could{color:var(--muted);border:1px dashed var(--line)}.req{font-size:17px;line-height:1.6;font-weight:600}.acc{color:var(--muted);font-size:16px;line-height:1.65}.acc b{display:block;color:var(--muted);font:600 12px/1.5 var(--mono);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}.question-register{margin-top:32px;padding:24px 26px;border:1px solid var(--line);background:var(--white)}.question-register .sec{margin:0 0 18px}.open>li{display:grid;grid-template-columns:minmax(0,1fr) 295px;gap:38px;padding:22px 0;border-top:1px solid var(--light)}.open>li:last-child{padding-bottom:0}.open h3{font-size:20px;font-weight:650;line-height:1.4}.q-copy>p{font-size:16px;line-height:1.65;color:var(--muted);margin-top:12px}.needs{font-size:16px;line-height:1.65;color:var(--muted);align-self:start}.needs b{font:600 13px/1.5 var(--mono);display:block;margin-bottom:8px;color:var(--amber)}.colophon{font-size:16px;line-height:1.65;color:var(--muted);margin-top:30px;max-width:100ch;padding-top:20px;border-top:1px solid var(--line)}a{color:var(--amber)}a:focus-visible{outline:3px solid var(--ink);outline-offset:4px}
+@media(max-width:960px){body{padding:26px 24px 40px}.mast{display:block}.mast h1{max-width:35ch}.facts{display:flex;flex-wrap:wrap;gap:10px 24px;margin-top:22px;align-items:baseline}.state{margin:0}.scope{gap:26px}.reqs>li{grid-template-columns:82px minmax(0,1fr) minmax(0,1.1fr);gap:22px}.open>li{grid-template-columns:minmax(0,1fr) 220px;gap:26px}.question-register{padding:22px}}
+@media(max-width:680px){body{padding:22px 16px 36px}.mast{padding-bottom:22px}.mast h1{font-size:27px;letter-spacing:-.045em}.lead{font-size:17px;margin-top:16px}.facts{display:flex;flex-direction:column;align-items:flex-start;padding:16px;gap:9px;font-size:16px}.sec{font-size:19px;margin-top:26px;margin-bottom:16px}.count{display:block;margin-top:6px;font-size:16px}.scope{grid-template-columns:1fr;gap:22px}.scope h3{padding:9px 12px}.scope li+li{margin-top:10px}.reqs>li{display:block;padding:22px 0}.r-head{flex-direction:row;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px}.rid{font-size:16px}.pri{font-size:13px}.req{font-size:17px}.acc{margin-top:16px}.acc b{margin-bottom:7px}.question-register{margin-top:28px;padding:20px 18px}.question-register .count{margin-top:8px}.open>li{display:block;padding:22px 0}.open h3{font-size:19px}.needs{margin-top:18px}.needs b{margin-bottom:6px}.colophon{margin-top:28px}}
 
-.eyebrow{display:inline-flex;align-items:center;gap:.5rem;color:var(--amber);font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}
-.eyebrow::before{content:"";width:1.25rem;height:.1875rem;background:var(--amber)}
-.mast h1{margin:.625rem 0 0;font-size:clamp(1.75rem,3.6vw,2.375rem);font-weight:700}
-.mast .lead{margin-top:.5rem;color:var(--muted);font-size:1.0625rem}
-.facts{display:flex;flex-wrap:wrap;gap:.375rem 1.25rem;margin-top:1.125rem;padding-top:.875rem;border-top:1px solid var(--line-2);font-size:.8125rem;color:var(--subtle)}
-.facts b{color:var(--muted);font-weight:600}
-.state{display:inline-flex;align-items:center;gap:.4375rem;padding:.0625rem .5rem;border:1px solid var(--amber-line);background:var(--amber-soft);border-radius:999px;color:var(--amber);font-size:.8125rem;font-weight:700}
-.state::before{content:"";width:.4375rem;height:.4375rem;border-radius:999px;background:var(--amber)}
-
-/* SCOPE BAND — the opening move, directly under the metadata row.
-   Two columns, in beside out, so the scope boundary is a vertical rule you read across rather
-   than a line you read down. This is the template's own best idea hoisted to the top: the shape
-   and the promise ("goals beside non-goals") are the same object again.
-   The marker is filled vs hollow AND the column carries the word, so neither colour nor position
-   is doing the work alone. */
-.scope{display:grid;grid-template-columns:1fr 1fr;margin-top:.75rem;background:var(--card);border:1px solid var(--line-2);border-radius:.75rem;box-shadow:var(--shadow);overflow:hidden}
-.scope>div{padding:.875rem 1.125rem}
-.scope .out{background:var(--tint);border-left:1px solid var(--line-2)}
-.scope h3{font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:.5rem}
-.scope .in h3{color:var(--amber)}
-.scope .out h3{color:var(--subtle)}
-.scope li{position:relative;padding-left:1.0625rem;font-size:.9375rem;line-height:1.5}
-.scope li+li{margin-top:.4375rem}
-.scope li::before{content:"";position:absolute;left:0;top:.4375rem;width:.5rem;height:.5rem;border-radius:.125rem}
-.scope .in li{color:var(--ink)}
-.scope .in li::before{background:var(--amber)}
-.scope .out li{color:var(--muted)}
-.scope .out li::before{box-shadow:inset 0 0 0 1.5px var(--line-2)}
-h2.sec{margin-top:2.25rem;font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--subtle)}
-h2.sec .count{color:var(--muted);font-weight:600;letter-spacing:0;text-transform:none;font-size:.8125rem}
-h2.sec+*{margin-top:.75rem}
-
-/* requirements */
-.reqs{background:var(--card);border:1px solid var(--line-2);border-radius:.75rem;box-shadow:var(--shadow);overflow:hidden}
-.reqs>li{display:grid;grid-template-columns:3.5rem minmax(0,1fr) auto;gap:.375rem 1rem;padding:.9375rem 1.125rem;border-top:1px solid var(--line)}
-.reqs>li:first-child{border-top:0}
-.rid{grid-row:span 2;color:var(--ink);font-size:.8125rem;font-weight:700;letter-spacing:.06em;font-variant-numeric:tabular-nums;line-height:1.5}
-.req{color:var(--ink);font-size:.9375rem;font-weight:600}
-.acc{grid-column:2;color:var(--muted);font-size:.875rem}
-.acc b{color:var(--subtle);font-weight:700;font-size:.6875rem;letter-spacing:.08em;text-transform:uppercase}
-.pri{grid-row:span 2;align-self:start;justify-self:end;padding:.0625rem .4375rem;border-radius:.25rem;font-size:.6875rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
-.pri.must{background:var(--ink);color:#fff}
-.pri.should{border:1px solid var(--line-2);color:var(--muted)}
-.pri.could{color:var(--subtle)}
-
-/* open questions */
-.open li{padding-block:.875rem;border-top:1px solid var(--line-2);font-size:.9375rem}
-.open li:first-child{border-top:0;padding-top:.25rem}
-.open h3{font-size:1rem;font-weight:650}
-.open p{margin-top:.25rem;color:var(--muted);max-width:42rem}
-.open .needs{margin-top:.3125rem;color:var(--subtle);font-size:.8125rem}
-.open .needs b{color:var(--muted);font-weight:600}
-
-.colophon{margin-top:2rem;padding-top:1rem;border-top:1px solid var(--line-2);color:var(--subtle);font-size:.8125rem}
-
-@media (max-width:34rem){
-  .page{padding:1.875rem 1rem 2rem}
-  .mast .lead{font-size:1rem}
-  h2.sec{margin-top:1.625rem}
-  .colophon{margin-top:1.5rem}
-  .scope{grid-template-columns:minmax(0,1fr)}
-  .scope>div{padding:.75rem 1rem}
-  .scope .out{border-left:0;border-top:1px solid var(--line-2)}
-  .reqs>li{grid-template-columns:minmax(0,1fr) auto;gap:.25rem .75rem;padding:.8125rem 1rem}
-  .rid{grid-row:auto;grid-column:1;font-size:.8125rem;color:var(--subtle)}
-  .req{grid-column:1/-1}
-  .acc{grid-column:1/-1}
-  .pri{grid-row:1;grid-column:2}
-}
 </style>
 </head>
 <body>
-<div class="page">
+<main class="page">
 
   <header class="mast">
     <span class="eyebrow">Product spec · 1.9</span>
@@ -147,66 +54,66 @@ h2.sec+*{margin-top:.75rem}
     <h2 class="sec">Requirements <span class="count">— 6, three of them must-have</span></h2>
     <ol class="reqs">
       <li>
-        <span class="rid">R-01</span>
-        <p class="req">An agent can save the current filter state as a named view.</p>
+        <div class="r-head"><span class="rid">R-01</span>
         <span class="pri must">Must</span>
+        </div><p class="req">An agent can save the current filter state as a named view.</p>
         <p class="acc"><b>Accepts when</b> — a view reproduces an identical result set on reload, sort included.</p>
       </li>
       <li>
-        <span class="rid">R-02</span>
-        <p class="req">Views list by last used: five shown, the rest behind "All views".</p>
+        <div class="r-head"><span class="rid">R-02</span>
         <span class="pri must">Must</span>
+        </div><p class="req">Views list by last used: five shown, the rest behind "All views".</p>
         <p class="acc"><b>Accepts when</b> — an agent with twelve views sees their five most recent without opening a menu.</p>
       </li>
       <li>
-        <span class="rid">R-03</span>
-        <p class="req">A view can be shared with a team and is read-only to everyone but its owner.</p>
+        <div class="r-head"><span class="rid">R-03</span>
         <span class="pri must">Must</span>
+        </div><p class="req">A view can be shared with a team and is read-only to everyone but its owner.</p>
         <p class="acc"><b>Accepts when</b> — a non-owner can duplicate a shared view and edit the copy, but not save over it.</p>
       </li>
       <li>
-        <span class="rid">R-04</span>
-        <p class="req">Every view has a stable URL that anyone with queue access can open.</p>
+        <div class="r-head"><span class="rid">R-04</span>
         <span class="pri should">Should</span>
+        </div><p class="req">Every view has a stable URL that anyone with queue access can open.</p>
         <p class="acc"><b>Accepts when</b> — the URL in a fresh session lands on the same queue, or a clear "no access" state.</p>
       </li>
       <li>
-        <span class="rid">R-05</span>
-        <p class="req">Deleting a shared view warns with the number of people who used it recently.</p>
+        <div class="r-head"><span class="rid">R-05</span>
         <span class="pri should">Should</span>
+        </div><p class="req">Deleting a shared view warns with the number of people who used it recently.</p>
         <p class="acc"><b>Accepts when</b> — the confirmation names the 30-day count and requires a second action.</p>
       </li>
       <li>
-        <span class="rid">R-06</span>
-        <p class="req">Up to three views can be pinned to the sidebar.</p>
+        <div class="r-head"><span class="rid">R-06</span>
         <span class="pri could">Could</span>
+        </div><p class="req">Up to three views can be pinned to the sidebar.</p>
         <p class="acc"><b>Accepts when</b> — pinning a fourth asks which pin to replace rather than failing silently.</p>
       </li>
     </ol>
   </section>
 
-  <section>
+  <section class="question-register">
     <h2 class="sec">Open questions <span class="count">— 3, all blocking the build start</span></h2>
     <ul class="open">
       <li>
-        <h3>What happens to a shared view when its owner leaves?</h3>
+        <div class="q-copy"><h3>What happens to a shared view when its owner leaves?</h3>
         <p>Transfer to the team, or archive. Archiving is safer and will annoy people; transfer needs an owner-election rule we do not have.</p>
-        <p class="needs"><b>Needs:</b> Mira Halvorsen with Legal — before the build starts, not before beta.</p>
+        </div><p class="needs"><b>Needs:</b> Mira Halvorsen with Legal — before the build starts, not before beta.</p>
       </li>
       <li>
-        <h3>Do saved views count against the 200-request filter rate limit?</h3>
+        <div class="q-copy"><h3>Do saved views count against the 200-request filter rate limit?</h3>
         <p>Five pinned views refreshing on queue open is five requests where there is one. At 400 agents that is a different traffic shape.</p>
-        <p class="needs"><b>Needs:</b> Platform — by 12 September.</p>
+        </div><p class="needs"><b>Needs:</b> Platform — by 12 September.</p>
       </li>
       <li>
-        <h3>Does "ordered by last used" leak activity between teammates?</h3>
+        <div class="q-copy"><h3>Does "ordered by last used" leak activity between teammates?</h3>
         <p>A view's position tells you when a colleague last worked the queue. Fine internally, probably; not obviously fine for regulated accounts.</p>
-        <p class="needs"><b>Needs:</b> Security review — before beta.</p>
+        </div><p class="needs"><b>Needs:</b> Security review — before beta.</p>
       </li>
     </ul>
   </section>
 
   <footer class="colophon">Draft 3, updated 4 September 2026 by Mira Halvorsen. The 90-second baseline comes from queue instrumentation added in 1.7, over 4,100 sessions in August. Comments close 11 September; anything unresolved becomes an open question above, not a silent assumption.</footer>
-</div>
+</main>
 </body>
 </html>
diff --git a/tests/e2e/template-table-semantics.spec.ts b/tests/e2e/template-table-semantics.spec.ts
index 1acd302..e518da4 100644
--- a/tests/e2e/template-table-semantics.spec.ts
+++ b/tests/e2e/template-table-semantics.spec.ts
@@ -1,6 +1,7 @@
-import { expect, test } from '@playwright/test';
+import { expect, type Locator, test } from '@playwright/test';
 
 const sandboxOrigin = process.env.E2E_CLOUD_SANDBOX_ORIGIN;
+const cloudOrigin = process.env.E2E_CLOUD_BASE_URL;
 
 for (const [slug, columns, expectedRows] of [
   ['metrics-dashboard', ['Channel', 'Sessions', 'Signups', 'Conv.', 'vs W33'], 6],
@@ -54,3 +55,94 @@ for (const [slug, columns, expectedRows] of [
     );
   });
 }
+
+// ID references must resolve to native scoped headers in this table and the correct row/column.
+// Seeing a rowheader role somewhere in an AX snapshot is not sufficient evidence of association.
+const inspectHeaderReferences = (table: Locator, expectedReferences: number) =>
+  table.evaluate((element, expected) => {
+    if (!(element instanceof HTMLTableElement)) throw new Error('Expected native table');
+    return [...element.querySelectorAll('tbody td')].map((cell) => {
+      if (!(cell instanceof HTMLTableCellElement)) throw new Error('Expected native cell');
+      const ids = cell.headers.trim().split(/\s+/);
+      const scopes = ids.map((id) => document.getElementById(id)?.getAttribute('scope'));
+      const valid =
+        ids.length === expected &&
+        new Set(ids).size === ids.length &&
+        scopes.filter((scope) => scope === 'col').length === 1 &&
+        scopes.filter((scope) => scope === 'row').length === expected - 1 &&
+        ids.every((id) => {
+          const header = document.getElementById(id);
+          if (!(header instanceof HTMLTableCellElement) || header.tagName !== 'TH') return false;
+          if (header.closest('table') !== element) return false;
+          if (header.scope === 'row') return header.parentElement === cell.parentElement;
+          return (
+            header.scope === 'col' &&
+            header.closest('thead') === element.tHead &&
+            header.cellIndex === cell.cellIndex
+          );
+        });
+      return { ids, valid };
+    });
+  }, expectedReferences);
+
+for (const [slug, expectedRows, expectedCells, expectedReferences] of [
+  ['meeting-recap', 6, 24, 1],
+  ['decision-brief', 5, 15, 2],
+] as const) {
+  test(`${slug}: native header IDs and AX names survive the real responsive sandbox`, async ({
+    page,
+  }) => {
+    await page.goto(`${cloudOrigin}/templates/${slug}`);
+    const iframe = page.locator('iframe');
+    await expect(iframe).toHaveAttribute('sandbox', 'allow-scripts');
+    await expect(iframe).toHaveAttribute('src', `${sandboxOrigin}/templates/${slug}/frame`);
+    const frame = page.frameLocator('iframe');
+    const table = frame.getByRole('table');
+    await expect(table).toHaveCount(1);
+    await expect(table.getByRole('row')).toHaveCount(expectedRows + 1);
+    await expect(table.getByRole('columnheader')).toHaveCount(4);
+    await expect(table.getByRole('rowheader')).toHaveCount(slug === 'decision-brief' ? 5 : 0);
+    await expect(table.getByRole('cell')).toHaveCount(expectedCells);
+    for (const header of await table.locator('th').all()) {
+      const id = await header.getAttribute('id');
+      expect(id).toBeTruthy();
+      // CSS uppercasing differs from the ARIA snapshot's authored case; words must still agree.
+      const name = (await header.innerText()).trim().replace(/\s+/g, ' ');
+      await expect(header).toHaveAccessibleName(
+        new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i')
+      );
+    }
+    const associations = await inspectHeaderReferences(table, expectedReferences);
+    expect(associations).toHaveLength(expectedCells);
+    expect(associations.every((association) => association.valid)).toBe(true);
+
+    // Red controls modify only this disposable browser DOM, never the source or customer data.
+    // The same assertion must detect both dangling IDs and existing-but-wrong column/row IDs.
+    const firstCell = table.locator('tbody td').first();
+    const original = await firstCell.getAttribute('headers');
+    if (!original) throw new Error('Missing native header references');
+    await firstCell.evaluate((cell) => cell.setAttribute('headers', 'missing-header'));
+    expect((await inspectHeaderReferences(table, expectedReferences)).every((a) => a.valid)).toBe(
+      false
+    );
+    await firstCell.evaluate((cell, ids) => cell.setAttribute('headers', ids), original);
+    const wrongHeader =
+      slug === 'meeting-recap'
+        ? await table.locator('thead th').nth(1).getAttribute('id')
+        : await table.locator('tbody th').nth(1).getAttribute('id');
+    if (!wrongHeader) throw new Error('Missing red-control header');
+    const wrongIds = original.replace(/^\S+/, wrongHeader);
+    expect(wrongIds).not.toBe(original);
+    await firstCell.evaluate((cell, ids) => cell.setAttribute('headers', ids), wrongIds);
+    expect((await inspectHeaderReferences(table, expectedReferences)).every((a) => a.valid)).toBe(
+      false
+    );
+    await firstCell.evaluate((cell, ids) => cell.setAttribute('headers', ids), original);
+    expect((await inspectHeaderReferences(table, expectedReferences)).every((a) => a.valid)).toBe(
+      true
+    );
+    expect(
+      await frame.locator('html').evaluate((element) => element.scrollWidth <= innerWidth)
+    ).toBe(true);
+  });
+}
diff --git a/tests/integration/viewer/view-capture.test.ts b/tests/integration/viewer/view-capture.test.ts
index 18845d5..c266186 100644
--- a/tests/integration/viewer/view-capture.test.ts
+++ b/tests/integration/viewer/view-capture.test.ts
@@ -1,4 +1,4 @@
-import { describe, expect, it } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
 import {
   createViewerTestContext,
   publishSharedArtifact,
@@ -258,6 +258,9 @@ describe('who the reader is, without knowing who they are', () => {
   });
 
   it('counts a returning reader as another view but the same visitor', async () => {
+    // This scenario promises the same UTC day. Real time after 23:00 plus one hour is tomorrow,
+    // when the privacy salt correctly rotates; anchor the fixture without changing production time.
+    vi.spyOn(Date, 'now').mockReturnValue(Date.UTC(2026, 8, 6, 12));
     const ctx = await createViewerTestContext({ trustProxy: 1 });
 
     try {
diff --git a/tests/unit/template-table-semantics.test.ts b/tests/unit/template-table-semantics.test.ts
index bc4b0d6..61ce0a9 100644
--- a/tests/unit/template-table-semantics.test.ts
+++ b/tests/unit/template-table-semantics.test.ts
@@ -4,9 +4,9 @@ import { describe, expect, it } from 'vitest';
 const source = (slug: string) =>
   readFileSync(new URL(`../../templates/${slug}.html`, import.meta.url), 'utf8');
 
-// These two responsive tables intentionally retain explicit roles when CSS changes their display.
+// These four responsive tables intentionally retain explicit roles when CSS changes their display.
 // Bound the exception to the known table inventory, rather than silencing future unrelated roles.
-describe('responsive operational table semantics', () => {
+describe('responsive template table semantics', () => {
   it('uses four native scoped Postmortem row headers, not interactive roles on td', () => {
     const html = source('postmortem');
     expect(
@@ -17,12 +17,19 @@ describe('responsive operational table semantics', () => {
     expect(html).not.toMatch(/<td[^>]*role="rowheader"/);
   });
 
-  for (const [slug, inventory] of [
+  for (const [slug, inventory, imageRoles] of [
     [
       'metrics-dashboard',
       { table: 1, rowgroup: 3, row: 7, columnheader: 5, rowheader: 6, cell: 24 },
+      1,
+    ],
+    ['postmortem', { table: 1, rowgroup: 2, row: 5, columnheader: 5, rowheader: 4, cell: 16 }, 1],
+    ['meeting-recap', { table: 1, rowgroup: 2, row: 7, columnheader: 4, cell: 24 }, 0],
+    [
+      'decision-brief',
+      { table: 1, rowgroup: 2, row: 6, columnheader: 4, rowheader: 5, cell: 15 },
+      0,
     ],
-    ['postmortem', { table: 1, rowgroup: 2, row: 5, columnheader: 5, rowheader: 4, cell: 16 }],
   ] as const) {
     it(`${slug} limits its compatibility exception to the documented role inventory`, () => {
       const html = source(slug);
@@ -39,8 +46,8 @@ describe('responsive operational table semantics', () => {
       }
       expect(counts).toEqual(inventory);
       expect(html.match(/role="[^"]+"/g)).toHaveLength(
-        Object.values(inventory).reduce((sum, count) => sum + count, 0) + 1
-      ); // One unchanged named SVG image, outside the table.
+        Object.values(inventory).reduce((sum, count) => sum + count, 0) + imageRoles
+      ); // The two operational examples also have one unchanged named SVG image.
     });
   }
 });
