Homepage (site/index.html): integration-v14 promoted, Writings section integrated with 33 pieces clustered by type (stories/essays/miscellany), Writings welcome lightbox, content frame at 98% opacity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
298 lines
9.8 KiB
Markdown
298 lines
9.8 KiB
Markdown
# 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 `<span>`, never a `<div>`. Decoders live inside `<p>` tags. A `<div>` inside a `<p>` silently ejects the panel from the DOM. Use `<span>` with `display: block` for block-like rendering.
|
|
|
|
```html
|
|
<span class="dc" id="[id]">
|
|
<button class="dc-btn [color-class]" onclick="tog('[id]',this)">[phrase from text]</button>
|
|
<span class="dc-panel [color-class]">
|
|
<span class="d-tag [color-class]">[tag text]</span>
|
|
<span class="d-head">[label]</span>
|
|
[body text]
|
|
<a class="d-link [color-class]" href="[url]" target="_blank">→ [link label]</a>
|
|
</span>
|
|
</span>
|
|
```
|
|
|
|
**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
|
|
<!-- Trigger — inline in prose -->
|
|
<button class="lb-t [color-class]" onclick="lb('[id]')">[trigger phrase]</button>
|
|
|
|
<!-- Lightbox — at end of body, before script tag -->
|
|
<div class="lb-overlay" id="[id]" onclick="closeLbOv(event,'[id]')">
|
|
<div class="lb-box [color-class]">
|
|
<div class="lb-head [color-class]">
|
|
<div>
|
|
<span class="lb-eyebrow [color-class]">[eyebrow]</span>
|
|
<span class="lb-title">[title]</span>
|
|
</div>
|
|
<button class="lb-close" onclick="closeLb('[id]')">✕</button>
|
|
</div>
|
|
<div class="lb-body">
|
|
<!-- h3 per section, p per paragraph, lb-src link at end -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**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 `<div>` — it is not inside prose, so block elements are safe here.
|
|
|
|
```html
|
|
<div class="acc">
|
|
<button class="acc-btn" onclick="acc(this)">
|
|
<span>[heading]</span>
|
|
<span class="acc-arrow [color-class]">▾</span>
|
|
</button>
|
|
<div class="acc-panel">
|
|
<!-- p tags per paragraph -->
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**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
|
|
<div class="bias [color-class]">
|
|
<strong>A note:</strong> [bias note text from content map]
|
|
</div>
|
|
```
|
|
|
|
Not collapsible. Always visible. One per analytical tab.
|
|
|
|
---
|
|
|
|
### Tab Navigation
|
|
|
|
```html
|
|
<nav>
|
|
<button class="active [color-class]" onclick="go('[id]',this,'[color]')">[label]</button>
|
|
<!-- one per tab -->
|
|
</nav>
|
|
```
|
|
|
|
```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
|
|
<div class="tab [active-on-load]" id="tab-[id]">
|
|
<!-- tab content -->
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
### Source Text — Markers to HTML
|
|
|
|
Translate content map markers into HTML components:
|
|
|
|
| Marker | Renders as |
|
|
|---|---|
|
|
| `[DECODER:id] phrase [/DECODER]` | `<span class="dc" id="id">` decoder component |
|
|
| `[LIGHTBOX:id] phrase [/LIGHTBOX]` | `<button class="lb-t">` trigger |
|
|
| `[ORDER] "text" [/ORDER]` | Styled display block — dialogue/order separator |
|
|
| `[LETTER-START:sender]` | Opening of styled letter card |
|
|
| `[LETTER-END]` | Close of letter card |
|
|
| `[PULL-QUOTE] text [/PULL-QUOTE]` | Styled pull-quote block |
|
|
| `[EDITORIAL] text [/EDITORIAL]` | Styled editorial aside |
|
|
| `[SECTION-BREAK]` | `<hr>` with aesthetic treatment |
|
|
|
|
Letter senders each get a distinct visual treatment (border color, label, optional stamp). Define sender colors in CSS variables using the project color scheme.
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
Single `.html` file. All CSS in `<style>` in `<head>`. All JS in `<script>` immediately before `</body>`. No external dependencies except Google Fonts (loaded via `<link>`). All lightboxes placed together at the end of `<body>`, before `<script>`. All external links use `target="_blank"`.
|
|
|
|
---
|
|
|
|
## Critical Bugs (Permanent Reference)
|
|
|
|
**1. Block elements inside `<p>` tags silently break the DOM.**
|
|
`<div>` inside `<p>` is invalid HTML. The browser auto-closes the `<p>`, ejecting the decoder panel. Every decoder element must be a `<span>`.
|
|
|
|
**2. Decoder panel must be `position: absolute`.**
|
|
`position: relative` expands the parent span when the panel opens, collapsing the surrounding text. Use `position: absolute`.
|
|
|
|
**3. Decoder panels clip off the right edge.**
|
|
After opening, check `panel.getBoundingClientRect().right > window.innerWidth - 16`. If true, add `.flip` class (`left: auto; right: 0`).
|
|
|
|
**4. Lightboxes placed inside tab content divs break scroll.**
|
|
Place all lightbox overlays as direct children of `<body>`, after all tab content, before `<script>`.
|
|
|
|
---
|
|
|
|
## Output Requirements
|
|
|
|
- Single `.html` file
|
|
- Source text reproduced exactly — no edits, no corrections (errors in the original stay)
|
|
- All decoders, lightboxes, accordions, and tabs from the content map implemented
|
|
- Bias notes present on every analytical tab
|
|
- All external links open in new tab
|
|
- Passes a basic sanity check: all decoder IDs referenced in prose exist in the JS/HTML
|