# Baseline 1.3.1: the shared site template

This package is the shared template of Jeff Posluns's 6 project sites (secureconfig.ai, opfiles.ai, auditthis.ai, cleanlanguage.ai, aiqt.ai, grclibrary.ai): 1 stylesheet, 1 palette file, 4 fonts, 3 scripts, the logos, marks and touch icons, 10 reference pages that fix the markup, and the 2 documents that govern the sites. A site that copies the runtime files, sets its site class and generates its pages with the reference markup looks and behaves as part of the family and differs only in its colours, logo and content. The normative description is `SPECIFICATION.md` (1.2); `CONTENT-GUIDELINES.md` (1.2) governs the wording, and the Clean Language standard (https://cleanlanguage.ai) governs the prose. `CHANGELOG.md` lists what changed in each version and why.

A published version never changes. Any change is a new version at `https://posluns.ca/baseline/<version>/`, with its archive `https://posluns.ca/baseline/baseline-<version>.tar.gz` and the archives' digests in `https://posluns.ca/baseline/SHA256SUMS`.

## What is in the package

| Path | Content | Copied by a site |
|---|---|---|
| `baseline.css` | The whole shared stylesheet: reset, typography, layout, every component, the footer's container query stages and their fallback. Reads only the tokens in `palettes.css` and the family and layout tokens it declares at the top. | yes |
| `palettes.css` | The 6 site palettes (light, dark, and `[data-scheme]` forced variants for previews), the cyclic rainbow and the footer's neutral grey. | yes |
| `gen_palettes.py`, `contrast-report.txt` | The palette generator and its report; the generator exits non-zero if any text pair misses 4.5:1 (UI pairs 3:1). Edit the script, never the CSS. | no |
| `fonts.css`, `fonts/` | Newsreader, Public Sans, JetBrains Mono and Dancing Script, latin subsets, variable weight, each with its SIL OFL 1.1 licence file. The font paths are relative to `fonts.css`. | yes, with the licence files |
| `copy.js`, `select.js`, `search.js`, `rail.js` | Copy to clipboard for prompt and command blocks; the install selector; the client-side list filter; the rail mark that follows the reader (from 1.3.0, on every page whose rail lists in-page sections). Each is opt-in per page and enhances markup that works without it. Load with `defer`. | yes |
| `logos/` | Per site: `<site>.svg` (full colour: hero, rail, footer), `<site>-mono.svg`, `<site>-favicon.svg`, `<site>-card.svg` (link preview), `<site>-touch.png` (Apple touch icon, 180 by 180, opaque). | yes |
| `marks/` | GitHub, LinkedIn and ORCID marks for the footer, with `marks/NOTICE.md` (sources, dates, terms). | yes, with `marks/NOTICE.md` |
| `reference/` | 10 reference pages, 1 per page type (section "The reference pages" below). | no |
| `tools/measure.js` | Measures the 2 per-site constants (section "Per-site constants"). | no |
| `SPECIFICATION.md`, `CONTENT-GUIDELINES.md` | The specification and the content guidelines at the version published with this package. | no |
| `LICENSE`, `NOTICE.md` | CC0 1.0; the exceptions (fonts, marks). | yes |
| `SHA256SUMS`, `CHANGELOG.md`, `README.md` | Checksums of every file; history; this file. | no |

## Adoption in a repository

