# Annotated Writing Build Instructions for a build agent. Input: a completed content map `.md` file. Output: a single-file `.html` artefact. Do not perform literary analysis. Do not rewrite or improve the source text. Execute the map. --- ## Two-Agent Protocol This agent receives a content map. It does not produce content maps. If asked to do both in one context, refuse the analysis task and request the content map as input. **What the build agent reads from the content map:** - Tab definitions (TOON) - Decoder metadata (TOON) + decoder bodies (YAML) - Lightbox definitions (YAML) - Accordion content (YAML) - Bias notes (YAML) - Further reading (TOON) - Annotated source text (with `[DECODER]`, `[LIGHTBOX]`, `[ORDER]`, `[LETTER-*]` markers) --- ## Reading TOON in the Content Map Content maps use TOON for uniform arrays. Parse as follows: **Tabular array syntax:** ```toon arrayName[N]{field1,field2,field3}: value1,value2,value3 value1,value2,value3 ``` `[N]` is the declared row count — validate against actual rows. `{fields}` is the column header — same for every row. Values follow CSV quoting rules: strings containing commas are double-quoted. **YAML scalar syntax** (for body text — not TOON): ```yaml key: > Block scalar. Paragraph text. May contain commas freely. Blank line = new paragraph in rendered output. ``` Both may appear in the same content map. TOON for metadata; YAML for prose. --- ## Aesthetic Direction The aesthetic is set per-project by the user. The content map may include an `aesthetic:` key. If absent, infer from context clues in the source text. When in doubt, ask before building. Regardless of aesthetic, these rules apply: - Choose fonts that are distinctive and specific to the aesthetic. Never: Arial, Inter, Roboto, system-ui. - Use CSS custom properties for all colors. No hardcoded hex values in component CSS. - Dark or light themes are both valid. Commit to one — do not default to white-on-grey. - Background texture adds atmosphere. Scanlines, grain, parchment patterns, noise — pick one and use it subtly. - Tab navigation is sticky. One tab visible at a time via JS class toggle. --- ## Component Library All components must be implemented as specified. Do not substitute or simplify. --- ### Decoder Inline interactive element. Wraps a phrase in prose. Floating panel on click. **Critical structural rule:** Every element inside a decoder must be a ``, never a `
`. Decoders live inside `

` tags. A `

` inside a `

` silently ejects the panel from the DOM. Use `` with `display: block` for block-like rendering. ```html [tag text] [label] [body text] → [link label] ``` **Required CSS:** ```css .dc { display: inline; position: relative; } .dc-panel { display: none; position: absolute; /* never relative */ top: calc(100% + 6px); left: 0; z-index: 500; width: 300px; /* adjust to aesthetic */ } .dc-panel.open { display: block; } .dc-panel.flip { left: auto; right: 0; } ``` **Required JS:** ```js function tog(id, btn) { const w = document.getElementById(id); const p = w.querySelector('.dc-panel'); const open = p.classList.contains('open'); document.querySelectorAll('.dc-panel.open').forEach(x => x.classList.remove('open')); document.querySelectorAll('.dc-btn.open').forEach(x => x.classList.remove('open')); if (!open) { p.classList.add('open'); btn.classList.add('open'); const r = p.getBoundingClientRect(); p.classList.toggle('flip', r.right > window.innerWidth - 16); } } document.addEventListener('click', e => { if (!e.target.closest('.dc')) { document.querySelectorAll('.dc-panel.open').forEach(x => x.classList.remove('open')); document.querySelectorAll('.dc-btn.open').forEach(x => x.classList.remove('open')); } }); ``` **Trigger visual requirements:** - Must look clickable (cursor pointer, border-bottom dashed or dotted) - Must indicate open/closed state (chevron via `::after`, or similar) - Color coding must match the scheme defined in the content map --- ### Lightbox Full-screen overlay. Triggered by an inline button in the prose. ```html

[eyebrow] [title]
``` **Required CSS:** ```css .lb-overlay { display: none; position: fixed; inset: 0; z-index: 2000; align-items: center; justify-content: center; padding: 20px; } .lb-overlay.open { display: flex; } .lb-box { max-height: 88vh; overflow-y: auto; } .lb-head { position: sticky; top: 0; } ``` **Required JS:** ```js function lb(id) { document.getElementById(id).classList.add('open'); document.body.style.overflow = 'hidden'; } function closeLb(id) { document.getElementById(id).classList.remove('open'); document.body.style.overflow = ''; } function closeLbOv(e, id) { if (e.target === document.getElementById(id)) closeLb(id); } document.addEventListener('keydown', e => { if (e.key === 'Escape') { document.querySelectorAll('.lb-overlay.open').forEach(x => x.classList.remove('open')); document.body.style.overflow = ''; } }); ``` --- ### Accordion Expandable sections within educational tabs. One open at a time. The accordion panel uses `
` — it is not inside prose, so block elements are safe here. ```html
``` **Required JS:** ```js function acc(btn) { const p = btn.nextElementSibling; const open = btn.classList.contains('open'); document.querySelectorAll('.acc-btn.open').forEach(b => { b.classList.remove('open'); b.nextElementSibling.classList.remove('open'); }); if (!open) { btn.classList.add('open'); p.classList.add('open'); } } ``` --- ### Bias Note A styled callout block at the top of each analytical tab, before the first accordion. ```html
A note: [bias note text from content map]
``` Not collapsible. Always visible. One per analytical tab. --- ### Tab Navigation ```html ``` ```js function go(id, btn, col) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('nav button').forEach(b => { b.classList.remove('active'); /* remove all color classes */ }); document.getElementById('tab-' + id).classList.add('active'); btn.classList.add('active', col || 'white'); window.scrollTo(0, 0); } ``` Tab content divs: ```html
``` --- ### Source Text — Markers to HTML Translate content map markers into HTML components: | Marker | Renders as | |---|---| | `[DECODER:id] phrase [/DECODER]` | `` decoder component | | `[LIGHTBOX:id] phrase [/LIGHTBOX]` | `