1. Obtain the version as its archive, `https://posluns.ca/baseline/baseline-<version>.tar.gz`, and verify its SHA-256 against `https://posluns.ca/baseline/SHA256SUMS` before unpacking. Never verify file by file over the live domain: Cloudflare injects scripts into the HTML it serves, so an HTML file fetched from the site is not the published file. Inside the archive, `SHA256SUMS` covers every file of the version (`sha256sum -c SHA256SUMS` from the version folder).
2. Copy only the runtime and licence files (the "yes" rows above) into the site's own asset folder (proposed `assets/baseline/`): `baseline.css`, `palettes.css`, `fonts.css`, `fonts/` with the 4 licence files, `copy.js`, `select.js`, `search.js`, `rail.js`, `logos/`, `marks/` with `marks/NOTICE.md`, `LICENSE`, `NOTICE.md`. Never copy `reference/`, `tools/`, `gen_palettes.py`, `contrast-report.txt` or the documents.
3. Record the baseline version and each copied file's SHA-256 in `.baseline/pin.toml`:

   ```toml
   version = "1.3.1"

   [files]
   "assets/baseline/baseline.css" = "<sha256>"
   "assets/baseline/palettes.css" = "<sha256>"
   ```

   Add an offline gate that fails if any copied file differs from the pin. Run that gate, the security.txt gate and the site's other integrity gates from the Cloudflare Pages build command, so that any failure fails the deploy. No gate reaches the network.
4. Put `class="site-<name>"` on `<html>` (`site-secureconfig`, `site-opfiles`, `site-auditthis`, `site-cleanlanguage`, `site-aiqt`, `site-grclibrary`). That class, the logo files and the content are the only per-site differences. A site must not add CSS that overrides `baseline.css`, with 1 exception: an optional `site.css`, loaded after it, may set only the measured per-site constants `--sw` and `--logo-inset` when the site's statement or logo changes (section "Per-site constants"). A need the template cannot meet is a change request to the baseline.
5. Generate every page from the site's own sources with the markup of the reference pages; they are the contract. Every page links `fonts.css`, `palettes.css` and `baseline.css` in that order, carries the favicon and touch-icon links, the skip link, the top bar, 1 `h1`, the footer and the `#copy-status` live region, and contains no inline style, inline script or `on*` attribute. A `<script type="application/ld+json">` block is data and is allowed, on live pages only, never on a noindex draft.
6. Update the site's own gates where they conflict (`SPECIFICATION.md`, section 11).
7. Add this entry to the repository's NOTICE:

   > Site template: the shared site template (baseline <version>, https://posluns.ca/baseline/) is dedicated to the public domain under CC0 1.0 Universal, with 2 exceptions. The fonts Newsreader, Public Sans, JetBrains Mono and Dancing Script are licensed by their authors under the SIL Open Font License 1.1; their licence files are in assets/baseline/fonts/. The GitHub Invertocat, the LinkedIn [in] bug and the ORCID iD icon in assets/baseline/marks/ are trademarks of their owners, used under their owners' published terms as recorded in assets/baseline/marks/NOTICE.md, and are not covered by the CC0 grant.

8. Updates arrive as 1 pull request per site that bumps the pin and the copied files; each site's own checks run as usual.

### Staging at `/draft/` and promotion

1. Base path. Generate every page, asset and internal link with a configurable base path: `/draft/` while staging, `/` after promotion. The draft is self-contained: no draft page links to, or loads anything from, the live pages at the root, except a live file whose path is kept at promotion (for example `/downloads/*`, or opfiles.ai's `/adopt.md`), which a draft page may link rather than copy. The baseline files are copied under the draft (`/draft/assets/baseline/`) and move with it. Every path in this package is relative, so the files work at any base path.
2. Kept out of search and away from the live site. Every draft page carries `<meta name="robots" content="noindex, nofollow">`; `_headers` sends `X-Robots-Tag: noindex` for `/draft/*`; the draft is absent from `sitemap.xml`, `llms.txt` and every other index the live site publishes; the live pages never link to it. Check `_redirects` for a live `/draft` redirect and remove it before staging.
3. Live site unchanged, with 1 exception. Building the draft changes no live page, path, redirect or published file. The exception is the owner-ordered live hardening, in its own pull request ahead of the draft: `/.well-known/security.txt`, `sitemap.xml` and the `Sitemap` line of `robots.txt`, the `apple-touch-icon` links, the removal of a live `/draft` redirect, and the header changes of `SPECIFICATION.md` section 9. Nothing else changes live before promotion. Every existing gate keeps passing on the live tree; gates that scan every HTML file either cover the draft under the template's rules or exclude `/draft/` explicitly, and the exclusion is removed at promotion.
4. Review. The draft is complete when every page of the site's page list exists under `/draft/`, the site's checks and the content guidelines checklist pass, and the implementer has reported to the owner with the draft's URL.
5. Promotion, after the owner's approval only. In 1 pull request: remove the old pages from the published tree (they remain in git history); rebuild the draft with base path `/`; add redirects for every old public path that changes; restore the root's `sitemap.xml`, `llms.txt` and other indexes for the new pages; remove the `/draft/` noindex rules and any gate exclusion; verify every fixed path on the preview deployment before merging.

### Headers

Cloudflare sends only its automatic headers (`X-Content-Type-Options`, `Referrer-Policy`, `Access-Control-Allow-Origin`, HSTS from the zone, `Speculation-Rules` from Speed Brain); no custom Cloudflare rule. The repository's `_headers` sends `Content-Security-Policy`, `X-Frame-Options: DENY`, `Permissions-Policy` and `Cross-Origin-Opener-Policy: same-origin` on `/*`, and on `/draft/*` removes the live CSP, sets the draft CSP and `X-Robots-Tag: noindex`. The shared CSP, the 4 Cloudflare features and their needs, the hash and nonce caveat, the Speed Brain caveat and the proposed Permissions-Policy value are in `SPECIFICATION.md`, section 9.

### Site class, logo and base path

- The site class on `<html>` selects the palette (`palettes.css`) and the per-site constants in `baseline.css` (`--sw`, `--logo-inset`, `--tail-from`).
- The logo appears once per page: in the hero on the landing page (`.hero-art img`), above the rail on every other page (`.rail-logo`); the footer uses all 6 full-colour logos. In the head: `<link rel="icon" href="<base>assets/baseline/logos/<site>-favicon.svg" type="image/svg+xml">` and `<link rel="apple-touch-icon" href="<base>assets/baseline/logos/<site>-touch.png">`; the card logo is for link previews.
- The top bar of every site: the site's own 4 to 6 page links on 1 row, then GitHub as the last, right-most item where the site has a public repository; no Disclosure or Licence item (`SPECIFICATION.md` section 7.1). On a window too narrow for the row, the links scroll sideways and a shadow at the scrolled edge shows that more follow; no script.
- The landing page's "About this project" line, below the value paragraph, links Disclosure, Licence, Privacy (`https://posluns.ca/privacy/`) and Security (`https://posluns.ca/security/`), separated by middle dots (section 7.24); it is the only place, besides the value paragraph's own licence link, that links the Disclosure and Licence pages. The licence page lives at `/license/` (heading "Licence"), the disclosure page at `/disclosure/`; the disclosure page links the vulnerability disclosure policy and the privacy policy.
- aiqt.ai's rules page keeps the anchors `#rule1` to `#rule5` on its 5 clause sections, so the live redirects from `/rule1` to `/rule5` keep working.

## The reference pages

Each page in `reference/` is built for 1 example site and opens directly from the folder, in light or dark mode as the operating system prefers. Cross-links between pages that exist in the set go to that page; links to pages of the example site that are not in the set go to `landing.html`, so the set stays self-contained. The pages are markup examples, not a site.

| Page | Example site | What it fixes |
|---|---|---|
| `landing.html` | secureconfig.ai | The hero (kicker, statement, introduction, start link, logo), the "Start here" entry tiles, the "What it gives you" value paragraph, the "About this project" line, the footer |
| `document.html` | aiqt.ai, the standard | The document grid with label column, rail ("On this page"), document control strip, numbered clauses, callout, Next block |
| `reference.html` | aiqt.ai, mappings | Figures, framed registers, a fixed-layout table with `colgroup` widths and wrapping cells, a commit in 7-character form, details blocks |
| `install.html` | cleanlanguage.ai | The selector (`select.js`), download blocks, copy buttons (`copy.js`), steps, the "Last verified" line |
| `license.html` | auditthis.ai | The licence page: scope sentence, parts under another licence, the complete plain-language summary, the owner's note, Next |
| `disclosure.html` | opfiles.ai (entries quoted from the AIQT disclosure as an example of the format) | The disclosure page: opening sentence, entries of claim, limitation and evidence, the security line with the vulnerability disclosure and privacy policy links, Next |
| `404.html` | grclibrary.ai | The 404 page with the logo-only rail |
| `channels.html` | cleanlanguage.ai, by channel | The channel list page: 1 section per channel, each an `h2` and its list of rules, the rail listing the channels, the source callout |
| `checklist.html` | cleanlanguage.ai, checklist | The checklist page: groups of questions as numbered clauses (`N.1`, `N.2`), citable by number |
| `demo.html` | cleanlanguage.ai, try it | The demo page: a before-and-after pair, the prompt in a copy block, the no-script note, the install link |

Prompt and code blocks come in 2 variants beyond the plain copy block: `pre.prompt-block` (capped at 36rem, scrolls inside, for a prompt the reader copies) and `pre.prompt-flow` (the whole text in the page flow, for a portable text the reader reads in place); both keep the same `pre id`, copy button and status contract.

## Per-site constants

Two values in `baseline.css` depend on a site's content and are measured, not designed. The values shipped in `baseline.css` for the 6 approved statements and logos:

| Site | `--sw` light | `--sw` dark | `--logo-inset` |
|---|---|---|---|
| secureconfig.ai | 13.712 | 14.612 | 0.115 |
| opfiles.ai | 15.070 | 16.070 | 0.1225 |
| auditthis.ai | 12.562 | 13.337 | 0.2075 |
| cleanlanguage.ai | 12.824 | 13.724 | 0.185 |
| aiqt.ai | 19.868 | 21.218 | 0.0975 |
| grclibrary.ai | 13.069 | 13.894 | 0.115 |

- `--sw` (per site, light and dark): the hero statement's width on 1 line in em; dark mode adds 0.01em of tracking, hence the larger dark value. `font-size: clamp(2.6rem, 98cqw / var(--sw), 5rem)` puts the statement on 1 line whenever that fits at 2.6rem or more. The dark value applies in system dark mode and under a forced dark scheme.
- `--logo-inset` (per site): the logo artwork's visible left margin as a fraction of the SVG box, used to indent the rail's text under the artwork's left edge.
- A site re-measures only when its statement's words or its logo change, as a development task on any machine with Node and Playwright, never as a build step: `node tools/measure.js statement <url or file of the landing page>` prints the light and dark `--sw`; `node tools/measure.js logo logos/<site>.svg` prints `--logo-inset`. Set the new values in the site's own `site.css`, loaded after `baseline.css` (`.site-<name> .statement { --sw: ...; }`, the dark value inside `@media (prefers-color-scheme: dark)`, and `.site-<name> .rail { --logo-inset: ...; }`). Never edit the copied `baseline.css`: the pin and its checksum must keep matching. The tool makes no network requests other than loading the page you name (`npx playwright install chromium` once).

## Security contact

Each domain publishes `/.well-known/security.txt` with the W3 fields, pointing at the owner's vulnerability disclosure policy, `https://posluns.ca/security/`; a gate checks the file. The disclosure page's closing line links the same policy and the universal privacy policy, `https://posluns.ca/privacy/`.

## Checking a build

- No en or em dash in any file; no inline style or script (JSON-LD data blocks excepted, live pages only); no `on*` attribute; every external link with `target="_blank" rel="noopener noreferrer"` and a visually hidden "(opens in a new tab)".
- Render at 390, 820, 1180, 1512 and 2000 px in light and dark: no horizontal scroll; the top bar's links on 1 row at every width (scrolling on phones, with the edge shadow), GitHub last where present; exactly 1 GitHub mark displayed in the footer and no blank tile; the footer on 1 line from 600 px of content width and on 2 lines below it; the hero statement on 1 line at 1440 px and above for a 1-sentence statement, in both schemes.
- The landing page within 2 screens at 1180 by 820; body text 20 px on desktop and 18 px on phones.
