-
-
{
- "verified": true,
- "confidence": 0.92,
- "sources": ["source-1"]
+
+
+
+
The bug normal tests miss
Same shape.
Different contract.
+
Pydantic can validate a boolean. It cannot prove the boolean still means what the next node assumes.
+
+
+
+
+
{
+ "verified": true,
+ "confidence": 0.92,
+ "sources": ["source-1"]
}
-
Source opened and shown to support the claim.
-
-
-
-
-
{
- "verified": true,
- "confidence": 0.92,
- "sources": ["source-1"]
+ Source opened and shown to support the claim.
+
+
+
+
+
{
+ "verified": true,
+ "confidence": 0.92,
+ "sources": ["source-1"]
}
-
Source access failed; the claim merely sounded plausible.
+
Source access failed; the claim merely sounded plausible.
+
+
The JSON is identical. The meaning at researcher → verifier is not.
- The JSON is identical. The meaning at researcher → verifier is not.
-
-
-
The graph is the explanation
The pulse stops.
The evidence begins.
-
Failure does not propagate as a successful execution. The graph freezes at the incompatible edge, then marks the downstream blast radius.
+
+
+
How GraphABI works
One causal sequence.
Every interface follows the same reasoning path. Nothing animates without explaining a step.
+
+ - 01Flow
Record what crosses each graph edge.
+ - 02Check
Evaluate what the consumer relies on.
+ - 03Break
Stop at the first incompatible meaning.
+ - 04Trace
Calculate downstream terminal paths.
+ - 05Explain
Show the exact run and conflicting value.
+ - 06Fix
Name the nearest repair location.
+
-
-
producerconsumer
-
semantic value in flightexplicit consumer contractrecorded witness
-
-
-
-
- How GraphABI works
One causal sequence.
Every interface follows the same reasoning path. Nothing animates without explaining a step.
-
- - 01Flow
Record what crosses each graph edge.
- - 02Check
Evaluate what the consumer relies on.
- - 03Break
Stop at the first incompatible meaning.
- - 04Trace
Calculate downstream terminal paths.
- - 05Explain
Show the exact run and conflicting value.
- - 06Fix
Name the nearest repair location.
-
-
- Consumer-driven edge contracts
Write the assumption down.
The verifier defines what it needs from the researcher. Evaluators stay deterministic and independently extensible.
-
-
version: "0.1"
+
+
+
Consumer-driven edge contracts
Write the assumption down.
The verifier defines what it needs from the researcher. Evaluators stay deterministic and independently extensible.
+
+
+
graphabi.contracts.yaml
+
version: "0.1"
graph: research_demo
edges:
- id: researcher_to_verifier
producer: researcher
consumer: verifier
invariants:
- - id: verified_requires_opened_source
- evaluator: implication
- severity: breaking
- when:
- path: output.verified
- equals: true
- require:
- path: metadata.opened_sources_count
- greater_than: 0
-
+ -
id: verified_requires_opened_supporting_source
+
evaluator: provenance
+
severity: breaking
+
rule: opened_supporting_source
+
+
+
-
- Run the proof locally
The failure is real output.
The demo executes baseline and candidate LangGraphs, records both in SQLite, and generates offline JSON and HTML.
-
-
graphabi demo --allow-breaking
-
GraphABI semantic compatibility report
+
+
+
Run the proof locally
The failure is real output.
The demo executes baseline and candidate LangGraphs, records both in SQLite, and generates offline JSON and HTML.
+
+
graphabi demo --allow-breaking
+
GraphABI semantic compatibility report
Structural compatibility: PASS
Semantic compatibility: FAIL
First breaking edge: researcher → verifier
@@ -193,62 +272,202 @@ Your schema passed.
Your agent still broke.<
Reports:
.graphabi/reports/latest/report.json
.graphabi/reports/latest/index.html
+
-
- Framework-independent core
Adapters observe.
Core decides.
LangGraph integration ends at a versioned trace boundary. Contracts, comparison, impact, and reporting do not import framework types.
-
+
+
+
Framework-independent core
Adapters observe.
Core decides.
LangGraph integration ends at a versioned trace boundary. Contracts, comparison, impact, and reporting do not import framework types.
+
+
Framework-independent semantic compatibility pipeline
+
+ -
+ Flow
+ Adapters
+ LangGraph today
+
+ - TraceBundle 0.1
+ -
+ Record
+ Trace model
+ SQLite · JSONL
+
+
+ -
+ Check
+ Contracts
+ Evaluator registry
+
+
+ -
+ Trace
+ Impact
+ NetworkX paths
+
+ - CompatibilityReport 0.1
+ -
+ Explain
+ Reports
+ JSON · HTML
+
+ - Framework-facing
+ - Framework-independent core
+
+
Framework types stop at the adapter boundary. Report presentation never decides compatibility.
+
+
-
- A report that explains causality
Not a score.
A repairable witness.
Machine-readable JSON and a self-contained HTML report come from one redacted report model. The report works offline.
-
Exact demo state: candidate-003 · researcher → verifier · publisher affected
+
+
+
A report that explains causality
Not a score.
A repairable witness.
Machine-readable JSON and a self-contained HTML report come from one redacted report model. The report works offline.
+
+
+
report.jsonmachine-readable
+
{
+ "structural": "pass",
+ "semantic": "breaking",
+ "first_breaking_edge": {
+ "producer": "researcher",
+ "consumer": "verifier",
+ "contract": "verified_requires_opened_supporting_source"
+ },
+ "witness": {
+ "run": "candidate-003",
+ "expected": "opened_sources_count > 0",
+ "observed": 0
+ },
+ "affected": ["verifier", "decision_maker", "publisher"],
+ "repair_before": "verifier"
+}
+
+
+
index.htmloffline, self-contained
+
+
+
Structural✓ PASS
+
Semantic× BREAKING
+
+
+
First breaking edge
+
researcherverifier
+
+
+
Witness · candidate-003
+
opened_sources_count > 0 expected, 0 observed.
+
+
+
Downstream
+
verifierdecision_makerpublisher
+
+
+
Repair location
+
Before verifier
+
+
+
+
+
One redacted report model. Both renderings are generated from it, and neither can disagree with the other.
+
+
+
+
+
+
Measured technical proof
Small surface.
Serious checks.
Current branch measurements, linked to the source that enforces them.
+
+ - Tests
- 112passing locally
+ - Coverage
- 92.94%85% CI floor
+ - Python CI
- 3.12 · 3.13both required
+ - Evaluators
- 7 typesdeterministic registry
+ - Runtime
- Local-firstno required API key
+ - Adapter
- LangGraphone maintained adapter
+
+
Benchmarks cover synthetic graphs at 10, 100, and 1,000 nodes. They do not establish production scale. Inspect the methodology.
+
+
+
+
+
+
Proof boundaryWhat GraphABI does not prove.
Honest uncertainty is part of the interface.
+
+ - 01
GraphABI evaluates explicit contracts. It does not understand arbitrary meaning.
+ - 02
A passing observed edge does not prove unobserved branches or inputs are compatible.
+ - 03
Trace schema 0.1 does not yet pair repeated loop or retry occurrences.
+ - 04
LangGraph is the only maintained adapter. OpenTelemetry and OpenInference ingestion are planned.
+
+
Read every current limitation ↗
+
-
- Public alpha roadmap
The next edges are explicit.
Planned means planned, not hidden behind a maturity claim.
-
-
01Causal pairing for loopsPlanned
-
02Contract coveragePlanned
-
03OpenTelemetry ingestionPlanned
-
04Second framework adapterPlanned
-
05Model-migration examplePlanned
+
+
+
Choose a real edge
Three useful ways in.
Each path has an architecture boundary, acceptance criteria, and a maintainer review point.
+
+
-
-
- Semantic Compatibility Infrastructure
- Find where meaning broke.
- Run GraphABI locally, inspect the witness, and help define the edge-level contracts agent graphs need.
-
+
+
+
+
+
Semantic Compatibility Infrastructure
+
Catch the first broken edge.
+
Run the local demo, read the contract, and inspect the trace-backed witness.
+
+
+
-
+
-
+
-
-
-
-
+
+
+
+
-
Run from Git with uvAvailable now
Run the deterministic demo without a local install or API key.
uvx --from git+https://github.com/graphabi/graphabi graphabi demo --allow-breaking
-
Run from Git with pipxAvailable now
Use pipx to run the CLI directly from the public repository. GraphABI is not published to PyPI.
pipx run --spec git+https://github.com/graphabi/graphabi graphabi demo --allow-breaking
-
Invoke from GitHub ActionsCLI invocation
No official composite action is shipped yet. Invoke the same local CLI in a workflow step.
- run: uvx --from git+https://github.com/graphabi/graphabi graphabi demo --allow-breaking
-
Instrument LangGraphAdapter available
Install GraphABI from Git, then run the included LangGraph research graph or add the adapter to your trace boundary.
uv add "graphabi @ git+https://github.com/graphabi/graphabi"
-
OpenTelemetry ingestionPlanned
OpenTelemetry ingestion is on the public roadmap and is not available in v0.1.0-alpha.1.
echo "OpenTelemetry ingestion is planned, not shipped"
+
Run from Git with uvAvailable now
Run the deterministic demo without a local install or API key.
$uvx --from git+https://github.com/graphabi/graphabi graphabi demo --allow-breaking
+
Run from Git with pipxAvailable now
Use pipx to run the CLI directly from the public repository. GraphABI is not published to PyPI.
$pipx run --spec git+https://github.com/graphabi/graphabi graphabi demo --allow-breaking
+
Clone and sync the repositoryAvailable now
Use the locked development environment for examples, tests, and contribution work.
$git clone https://github.com/graphabi/graphabi.git && cd graphabi && uv sync
+
Instrument LangGraphAdapter available
Install GraphABI from Git, then run the included LangGraph research graph or add the adapter to your trace boundary.
$uv add "graphabi @ git+https://github.com/graphabi/graphabi"
diff --git a/motion.js b/motion.js
index 3d9beaf..e543d0f 100644
--- a/motion.js
+++ b/motion.js
@@ -1,137 +1,383 @@
+/* =============================================================================
+ GraphABI: product motion
+ -----------------------------------------------------------------------------
+ Everything here is causal. Motion exists to show where meaning flowed, what
+ checked it, where it stopped, and what that reached. Nothing loops, and no
+ state is expressed by motion alone: the final frame always carries the
+ whole result in text and colour.
+ ========================================================================== */
+
(() => {
"use strict";
- const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
- const timersByScene = new WeakMap();
+ const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const $ = (sel, root) => (root || document).querySelector(sel);
+ const $$ = (sel, root) => [...(root || document).querySelectorAll(sel)];
+
+ /* ------------------------------------------------- edge measurement ---
+ Pulses travel with transform, which needs the edge's pixel length.
+ One ResizeObserver keeps the custom property honest across breakpoints. */
+
+ const measured = $$(".graph-edge, .playground-edge");
+ if (measured.length && "ResizeObserver" in window) {
+ const ro = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ const el = entry.target;
+ el.style.setProperty("--edge-w", el.offsetWidth + "px");
+ el.style.setProperty("--edge-h", el.offsetHeight + "px");
+ }
+ });
+ measured.forEach((el) => ro.observe(el));
+ }
+
+ /* --------------------------------------------------- canonical replay --- */
+
+ const timers = new WeakMap();
- const clearScene = (scene) => {
- (timersByScene.get(scene) || []).forEach(window.clearTimeout);
- timersByScene.set(scene, []);
+ const clear = (scene) => {
+ (timers.get(scene) || []).forEach(clearTimeout);
+ timers.set(scene, []);
};
- const schedule = (scene, delay, action) => {
- const timers = timersByScene.get(scene) || [];
- timers.push(window.setTimeout(action, delay));
- timersByScene.set(scene, timers);
+ const after = (scene, delay, fn) => {
+ const list = timers.get(scene) || [];
+ list.push(setTimeout(fn, delay));
+ timers.set(scene, list);
};
const announce = (scene, text) => {
- const phase = scene.querySelector("[data-scene-phase]");
- const live = scene.querySelector("[data-scene-live]");
+ const phase = $("[data-scene-phase]", scene);
+ const live = $("[data-scene-live]", scene);
if (phase) phase.textContent = text;
if (live) live.textContent = text;
};
const finish = (scene) => {
+ clear(scene);
scene.className = "pulse-scene is-complete";
- announce(scene, "Break found: researcher to verifier");
- const button = scene.querySelector("[data-replay]");
+ announce(scene, "Break found · researcher → verifier");
+ const button = $("[data-replay]", scene);
if (button) button.disabled = false;
};
const play = (scene) => {
- clearScene(scene);
- if (reducedMotion.matches) {
- finish(scene);
- return;
- }
- const button = scene.querySelector("[data-replay]");
+ clear(scene);
+ if (reduced.matches) return finish(scene);
+
+ const button = $("[data-replay]", scene);
if (button) button.disabled = true;
scene.className = "pulse-scene is-playing";
- announce(scene, "Run begins: loading recorded nodes");
- schedule(scene, 180, () => { scene.classList.add("nodes-active"); announce(scene, "Flow: nodes activated"); });
- schedule(scene, 720, () => { scene.classList.add("baseline-active"); announce(scene, "Check: baseline contracts pass"); });
- schedule(scene, 1900, () => { scene.classList.remove("baseline-active"); scene.classList.add("candidate-active"); announce(scene, "Candidate swapped: schema remains valid"); });
- schedule(scene, 2550, () => { scene.classList.add("broken-active"); announce(scene, "Break: researcher to verifier"); });
- schedule(scene, 2870, () => { scene.classList.add("impact-active"); announce(scene, "Trace: downstream impact identified"); });
- schedule(scene, 3370, () => { scene.classList.add("witness-active"); announce(scene, "Explain: trace-backed witness revealed"); });
- schedule(scene, 3870, () => finish(scene));
+ announce(scene, "Loading recorded nodes");
+
+ // Flow, check, break, trace, explain. The order is the product.
+ // Hop length and blast stagger are mirrored in styles.css.
+ after(scene, 160, () => { scene.classList.add("nodes-active"); announce(scene, "Flow · nodes resolved in topology order"); });
+ after(scene, 700, () => { scene.classList.add("baseline-active"); announce(scene, "Check · baseline pulse crosses every edge"); });
+ after(scene, 2060, () => { scene.classList.add("resetting"); });
+ after(scene, 2400, () => {
+ scene.classList.remove("baseline-active", "resetting");
+ scene.classList.add("candidate-active");
+ announce(scene, "Candidate swapped · schema still valid");
+ });
+ after(scene, 3340, () => { scene.classList.add("broken-active"); announce(scene, "Break · researcher → verifier"); });
+ after(scene, 3760, () => { scene.classList.add("impact-active"); announce(scene, "Trace · downstream impact identified"); });
+ after(scene, 4560, () => { scene.classList.add("witness-active"); announce(scene, "Explain · trace-backed witness recorded"); });
+ after(scene, 5200, () => finish(scene));
};
- document.querySelectorAll("[data-pulse-scene]").forEach((scene) => {
- const button = scene.querySelector("[data-replay]");
+ $$("[data-pulse-scene]").forEach((scene) => {
+ const button = $("[data-replay]", scene);
if (button) button.addEventListener("click", () => play(scene));
- window.setTimeout(() => play(scene), 260);
+
+ // Autoplay once, only when the scene is actually on screen, and never
+ // move focus while it runs.
+ if ("IntersectionObserver" in window) {
+ const io = new IntersectionObserver((entries) => {
+ entries.forEach((entry) => {
+ if (!entry.isIntersecting) return;
+ io.disconnect();
+ setTimeout(() => play(scene), 240);
+ });
+ }, { threshold: 0.25 });
+ io.observe(scene);
+ } else {
+ setTimeout(() => play(scene), 240);
+ }
});
- const playground = document.querySelector("[data-playground]");
+ /* ------------------------------------------------------- playground --- */
+
+ const playground = $("[data-playground]");
if (playground) {
+ const result = $("[data-play-result]", playground);
+ const choices = $$("[data-play-choice]", playground);
+ const thumb = $(".segment-thumb", playground);
+ const run = $("[data-play-run]", playground);
+ const witness = $("[data-play-witness]", playground);
let choice = "baseline";
- const result = playground.querySelector("[data-play-result]");
- const choices = [...playground.querySelectorAll("[data-play-choice]")];
+ let witnessTimer = 0;
+
+ const moveThumb = () => {
+ const active = choices.find((b) => b.classList.contains("is-selected"));
+ if (!active || !thumb) return;
+ thumb.style.width = active.offsetWidth + "px";
+ thumb.style.transform = `translateX(${active.offsetLeft - choices[0].offsetLeft}px)`;
+ };
+
+ const reset = () => {
+ clearTimeout(witnessTimer);
+ playground.className = "playground";
+ if (witness) {
+ witness.hidden = true;
+ witness.classList.remove("is-open");
+ }
+ if (result) {
+ result.innerHTML =
+ "Select an output and run the check." +
+ "The graph will report the observed contract state here.";
+ }
+ };
+
+ const sources = $("[data-play-sources]", playground);
+
const setChoice = (value) => {
choice = value;
+ // The candidate keeps the schema and changes one recorded value.
+ if (sources) {
+ sources.textContent = value === "candidate" ? "0" : "1";
+ sources.classList.toggle("is-breaking", value === "candidate");
+ sources.classList.toggle("is-passing", value !== "candidate");
+ }
choices.forEach((button) => {
- const selected = button.dataset.playChoice === value;
- button.classList.toggle("is-selected", selected);
- button.setAttribute("aria-pressed", String(selected));
+ const on = button.dataset.playChoice === value;
+ button.classList.toggle("is-selected", on);
+ button.setAttribute("aria-pressed", String(on));
});
- playground.className = "playground";
- if (result) result.innerHTML = "Select an output and run the check.The graph will report the observed contract state here.";
+ moveThumb();
+ reset();
};
- choices.forEach((button) => button.addEventListener("click", () => setChoice(button.dataset.playChoice)));
- const run = playground.querySelector("[data-play-run]");
- if (run) run.addEventListener("click", () => {
- playground.className = `playground is-running ${choice === "candidate" ? "is-candidate" : "is-baseline"}`;
- if (result) result.innerHTML = choice === "candidate"
- ? "BREAK: verified=true, opened_sources_count=0.The pulse stopped at researcher to verifier. The witness is the missing source access."
- : "PASS: verified=true, opened_sources_count=1.The pulse reached verifier with the supporting source recorded.";
- if (reducedMotion.matches) playground.classList.add("is-settled");
- });
+
+ choices.forEach((b) => b.addEventListener("click", () => setChoice(b.dataset.playChoice)));
+ setChoice("baseline");
+ requestAnimationFrame(moveThumb);
+ window.addEventListener("resize", moveThumb, { passive: true });
+ // Inter arriving late changes the label widths the thumb is sized from.
+ if (document.fonts && document.fonts.ready) document.fonts.ready.then(moveThumb);
+
+ if (run) {
+ run.addEventListener("click", () => {
+ // Restart cleanly so a second run replays rather than doing nothing.
+ playground.className = "playground";
+ void playground.offsetWidth;
+ playground.className = `playground is-running ${choice === "candidate" ? "is-candidate" : "is-baseline"}`;
+ if (!result) return;
+ if (choice === "candidate") {
+ result.innerHTML =
+ "BREAKING · verified=true with opened_sources_count=0." +
+ "First break: researcher → verifier. Affected: verifier, decision_maker, publisher.";
+ if (witness) {
+ witnessTimer = setTimeout(() => {
+ witness.hidden = false;
+ requestAnimationFrame(() => witness.classList.add("is-open"));
+ }, reduced.matches ? 0 : 980);
+ }
+ } else {
+ result.innerHTML =
+ "PASS · verified=true with opened_sources_count=1." +
+ "The observed baseline satisfied the verifier's explicit contract.";
+ }
+ });
+ }
}
- const modal = document.querySelector("[data-setup-modal]");
+ /* ------------------------------------------------------------ modal --- */
+
+ const modal = $("[data-setup-modal]");
if (modal) {
- const dialog = modal.querySelector(".setup-dialog");
- const openers = [...document.querySelectorAll("[data-open-setup]")];
- const closers = [...modal.querySelectorAll("[data-close-setup]")];
- const tabs = [...modal.querySelectorAll("[data-setup-option]")];
- const panels = [...modal.querySelectorAll("[data-setup-panel]")];
- let previousFocus;
- const focusables = () => [...modal.querySelectorAll('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])')];
- const close = () => {
- modal.classList.remove("is-open");
- window.setTimeout(() => { modal.hidden = true; document.body.classList.remove("modal-open"); }, reducedMotion.matches ? 0 : 240);
- if (previousFocus) previousFocus.focus();
+ const dialog = $(".setup-dialog", modal);
+ const tabs = $$("[data-setup-option]", modal);
+ const panels = $$("[data-setup-panel]", modal);
+ const rail = $(".tab-rail", modal);
+ let previous = null;
+
+ const focusable = () =>
+ $$('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])', modal)
+ .filter((el) => el.offsetParent !== null || el === dialog);
+
+ const moveRail = () => {
+ const active = tabs.find((t) => t.classList.contains("is-active"));
+ if (!active || !rail) return;
+ rail.style.width = active.offsetWidth + "px";
+ rail.style.transform = `translateX(${active.offsetLeft}px)`;
};
- const open = () => {
- previousFocus = document.activeElement;
+
+ const select = (value, focus) => {
+ tabs.forEach((tab) => {
+ const on = tab.dataset.setupOption === value;
+ tab.classList.toggle("is-active", on);
+ tab.setAttribute("aria-selected", String(on));
+ tab.tabIndex = on ? 0 : -1;
+ if (on && focus) tab.focus();
+ });
+ panels.forEach((panel) => {
+ const on = panel.dataset.setupPanel === value;
+ panel.hidden = !on;
+ panel.classList.toggle("is-active", on);
+ });
+ moveRail();
+ };
+
+ // The dialog expands from whichever control opened it, so the panel reads
+ // as that node unfolding rather than as a sheet arriving from nowhere.
+ const open = (event) => {
+ previous = document.activeElement;
modal.hidden = false;
document.body.classList.add("modal-open");
- window.requestAnimationFrame(() => { modal.classList.add("is-open"); dialog.focus(); });
- };
- const select = (value) => {
- tabs.forEach((tab) => {
- const active = tab.dataset.setupOption === value;
- tab.classList.toggle("is-active", active);
- tab.setAttribute("aria-selected", String(active));
+
+ const trigger = event && event.currentTarget;
+ if (trigger && !reduced.matches) {
+ const t = trigger.getBoundingClientRect();
+ requestAnimationFrame(() => {
+ const d = dialog.getBoundingClientRect();
+ if (d.width && d.height) {
+ const ox = ((t.left + t.width / 2 - d.left) / d.width) * 100;
+ const oy = ((t.top + t.height / 2 - d.top) / d.height) * 100;
+ dialog.style.transformOrigin =
+ `${Math.max(-20, Math.min(120, ox))}% ${Math.max(-20, Math.min(120, oy))}%`;
+ }
+ });
+ } else {
+ dialog.style.transformOrigin = "";
+ }
+
+ requestAnimationFrame(() => {
+ modal.classList.add("is-open");
+ moveRail();
+ const activeTab = tabs.find((tab) => tab.getAttribute("aria-selected") === "true");
+ (activeTab || dialog).focus();
});
- panels.forEach((panel) => { panel.hidden = panel.dataset.setupPanel !== value; panel.classList.toggle("is-active", panel.dataset.setupPanel === value); });
};
- openers.forEach((button) => button.addEventListener("click", open));
- closers.forEach((button) => button.addEventListener("click", close));
+
+ const close = () => {
+ modal.classList.remove("is-open");
+ const done = () => {
+ modal.hidden = true;
+ document.body.classList.remove("modal-open");
+ if (previous && previous.isConnected) previous.focus();
+ };
+ reduced.matches ? done() : setTimeout(done, 240);
+ };
+
+ $$("[data-open-setup]").forEach((b) => b.addEventListener("click", open));
+ $$("[data-close-setup]", modal).forEach((b) => b.addEventListener("click", close));
tabs.forEach((tab) => tab.addEventListener("click", () => select(tab.dataset.setupOption)));
+
modal.addEventListener("keydown", (event) => {
- if (event.key === "Escape") { event.preventDefault(); close(); return; }
+ if (event.key === "Escape") { event.preventDefault(); return close(); }
+
+ // Roving tabs, as the tablist pattern expects.
+ if (event.target.matches("[data-setup-option]") && /^Arrow(Left|Right)$/.test(event.key)) {
+ event.preventDefault();
+ const i = tabs.indexOf(event.target);
+ const next = event.key === "ArrowRight" ? (i + 1) % tabs.length : (i - 1 + tabs.length) % tabs.length;
+ return select(tabs[next].dataset.setupOption, true);
+ }
+
if (event.key !== "Tab") return;
- const items = focusables();
+ const items = focusable();
if (!items.length) return;
- const first = items[0]; const last = items[items.length - 1];
+ const first = items[0];
+ const last = items[items.length - 1];
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
});
- modal.querySelectorAll("[data-copy-command]").forEach((button) => button.addEventListener("click", async () => {
+
+ window.addEventListener("resize", moveRail, { passive: true });
+ }
+
+ /* -------------------------------------------------------------- copy --- */
+
+ const copy = async (text) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ return true;
+ } catch (_) {
+ const area = document.createElement("textarea");
+ area.value = text;
+ area.setAttribute("readonly", "");
+ area.style.cssText = "position:fixed;top:0;left:0;opacity:0";
+ document.body.appendChild(area);
+ area.select();
+ let ok = false;
+ try { ok = document.execCommand("copy"); } catch (__) { ok = false; }
+ area.remove();
+ return ok;
+ }
+ };
+
+ $$("[data-copy-command]").forEach((button) => {
+ button.addEventListener("click", async () => {
const panel = button.closest(".setup-panel");
- const command = panel.querySelector("[data-setup-command]").textContent;
- try { await navigator.clipboard.writeText(command); } catch (error) {
- const area = document.createElement("textarea"); area.value = command; area.setAttribute("readonly", ""); area.style.position = "fixed"; area.style.opacity = "0"; document.body.appendChild(area); area.select(); document.execCommand("copy"); area.remove();
- }
- button.classList.add("copied");
- const feedback = panel.querySelector("[data-copy-feedback]");
- if (feedback) feedback.textContent = "Copied";
- window.setTimeout(() => { button.classList.remove("copied"); if (feedback) feedback.textContent = ""; }, 1600);
- }));
+ const source = $("[data-setup-command]", panel);
+ const feedback = $("[data-copy-feedback]", panel);
+ const ok = await copy(source.textContent.trim());
+ button.classList.toggle("copied", ok);
+ if (feedback) feedback.textContent = ok ? "Copied" : "Press Ctrl+C to copy";
+ setTimeout(() => {
+ button.classList.remove("copied");
+ if (feedback) feedback.textContent = "";
+ }, 1800);
+ });
+ });
+
+ $$("[data-copy-inline]").forEach((button) => {
+ const line = button.closest(".install-line");
+ const source = $("[data-copy-source]", line);
+ button.addEventListener("click", async () => {
+ const ok = await copy(source.textContent.trim());
+ button.classList.toggle("copied", ok);
+ button.setAttribute("aria-label", ok ? "Command copied" : "Copy demo command");
+ setTimeout(() => {
+ button.classList.remove("copied");
+ button.setAttribute("aria-label", "Copy demo command");
+ }, 1800);
+ });
+ });
+
+ /* -------------------------------------------------- scroll traversal ---
+ Reaching a section is arriving at a node: the rail from the previous
+ section draws down, then the node resolves. Once only, never reversed. */
+
+ if ("IntersectionObserver" in window) {
+ const io = new IntersectionObserver((entries) => {
+ entries.forEach((entry) => {
+ if (!entry.isIntersecting) return;
+ entry.target.classList.add("is-reached");
+ io.unobserve(entry.target);
+ });
+ }, { rootMargin: "0px 0px -22% 0px", threshold: 0 });
+ $$(".section").forEach((section) => io.observe(section));
+ } else {
+ $$(".section").forEach((section) => section.classList.add("is-reached"));
}
- reducedMotion.addEventListener("change", () => document.querySelectorAll("[data-pulse-scene]").forEach(finish));
+ /* ------------------------------------------------------------ header --- */
+
+ const header = $(".site-header");
+ if (header) {
+ const sentinel = document.createElement("div");
+ sentinel.setAttribute("aria-hidden", "true");
+ sentinel.style.cssText = "position:absolute;top:0;height:1px;width:1px";
+ document.body.prepend(sentinel);
+ if ("IntersectionObserver" in window) {
+ new IntersectionObserver(
+ ([entry]) => header.classList.toggle("is-stuck", !entry.isIntersecting),
+ { threshold: 0 }
+ ).observe(sentinel);
+ }
+ }
+
+ reduced.addEventListener("change", () => {
+ if (reduced.matches) $$("[data-pulse-scene]").forEach(finish);
+ });
})();
diff --git a/proof.json b/proof.json
new file mode 100644
index 0000000..ee33713
--- /dev/null
+++ b/proof.json
@@ -0,0 +1,20 @@
+{
+ "tests": 112,
+ "coverage_percent": 92.94,
+ "python_versions": [
+ "3.12",
+ "3.13"
+ ],
+ "evaluator_names": [
+ "authority",
+ "completeness",
+ "freshness",
+ "implication",
+ "provenance",
+ "set_preservation",
+ "unit_consistency"
+ ],
+ "maintained_adapters": [
+ "langgraph"
+ ]
+}
diff --git a/scripts/check_site.py b/scripts/check_site.py
new file mode 100644
index 0000000..3bc4d97
--- /dev/null
+++ b/scripts/check_site.py
@@ -0,0 +1,88 @@
+"""Verify local links and progressive-enhancement invariants."""
+
+from __future__ import annotations
+
+import json
+from html.parser import HTMLParser
+from pathlib import Path
+from urllib.parse import urlsplit
+
+
+ROOT = Path(__file__).parents[1]
+
+
+class SiteParser(HTMLParser):
+ def __init__(self) -> None:
+ super().__init__()
+ self.ids: set[str] = set()
+ self.references: list[tuple[str, str]] = []
+ self.field_is_hidden = False
+ self.proof_values: dict[str, str] = {}
+ self.active_proof: str | None = None
+
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
+ values = dict(attrs)
+ if identifier := values.get("id"):
+ self.ids.add(identifier)
+ for name in ("href", "src"):
+ if reference := values.get(name):
+ self.references.append((name, reference))
+ if tag == "canvas" and "data-field" in values:
+ self.field_is_hidden = values.get("aria-hidden") == "true"
+ if proof := values.get("data-proof"):
+ self.active_proof = proof
+
+ def handle_data(self, data: str) -> None:
+ if self.active_proof:
+ self.proof_values[self.active_proof] = (
+ self.proof_values.get(self.active_proof, "") + data
+ )
+
+ def handle_endtag(self, tag: str) -> None:
+ if tag == "strong" and self.active_proof:
+ self.proof_values[self.active_proof] = self.proof_values[self.active_proof].strip()
+ self.active_proof = None
+
+
+def main() -> int:
+ parser = SiteParser()
+ parser.feed((ROOT / "index.html").read_text(encoding="utf-8"))
+ errors: list[str] = []
+
+ for attribute, reference in parser.references:
+ parsed = urlsplit(reference)
+ if parsed.scheme or parsed.netloc:
+ continue
+ if parsed.path:
+ target = ROOT / parsed.path
+ if not target.exists():
+ errors.append(f"{attribute} references missing local file: {reference}")
+ if parsed.fragment and parsed.fragment not in parser.ids:
+ errors.append(f"{attribute} references missing fragment: #{parsed.fragment}")
+
+ if not parser.field_is_hidden:
+ errors.append("The decorative field must remain aria-hidden.")
+
+ proof = json.loads((ROOT / "proof.json").read_text(encoding="utf-8"))
+ expected_proof = {
+ "tests": str(proof["tests"]),
+ "coverage": f"{proof['coverage_percent']:.2f}%",
+ "python": " · ".join(proof["python_versions"]),
+ "evaluators": f"{len(proof['evaluator_names'])} types",
+ }
+ if parser.proof_values != expected_proof:
+ errors.append(
+ f"Public proof does not match proof.json: {parser.proof_values!r} != {expected_proof!r}"
+ )
+
+ if errors:
+ print("Site verification failed:")
+ for error in errors:
+ print(f"- {error}")
+ return 1
+ print("Local links, fragments, and field accessibility checks passed.")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_text.py b/scripts/check_text.py
new file mode 100644
index 0000000..71afe6d
--- /dev/null
+++ b/scripts/check_text.py
@@ -0,0 +1,36 @@
+"""Reject prohibited punctuation in tracked text files."""
+
+from __future__ import annotations
+
+import subprocess
+from pathlib import Path
+
+
+ROOT = Path(__file__).parents[1]
+
+
+def main() -> int:
+ names = subprocess.check_output(["git", "ls-files", "-z"], cwd=ROOT).split(b"\0")
+ offenders: list[str] = []
+ for name in names:
+ if not name:
+ continue
+ path = ROOT / name.decode()
+ try:
+ text = path.read_text(encoding="utf-8")
+ except (UnicodeDecodeError, OSError):
+ continue
+ if chr(0x2014) in text:
+ offenders.append(str(path.relative_to(ROOT)))
+
+ if offenders:
+ print("Em dash characters are not allowed in tracked project text:")
+ for offender in offenders:
+ print(f"- {offender}")
+ return 1
+ print("Tracked project text contains no em dash characters.")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/styles.css b/styles.css
index 627e2a4..1c5d976 100644
--- a/styles.css
+++ b/styles.css
@@ -1,275 +1,2156 @@
+/* ==========================================================================
+ GraphABI: Semantic Pulse
+ Two graph languages, in deliberate opposition:
+ - the ambient field is organic, continuous and never resolves;
+ - every product surface is precise, deterministic, and stops.
+ ========================================================================== */
+
:root {
color-scheme: light dark;
- --bg:#F8FAFC;
- --surface:#FFFFFF;
- --surface-strong:#F1F5F9;
- --primary:#7C3AED;
- --pulse:#7C3AED;
- --pass:#15803D;
- --fail:#DC2626;
- --unknown:#B45309;
- --edge:#64748B;
- --text:#0B0F14;
- --muted:#475569;
- --subtle:#64748B;
- --border:#CBD5E1;
- --space-1:8px; --space-2:16px; --space-3:24px; --space-4:32px; --space-5:40px; --space-6:48px; --space-7:64px; --space-8:80px; --space-9:96px;
- --sans:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
- --mono:"SFMono-Regular","Cascadia Code","Roboto Mono",ui-monospace,monospace;
- --ease-out-quint:cubic-bezier(.22,1,.36,1);
+
+ /* Light theme is designed, not inverted. */
+ --bg: #FBFCFD;
+ --bg-sunk: #F1F4F8;
+ --surface: #FFFFFF;
+ --surface-2: #F5F8FA;
+ --surface-code: #F7F9FB;
+ --border: #E3E8EF;
+ --border-strong: #CBD5E1;
+ --text: #0B0F14;
+ --muted: #4C5866;
+ --subtle: #5D6B7A;
+ --primary: #6D28D9;
+ --primary-hover: #5B21B6;
+ --on-primary: #FFFFFF;
+ --pulse: #7C3AED;
+ --pass: #15803D;
+ --fail: #DC2626;
+ --unknown: #B45309;
+ --edge: #A3B0C0;
+ --code: #1E293B;
+
+ /* Status colours split by role. Strokes, rails and marks only have to
+ clear 3:1 as non-text UI; anything set as small text has to clear 4.5:1
+ on every surface it can land on, including the status tints. */
+ --pass-text: #136B33;
+ --fail-text: #C81E1E;
+ --unknown-text: #9A4708;
+
+ --tint-pulse: rgba(124, 58, 237, .07);
+ --tint-pass: rgba(21, 128, 61, .08);
+ --tint-fail: rgba(220, 38, 38, .06);
+ --edge-pulse: rgba(124, 58, 237, .35);
+ --edge-pass: rgba(21, 128, 61, .35);
+ --edge-fail: rgba(220, 38, 38, .35);
+ --header-bg: rgba(251, 252, 253, .78);
+ --shadow-card: 0 1px 2px rgba(11, 15, 20, .04), 0 8px 24px -12px rgba(11, 15, 20, .10);
+ --shadow-lift: 0 1px 2px rgba(11, 15, 20, .05), 0 18px 48px -20px rgba(11, 15, 20, .22);
+ --shadow-modal: 0 32px 80px -24px rgba(11, 15, 20, .32);
+ --field-alpha: .82;
+
+ /* 8px spacing system. --s0 is the only half-step, for optical alignment. */
+ --s0: 4px; --s1: 8px; --s2: 16px; --s3: 24px; --s4: 32px;
+ --s5: 40px; --s6: 48px; --s7: 64px; --s8: 80px; --s9: 96px;
+ --s10: 128px; --s11: 160px; --s12: 192px;
+
+ --sans: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --mono: "SFMono-Regular", "Cascadia Code", "Roboto Mono", ui-monospace, monospace;
+
+ --ease: cubic-bezier(.22, 1, .36, 1);
+ --ease-io: cubic-bezier(.65, 0, .35, 1);
+ --t-fast: 220ms;
+ --t-mid: 320ms;
+ --t-slow: 500ms;
+
+ /* Where a stopped pulse rests on an edge, as a fraction of edge length.
+ The cut sits at 50%, so the pulse must always come to rest short of it. */
+ --break-stop: .34;
+
+ --radius: 12px;
+ --radius-sm: 8px;
+ --radius-lg: 16px;
+ --gutter: var(--s6);
}
@media (prefers-color-scheme: dark) {
:root {
- color-scheme:dark;
- --bg:#0B0F14;
- --surface:#121820;
- --surface-strong:#18212C;
- --primary:#8B5CF6;
- --pulse:#A78BFA;
- --pass:#22C55E;
- --fail:#EF4444;
- --unknown:#F59E0B;
- --edge:#556070;
- --text:#F8FAFC;
- --muted:#94A3B8;
- --subtle:#64748B;
- --border:#273241;
- --sans:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
- --mono:"SFMono-Regular","Cascadia Code","Roboto Mono",ui-monospace,monospace;
- --ease-out-quint:cubic-bezier(.22,1,.36,1);
+ color-scheme: dark;
+ --bg: #0B0F14;
+ --bg-sunk: #080B0F;
+ --surface: #111820;
+ --surface-2: #18212C;
+ --surface-code: #0D131A;
+ --border: #222E3C;
+ --border-strong: #33414F;
+ --text: #F8FAFC;
+ --muted: #97A5B6;
+ --subtle: #8695A6;
+ --primary: #8B5CF6;
+ --primary-hover: #A78BFA;
+ --on-primary: #0B0F14;
+ --pulse: #A78BFA;
+ --pass: #22C55E;
+ --fail: #EF4444;
+ --unknown: #F59E0B;
+ --edge: #46556A;
+ --code: #D5DDE8;
+
+ --pass-text: #22C55E;
+ --fail-text: #F87171;
+ --unknown-text: #F59E0B;
+
+ --tint-pulse: rgba(167, 139, 250, .10);
+ --tint-pass: rgba(34, 197, 94, .10);
+ --tint-fail: rgba(239, 68, 68, .08);
+ --edge-pulse: rgba(167, 139, 250, .45);
+ --edge-pass: rgba(34, 197, 94, .40);
+ --edge-fail: rgba(239, 68, 68, .42);
+ --header-bg: rgba(11, 15, 20, .72);
+ --shadow-card: 0 1px 0 rgba(255, 255, 255, .02) inset, 0 12px 32px -18px rgba(0, 0, 0, .8);
+ --shadow-lift: 0 1px 0 rgba(255, 255, 255, .03) inset, 0 24px 60px -24px rgba(0, 0, 0, .9);
+ --shadow-modal: 0 40px 96px -28px rgba(0, 0, 0, .85);
+ --field-alpha: 1;
}
}
-* { box-sizing:border-box }
-html { scroll-behavior:smooth; scroll-padding-top:96px }
-body { margin:0; overflow-x:hidden; background:var(--bg); color:var(--text); font:16px/1.65 var(--sans) }
-img { display:block; max-width:100%; height:auto }
-a { color:inherit; text-decoration-color:var(--primary); text-underline-offset:4px }
-button,a { -webkit-tap-highlight-color:transparent }
-:focus-visible { outline:2px solid var(--pulse); outline-offset:3px }
-.skip-link { position:fixed; top:12px; left:16px; z-index:100; min-height:44px; padding:9px 14px; transform:translateY(-180%); border-radius:8px; background:var(--primary); color:white; font-weight:700 }
-.skip-link:focus { transform:none }
-.shell { width:min(1200px,calc(100% - 64px)); margin-inline:auto }
-.sr-only { position:absolute!important; width:1px!important; height:1px!important; padding:0!important; margin:-1px!important; overflow:hidden!important; clip:rect(0 0 0 0)!important; white-space:nowrap!important; border:0!important }
-
-.site-header { position:relative; z-index:10; border-bottom:1px solid rgba(39,50,65,.72); background:rgba(11,15,20,.9) }
-.nav { display:flex; align-items:center; justify-content:space-between; gap:32px; min-height:84px }
-.brand { display:flex; align-items:center; min-height:44px; text-decoration:none }
-.brand img { width:207px }
-.nav-links { display:flex; align-items:center; gap:6px }
-.nav-links a { display:flex; align-items:center; min-height:44px; padding:0 13px; color:var(--muted); text-decoration:none; font-size:13px; font-weight:600 }
-.nav-links a:hover { color:var(--text) }
-.nav-links .nav-github { margin-left:8px; border:1px solid var(--border); border-radius:8px; color:var(--text) }
-.nav-links .nav-github:hover { border-color:var(--primary); background:#171324 }
-
-.hero { padding:104px 0 42px }
-.hero-copy { max-width:990px }
-.eyebrow { margin:0 0 18px; color:var(--pulse); font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase }
-h1,h2,h3,p { margin-top:0 }
-h1,h2,h3 { letter-spacing:-.035em }
-h1 { margin-bottom:30px; font-size:clamp(3.3rem,8.4vw,7rem); font-weight:650; line-height:.94 }
-h1 span { color:var(--fail) }
-h2 { margin-bottom:0; font-size:clamp(2.2rem,5vw,4.25rem); font-weight:640; line-height:1.02 }
-.hero-lede { max-width:750px; margin-bottom:0; color:var(--muted); font-size:clamp(1.08rem,2vw,1.3rem); line-height:1.65 }
-.hero-actions { display:flex; flex-wrap:wrap; gap:12px; margin-top:32px }
-.button { display:inline-flex; align-items:center; justify-content:center; min-height:48px; padding:0 18px; border:1px solid var(--border); border-radius:8px; background:transparent; color:var(--text); font:700 14px var(--sans); text-decoration:none; cursor:pointer; transition:border-color 220ms var(--ease-out-quint),background 220ms var(--ease-out-quint),color 220ms var(--ease-out-quint) }
-.button.primary { border-color:var(--primary); background:var(--primary); color:var(--bg) }
-.button.primary:hover { background:#A78BFA }
-.button.secondary:hover { border-color:var(--primary); background:#171324 }
-.install-line { max-width:820px; margin-top:24px; padding:14px 16px; overflow-x:auto; border:1px solid var(--border); border-left:3px solid var(--pulse); border-radius:8px; background:var(--surface); color:#D8DEE9; white-space:nowrap }
-.install-line code { font:12px/1.5 var(--mono) }
-
-.pulse-scene { margin-top:74px; overflow:hidden; border:1px solid var(--border); border-radius:14px; background:var(--surface) }
-.scene-toolbar { display:flex; align-items:center; justify-content:space-between; gap:24px; min-height:70px; padding:13px 18px 13px 24px; border-bottom:1px solid var(--border) }
-.scene-toolbar > div { display:flex; flex-direction:column; gap:3px }
-.scene-kicker { color:var(--muted); font:700 10px/1.3 var(--mono); letter-spacing:.12em }
-.scene-phase { color:var(--text); font:650 12px/1.4 var(--mono) }
-.replay { min-height:44px; padding:0 15px; border:1px solid var(--border); border-radius:8px; background:transparent; color:var(--text); font:650 13px var(--sans); cursor:pointer }
-.replay:hover { border-color:var(--primary); background:#171324 }
-.replay:disabled { cursor:wait; opacity:.62 }
-.scene-status { display:flex; gap:12px; padding:20px 24px 0 }
-.status { min-width:185px; padding:12px 14px; border:1px solid var(--border); border-radius:9px; background:var(--bg) }
-.status span,.status small { display:block; color:var(--muted); font:700 9px/1.35 var(--mono); letter-spacing:.09em }
-.status strong { display:block; margin:4px 0; font:700 15px/1.3 var(--mono) }
-.schema-status { border-color:#205E39 }.schema-status strong { color:var(--pass) }
-.semantic-status { border-color:#6F2429 }.semantic-status strong { color:var(--fail) }
-.graph-flow { display:grid; grid-template-columns:1.08fr minmax(64px,.55fr) 1fr minmax(58px,.5fr) 1.15fr minmax(58px,.5fr) 1.06fr; align-items:center; padding:46px 24px 42px }
-.graph-node { position:relative; min-width:0; min-height:104px; padding:22px 18px; border:1px solid var(--edge); border-radius:11px; background:var(--surface-strong) }
-.graph-node strong,.graph-node small { display:block; overflow-wrap:anywhere }
-.graph-node strong { font:700 14px/1.4 var(--mono) }
-.graph-node small { margin-top:10px; color:var(--muted); font-size:10px }
-.graph-node.direct { border:2px solid var(--fail); background:#1C1418 }
-.graph-node.direct small { color:var(--fail) }
-.graph-node.affected { border-color:#66363B; background:#17171C }
-.graph-node.affected small { color:#EF8080 }
-.node-pulse { display:block; width:9px; height:9px; margin-bottom:11px; border-radius:50%; background:var(--pulse) }
-.graph-edge { position:relative; height:48px }
-.edge-rail { position:absolute; top:23px; left:0; width:100%; height:3px; background:var(--edge) }
-.first-edge .edge-rail { right:52%; width:48%; height:5px; border-radius:3px; background:var(--pulse) }
-.pulse { position:absolute; top:18px; left:calc(48% - 5px); width:13px; height:13px; border-radius:50%; background:var(--pulse) }
-.break-mark { position:absolute; top:10px; left:calc(50% + 4px); width:22px; height:30px }
-.break-mark i { position:absolute; left:3px; width:19px; height:4px; border-radius:3px; background:var(--fail); transform:rotate(42deg); transform-origin:left center }
-.break-mark i:first-child { top:2px }.break-mark i:last-child { top:17px }
-.edge-caption { position:absolute; top:-12px; left:50%; color:var(--fail); font:700 9px var(--sans); letter-spacing:.1em; white-space:nowrap; transform:translateX(-50%) }
-.blast-edge .edge-rail { height:2px; background:repeating-linear-gradient(90deg,var(--fail) 0 6px,transparent 6px 13px) }
-.scene-witness { margin:0 24px 24px; border:1px solid #49282D; border-left:5px solid var(--fail); border-radius:10px; background:var(--bg) }
-.witness-heading { padding:18px 20px; border-bottom:1px solid var(--border) }
-.witness-heading span,.witness-values span { display:block; color:var(--fail); font:700 9px/1.4 var(--mono); letter-spacing:.1em }
-.witness-heading strong { display:block; margin-top:8px; font-size:16px; line-height:1.4 }
-.witness-values { display:grid; grid-template-columns:1fr 1fr; gap:1px; background:var(--border) }
-.witness-values > div { padding:16px 20px; background:var(--bg) }
-.witness-values code { display:block; margin-top:7px; color:var(--pass); font:12px/1.5 var(--mono) }
-.witness-values > div:last-child code { color:var(--fail) }
-.scene-witness > p { margin:0; padding:13px 20px; color:var(--muted); font-size:11px }
-
-.pulse-scene.is-playing .graph-node,.pulse-scene.is-playing .scene-status,.pulse-scene.is-playing .scene-witness,.pulse-scene.is-playing .pulse,.pulse-scene.is-playing .break-mark,.pulse-scene.is-playing .edge-caption { opacity:0 }
-.pulse-scene.is-playing .graph-node { transform:translateY(8px) }
-.pulse-scene.is-playing .graph-node,.pulse-scene.is-playing .scene-status { transition:opacity 320ms var(--ease-out-quint),transform 320ms var(--ease-out-quint) }
-.pulse-scene.is-playing.nodes-active .graph-node { opacity:1; transform:none }
-.pulse-scene.is-playing.baseline-active .scene-status { opacity:1 }
-.pulse-scene.is-playing.baseline-active .semantic-status { visibility:hidden; opacity:0 }
-.pulse-scene.is-playing.baseline-active .edge-rail { background:var(--pass) }
-.pulse-scene.is-playing.candidate-active .scene-status { opacity:1 }
-.pulse-scene.is-playing.candidate-active .semantic-status { visibility:hidden; opacity:0 }
-.pulse-scene.is-playing.candidate-active .first-edge .edge-rail { background:var(--pulse) }
-.pulse-scene.is-playing.candidate-active .pulse { opacity:1; transition:opacity 220ms var(--ease-out-quint) }
-.pulse-scene.is-playing:not(.broken-active) .graph-node.direct,.pulse-scene.is-playing:not(.impact-active) .graph-node.affected { border-color:var(--edge); background:var(--surface-strong) }
-.pulse-scene.is-playing:not(.broken-active) .graph-node.direct small,.pulse-scene.is-playing:not(.impact-active) .graph-node.affected small { color:var(--muted) }
-.pulse-scene.is-playing:not(.impact-active) .blast-edge .edge-rail { background:var(--edge) }
-.pulse-scene.is-playing.broken-active .semantic-status { visibility:visible; opacity:1 }
-.pulse-scene.is-playing.broken-active .break-mark,.pulse-scene.is-playing.broken-active .edge-caption { opacity:1; transition:opacity 220ms var(--ease-out-quint) }
-.pulse-scene.is-playing.impact-active .blast-edge .edge-rail { background:repeating-linear-gradient(90deg,var(--fail) 0 6px,transparent 6px 13px) }
-.pulse-scene.is-playing.witness-active .scene-witness { opacity:1; transform:none; transition:opacity 500ms var(--ease-out-quint),transform 500ms var(--ease-out-quint) }
-.pulse-scene.is-playing:not(.witness-active) .scene-witness { transform:translateY(12px) }
-
-.section { padding-top:150px }
-.section-heading { display:flex; align-items:end; justify-content:space-between; gap:48px; margin-bottom:50px }
-.section-heading > div { flex:1 1 60% }
-.section-heading > p { flex:0 1 480px; margin:0; color:var(--muted); font-size:17px }
-
-.meaning-diff { display:grid; grid-template-columns:1fr 94px 1fr; align-items:center }
-.payload { overflow:hidden; border:1px solid var(--border); border-radius:12px; background:var(--surface) }
-.payload-header { display:flex; align-items:center; justify-content:space-between; gap:18px; min-height:55px; padding:0 18px; border-bottom:1px solid var(--border); color:var(--muted); font:700 10px var(--mono); letter-spacing:.09em }
-.badge { padding:4px 8px; border:1px solid currentColor; border-radius:7px }.pass { color:var(--pass)!important }.fail { color:var(--fail)!important }.muted { color:var(--muted)!important }
-.payload pre,.terminal pre,.yaml { margin:0; overflow:auto; background:var(--bg); font:13px/1.75 var(--mono) }
-.payload pre { padding:24px 20px }
-.payload > p { min-height:72px; margin:0; padding:16px 18px; border-top:1px solid var(--border); color:var(--muted); font-size:13px }
-.flow-dot,.break-dot { display:inline-block; width:8px; height:8px; margin-right:7px; border-radius:50%; background:var(--pass) }.break-dot { background:var(--fail) }
-.contract-cut { position:relative; height:4px; background:linear-gradient(90deg,var(--pulse) 0 42%,transparent 42% 58%,var(--edge) 58%) }
-.contract-cut i { position:absolute; left:46%; width:22px; height:4px; border-radius:2px; background:var(--fail); transform:rotate(42deg); transform-origin:left center }.contract-cut i:nth-of-type(1) { top:-10px }.contract-cut i:nth-of-type(2) { top:7px }
-.diff-caption { margin:22px 0 0; color:var(--muted); text-align:center }.diff-caption strong { color:var(--text) }.diff-caption code { color:var(--pulse); font-family:var(--mono) }
-
-.edge-anatomy { padding:74px 54px; border:1px solid var(--border); border-radius:14px; background:var(--surface) }
-.anatomy-flow { display:grid; grid-template-columns:170px 1fr 170px; align-items:center }
-.anatomy-node { padding:18px; border:1px solid var(--edge); border-radius:10px; background:var(--surface-strong); text-align:center; font:700 13px var(--mono) }.anatomy-node.consumer { border-color:var(--fail) }
-.long-rail { position:relative; height:5px; background:linear-gradient(90deg,var(--pulse) 0 49%,transparent 49% 54%,var(--edge) 54%); border-radius:3px }
-.long-rail b { position:absolute; top:-5px; left:calc(49% - 5px); width:15px; height:15px; border-radius:50%; background:var(--pulse) }
-.long-rail i { position:absolute; left:50.3%; width:24px; height:4px; border-radius:3px; background:var(--fail); transform:rotate(42deg); transform-origin:left center }.long-rail .cut-one { top:-11px }.long-rail .cut-two { top:7px }
-.anatomy-labels { display:grid; grid-template-columns:1fr 1fr 1fr; gap:20px; margin-top:40px; color:var(--muted); font:700 10px var(--mono); letter-spacing:.08em; text-align:center }
-
-.method-rail { display:grid; grid-template-columns:repeat(6,1fr); margin:0; padding:0; border:1px solid var(--border); border-radius:12px; overflow:hidden; list-style:none; background:var(--surface) }
-.method-rail li { position:relative; min-height:220px; padding:24px 20px; border-right:1px solid var(--border) }.method-rail li:last-child { border-right:0 }
-.method-rail li::before { content:""; position:absolute; top:53px; left:0; right:0; height:2px; background:var(--edge) }.method-rail li:first-child::before { left:24px }.method-rail li:last-child::before { right:calc(100% - 26px) }
-.method-rail span { position:relative; z-index:1; display:grid; place-items:center; width:26px; height:26px; border:2px solid var(--pulse); border-radius:50%; background:var(--surface); color:var(--pulse); font:700 9px var(--mono) }
-.method-rail strong { display:block; margin-top:42px; font-size:17px }.method-rail p { margin:9px 0 0; color:var(--muted); font-size:12px; line-height:1.55 }
-.method-rail li:nth-child(3) span { border-color:var(--fail); color:var(--fail) }.method-rail li:nth-child(3)::after { content:""; position:absolute; top:45px; left:55%; width:15px; height:3px; border-radius:2px; background:var(--fail); transform:rotate(42deg) }
-
-.code-layout { display:grid; grid-template-columns:1.55fr .75fr; border:1px solid var(--border); border-radius:12px; overflow:hidden; background:var(--surface) }
-.yaml { padding:30px; border-right:1px solid var(--border); color:#D8DEE9 }.yaml .key { color:var(--pulse) }.yaml .string { color:var(--pass) }
-.evaluator-list { padding:30px }.list-label { color:var(--muted); font:700 10px var(--mono); letter-spacing:.1em }.evaluator-list ul { margin:24px 0 30px; padding:0; list-style:none }.evaluator-list li { display:flex; gap:14px; padding:12px 0; border-bottom:1px solid var(--border); font-size:14px }.evaluator-list li span { color:var(--muted); font:11px var(--mono) }.evaluator-list a { font-size:13px; font-weight:700 }
-
-.terminal { overflow:hidden; border:1px solid var(--border); border-radius:12px; background:var(--bg) }
-.terminal-bar { display:flex; align-items:center; gap:7px; min-height:50px; padding:0 18px; border-bottom:1px solid var(--border); background:var(--surface) }.terminal-bar span { width:8px; height:8px; border-radius:50%; background:var(--edge) }.terminal-bar strong { margin-left:10px; color:var(--muted); font:600 11px var(--mono) }
-.terminal pre { padding:28px; color:#D8DEE9 }
-
-.architecture-visual,.report-figure { width:100%; border:1px solid var(--border); border-radius:14px; background:var(--surface) }
-.report-figure { margin:0; overflow:hidden }.report-figure img { width:100% }.report-figure figcaption { padding:13px 18px; border-top:1px solid var(--border); color:var(--muted); font:11px var(--mono) }
-
-.roadmap-rail { border-top:1px solid var(--border) }.roadmap-rail a { display:grid; grid-template-columns:60px 1fr auto; align-items:center; gap:20px; min-height:82px; border-bottom:1px solid var(--border); text-decoration:none }.roadmap-rail a:hover { background:var(--surface) }.roadmap-rail span,.roadmap-rail small { color:var(--muted); font:11px var(--mono) }.roadmap-rail strong { font-size:17px }.roadmap-rail small { color:var(--unknown); text-transform:uppercase }
-
-.final-cta { margin-top:150px; padding:90px 52px; border:1px solid var(--border); border-radius:14px; background:var(--surface) }.final-cta h2 { max-width:700px }.final-cta > p:not(.eyebrow) { max-width:640px; margin:24px 0 0; color:var(--muted); font-size:18px }.cta-mark { position:relative; width:168px; height:18px; margin-bottom:46px; background:linear-gradient(90deg,var(--pulse) 0 44%,transparent 44% 56%,var(--edge) 56%); border-radius:5px }.cta-mark::before,.cta-mark::after { content:""; position:absolute; top:4px; width:10px; height:10px; border-radius:50%; background:var(--pulse) }.cta-mark::before { left:-5px }.cta-mark::after { right:-5px; background:var(--edge) }.cta-mark i { position:absolute; left:46%; width:22px; height:4px; border-radius:3px; background:var(--fail); transform:rotate(42deg); transform-origin:left center }.cta-mark i:first-of-type { top:-6px }.cta-mark i:last-of-type { top:10px }
-
-.site-footer { margin-top:100px; border-top:1px solid var(--border) }.footer-inner { display:flex; align-items:center; justify-content:space-between; gap:32px; min-height:116px; color:var(--muted); font-size:12px }.footer-inner > div { display:flex; align-items:center; gap:14px }.footer-inner nav { display:flex; gap:24px }.footer-inner a:hover { color:var(--text) }
-
-@media (max-width:920px) {
- .shell { width:min(100% - 40px,1200px) }
- .nav-links a:not(.nav-github) { display:none }
- .section { padding-top:112px }
- .section-heading { align-items:flex-start; flex-direction:column; gap:22px }
- .meaning-diff { grid-template-columns:1fr; gap:44px }.contract-cut { width:4px; height:44px; margin:auto; background:linear-gradient(180deg,var(--pulse) 0 42%,transparent 42% 58%,var(--edge) 58%) }.contract-cut i { left:-8px; transform:rotate(-42deg) }.contract-cut i:nth-of-type(1) { top:17px }.contract-cut i:nth-of-type(2) { top:31px }
- .method-rail { grid-template-columns:repeat(3,1fr) }.method-rail li:nth-child(3) { border-right:0 }.method-rail li:nth-child(-n+3) { border-bottom:1px solid var(--border) }
- .code-layout { grid-template-columns:1fr }.yaml { border-right:0; border-bottom:1px solid var(--border) }
-}
-
-/* Semantic Pulse product controls. These are state changes, not ambient decoration. */
-.playground { border:1px solid var(--border); border-radius:14px; background:var(--surface); overflow:hidden }
-.playground-toolbar { display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap; padding:var(--space-3); border-bottom:1px solid var(--border) }
-.playground-choice { min-height:44px; padding:0 var(--space-2); border:1px solid var(--border); border-radius:8px; background:transparent; color:var(--muted); font:700 13px var(--sans); cursor:pointer }
-.playground-choice:hover,.playground-choice.is-selected { border-color:var(--pulse); color:var(--text) }
-.playground-run { margin-left:auto }
-.playground-graph { display:grid; grid-template-columns:1fr minmax(120px,.8fr) 1fr; align-items:center; gap:var(--space-3); padding:var(--space-7) var(--space-6) var(--space-6) }
-.playground-node { min-height:96px; padding:var(--space-3); border:1px solid var(--edge); border-radius:10px; background:var(--surface-strong) }
-.playground-node strong,.playground-node small { display:block }.playground-node strong { font:700 14px var(--mono) }.playground-node small { margin-top:var(--space-1); color:var(--muted); font-size:12px }
-.playground-edge { position:relative; height:32px; border-top:3px solid var(--edge) }.playground-edge small { position:absolute; top:-32px; left:50%; transform:translateX(-50%); color:var(--muted); font:700 10px var(--mono); white-space:nowrap }
-.playground-pulse { position:absolute; top:-8px; left:0; width:13px; height:13px; border-radius:50%; background:var(--pulse); opacity:0 }
-.playground-break { position:absolute; top:-14px; left:50%; width:24px; height:28px; opacity:0 }.playground-break i { position:absolute; width:22px; height:4px; background:var(--fail); transform:rotate(42deg) }.playground-break i:last-child { top:15px }
-.playground.is-running .playground-pulse { opacity:1; animation:semantic-pulse 700ms var(--ease-out-quint) forwards }
-.playground.is-baseline .playground-edge { border-color:var(--pass) }.playground.is-baseline .playground-node:last-child { border-color:var(--pass) }
-.playground.is-candidate .playground-edge { border-color:var(--fail) }.playground.is-candidate .playground-pulse { animation-play-state:paused; left:calc(50% - 6px); background:var(--fail) }.playground.is-candidate .playground-break { opacity:1 }.playground.is-candidate .playground-node:last-child { border-color:var(--fail) }
-.playground-result { display:flex; flex-direction:column; gap:var(--space-1); min-height:82px; padding:var(--space-3) var(--space-6); border-top:1px solid var(--border); background:var(--bg); font-size:14px }.playground-result span { color:var(--muted); font-size:13px }
-@keyframes semantic-pulse { from { left:0 } to { left:calc(100% - 13px) } }
-
-.setup-modal[hidden] { display:none }
-.setup-modal { position:fixed; inset:0; z-index:80; display:grid; place-items:center; padding:var(--space-3); color:var(--text) }
-.modal-backdrop { position:absolute; inset:0; background:rgba(11,15,20,.66); backdrop-filter:blur(6px) }
-.setup-dialog { position:relative; width:min(720px,100%); max-height:calc(100dvh - 48px); overflow:auto; border:1px solid var(--border); border-radius:16px; background:var(--surface); opacity:0; transform:translateY(8px); transition:opacity 320ms var(--ease-out-quint),transform 320ms var(--ease-out-quint) }
-.setup-modal.is-open .setup-dialog { opacity:1; transform:none }
-.setup-dialog-header { display:flex; justify-content:space-between; gap:var(--space-4); padding:var(--space-5) var(--space-5) var(--space-3) }
-.setup-dialog-header h2 { font-size:clamp(1.8rem,4vw,2.6rem) }.setup-dialog-header p:not(.eyebrow) { max-width:560px; margin:var(--space-2) 0 0; color:var(--muted); font-size:14px }
-.modal-close { position:sticky; top:var(--space-2); flex:0 0 44px; width:44px; height:44px; border:1px solid var(--border); border-radius:8px; background:var(--surface); color:var(--text); font-size:24px; line-height:1; cursor:pointer }
-.setup-tabs { display:flex; gap:4px; overflow-x:auto; padding:0 var(--space-5); border-bottom:1px solid var(--border) }.setup-tab { min-height:48px; padding:0 var(--space-2); border:0; border-bottom:2px solid transparent; background:transparent; color:var(--muted); font:700 12px var(--sans); white-space:nowrap; cursor:pointer }.setup-tab:hover,.setup-tab.is-active { border-bottom-color:var(--pulse); color:var(--text) }
-.setup-panel { padding:var(--space-5) }.setup-panel-meta { display:flex; align-items:center; justify-content:space-between; gap:var(--space-2) }.setup-panel-meta strong { font-size:16px }.setup-panel p { margin:var(--space-2) 0 var(--space-3); color:var(--muted); font-size:14px }
-.support-badge { padding:4px 7px; border:1px solid currentColor; border-radius:999px; font:700 10px var(--mono); white-space:nowrap }.support-badge.available { color:var(--pass) }.support-badge.cli { color:var(--pulse) }.support-badge.planned { color:var(--unknown) }
-.command-line { display:flex; align-items:center; gap:var(--space-2); padding:var(--space-2); border:1px solid var(--border); border-left:3px solid var(--pulse); border-radius:8px; background:var(--bg) }.command-line code { flex:1; overflow:auto; color:var(--text); font:12px/1.6 var(--mono); white-space:pre-wrap; overflow-wrap:anywhere }.copy-button { flex:0 0 auto; min-height:40px; padding:0 12px; border:1px solid var(--border); border-radius:7px; background:var(--surface); color:var(--text); font:700 12px var(--sans); cursor:pointer }.copy-button:hover,.copy-button.copied { border-color:var(--pass); color:var(--pass) }.copy-feedback { display:block; min-height:22px; margin-top:var(--space-1); color:var(--pass); font:11px var(--mono) }
-body.modal-open { overflow:hidden }
+/* --------------------------------------------------------------- base --- */
-@media (prefers-color-scheme: dark) {
- .site-header { background:rgba(11,15,20,.9) }.install-line,.yaml,.terminal pre { color:#D8DEE9 }.button.primary { color:var(--bg) }.button.secondary:hover,.replay:hover { background:#171324 }
-}
-
-@media (max-width:920px) {
- .section { padding-top:var(--space-8) }.hero { padding-top:var(--space-7) }.final-cta { margin-top:var(--space-8) }
-}
-@media (max-width:700px) {
- .section { padding-top:var(--space-7) }.hero { padding-top:var(--space-6) }.section-heading { margin-bottom:var(--space-4) }.final-cta { margin-top:var(--space-7); padding:var(--space-6) var(--space-3) }
- .playground-toolbar { align-items:stretch; flex-direction:column }.playground-run { margin-left:0 }.playground-graph { grid-template-columns:1fr; gap:var(--space-4); padding:var(--space-5) var(--space-3) }.playground-edge { width:calc(100% - 24px); margin-inline:auto; transform:rotate(90deg) }.playground-edge small { transform:translateX(-50%) rotate(-90deg); top:-50px }.playground-pulse { animation-name:semantic-pulse-vertical!important; top:-8px; left:0 }.playground.is-candidate .playground-pulse { left:calc(50% - 6px) }.playground-break { transform:rotate(-90deg); left:calc(50% - 12px) }.playground-result { padding-inline:var(--space-3) }
- .setup-modal { align-items:end; padding:0 }.setup-dialog { width:100%; max-height:92dvh; border-radius:16px 16px 0 0; padding-bottom:env(safe-area-inset-bottom) }.setup-dialog-header { padding:var(--space-4) var(--space-3) var(--space-2) }.setup-tabs,.setup-panel { padding-left:var(--space-3); padding-right:var(--space-3) }.setup-tab { min-height:52px }.command-line { align-items:stretch; flex-direction:column }.copy-button { width:100% }.modal-close { position:absolute; top:var(--space-3); right:var(--space-3) }
-}
-@keyframes semantic-pulse-vertical { from { top:-8px } to { top:calc(100% - 8px) } }
-@media (prefers-reduced-motion:reduce) {
- .setup-dialog { transition:none }.playground.is-running .playground-pulse { animation:none; left:calc(100% - 13px) }.playground.is-candidate .playground-pulse { left:calc(50% - 6px) }
-}
-
-@media (max-width:700px) {
- .shell { width:min(100% - 32px,1200px) }
- .site-header { position:static }.nav { min-height:72px }.brand img { width:166px }.nav-github { font-size:0!important }.nav-github span { font-size:16px }
- .hero { padding-top:70px } h1 { font-size:clamp(3rem,15vw,4.35rem) }.hero-lede { font-size:1rem }.hero-actions .button { flex:1 1 160px }
- .install-line { margin-top:18px }.install-line code { font-size:11px }
- .pulse-scene { margin-top:50px }.scene-toolbar { align-items:flex-start; flex-direction:column }.replay { width:100% }.scene-status { display:grid; grid-template-columns:1fr 1fr; padding:16px 16px 0 }.status { min-width:0 }.status small { display:none }
- .graph-flow { grid-template-columns:1fr; padding:30px 16px }.graph-node { width:100%; min-height:86px }.graph-edge { width:100%; height:64px }.edge-rail,.first-edge .edge-rail,.blast-edge .edge-rail { top:0; left:calc(50% - 1px); width:3px; height:100%; background:var(--edge) }.first-edge .edge-rail { bottom:54%; height:46%; background:var(--pulse) }.blast-edge .edge-rail { background:repeating-linear-gradient(180deg,var(--fail) 0 6px,transparent 6px 13px) }.pulse { top:calc(46% - 6px); left:calc(50% - 6px) }.break-mark { top:47%; left:calc(50% + 4px); transform:rotate(90deg) }.edge-caption { top:42%; left:calc(50% + 68px) }
- .scene-witness { margin:0 16px 16px }.witness-values { grid-template-columns:1fr }.witness-heading strong { font-size:14px }
- .section { padding-top:100px }.section-heading { margin-bottom:36px }.section-heading > p { font-size:15px }
- .edge-anatomy { padding:54px 18px }.anatomy-flow { grid-template-columns:1fr; gap:38px }.long-rail { width:5px; height:90px; margin:auto; background:linear-gradient(180deg,var(--pulse) 0 49%,transparent 49% 54%,var(--edge) 54%) }.long-rail b { top:calc(49% - 7px); left:-5px }.long-rail i { left:-9px; transform:rotate(-42deg) }.long-rail .cut-one { top:43% }.long-rail .cut-two { top:53% }.anatomy-labels { grid-template-columns:1fr; gap:10px }
- .method-rail { grid-template-columns:1fr }.method-rail li { min-height:156px; border-right:0; border-bottom:1px solid var(--border)!important }.method-rail li:last-child { border-bottom:0!important }.method-rail li::before { top:50px; right:auto; bottom:-1px; left:32px!important; width:2px; height:auto }.method-rail li:last-child::before { display:none }.method-rail strong { margin-top:20px }
- .yaml,.evaluator-list,.terminal pre { padding:20px 16px }.yaml,.terminal pre { font-size:11px }
- .roadmap-rail a { grid-template-columns:40px 1fr; padding:15px 0 }.roadmap-rail small { grid-column:2 }
- .final-cta { margin-top:100px; padding:60px 24px }.final-cta > p:not(.eyebrow) { font-size:16px }
- .footer-inner { align-items:flex-start; flex-direction:column; justify-content:center; padding:28px 0 }.footer-inner nav { flex-wrap:wrap; gap:14px 22px }
-}
-
-@media (prefers-reduced-motion:reduce) {
- html { scroll-behavior:auto }
- *,*::before,*::after { animation-duration:.01ms!important; animation-iteration-count:1!important; transition-duration:.01ms!important }
- .pulse-scene.is-playing .graph-node,.pulse-scene.is-playing .scene-status,.pulse-scene.is-playing .scene-witness,.pulse-scene.is-playing .pulse,.pulse-scene.is-playing .break-mark,.pulse-scene.is-playing .edge-caption { opacity:1; transform:none }
+*, *::before, *::after { box-sizing: border-box }
+
+html {
+ scroll-behavior: smooth;
+ scroll-padding-top: var(--s9);
+ -webkit-text-size-adjust: 100%;
+}
+
+body {
+ margin: 0;
+ overflow-x: hidden;
+ background: var(--bg);
+ color: var(--text);
+ font: 400 16px/1.65 var(--sans);
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+img { display: block; max-width: 100%; height: auto }
+svg { display: block }
+a { color: inherit; text-decoration: none }
+button, a { -webkit-tap-highlight-color: transparent }
+button { font: inherit }
+
+:focus-visible {
+ outline: 2px solid var(--pulse);
+ outline-offset: 3px;
+ border-radius: 3px;
+}
+
+.skip-link {
+ position: fixed;
+ top: var(--s2);
+ left: var(--s2);
+ z-index: 200;
+ display: grid;
+ place-items: center;
+ min-height: 44px;
+ padding: 0 var(--s2);
+ border-radius: var(--radius-sm);
+ background: var(--primary);
+ color: var(--on-primary);
+ font-weight: 650;
+ transform: translateY(-200%);
+ transition: transform var(--t-fast) var(--ease);
+}
+.skip-link:focus { transform: none }
+
+.shell {
+ position: relative;
+ width: min(1200px, 100% - (var(--gutter) * 2));
+ margin-inline: auto;
+}
+
+.sr-only {
+ position: absolute !important;
+ width: 1px !important; height: 1px !important;
+ padding: 0 !important; margin: -1px !important;
+ overflow: hidden !important;
+ clip-path: inset(50%) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
+
+/* -------------------------------------------------------- typography --- */
+
+h1, h2, h3, p, ol, ul, figure, pre { margin: 0 }
+h1, h2, h3 { font-weight: 640; text-wrap: balance }
+
+h1 {
+ font-size: clamp(2.125rem, 7.6vw, 6.5rem);
+ line-height: .93;
+ letter-spacing: -.042em;
+}
+h1 .line { display: block }
+h1 em { font-style: normal; color: var(--fail) }
+
+h2 {
+ font-size: clamp(2rem, 4.4vw, 3.5rem);
+ line-height: 1.04;
+ letter-spacing: -.034em;
+}
+
+.eyebrow {
+ display: flex;
+ align-items: center;
+ gap: var(--s1);
+ margin-bottom: var(--s3);
+ color: var(--pulse);
+ font-size: .6875rem;
+ font-weight: 650;
+ letter-spacing: .13em;
+ text-transform: uppercase;
+}
+/* The eyebrow bullet is a graph node: sections are nodes on one traversal. */
+.eyebrow i {
+ flex: 0 0 auto;
+ width: 7px; height: 7px;
+ border-radius: 50%;
+ background: var(--pulse);
+ box-shadow: 0 0 0 4px var(--tint-pulse);
+}
+
+.hero-lede, .cta-lede {
+ color: var(--muted);
+ font-size: clamp(1.0625rem, 1.5vw, 1.3125rem);
+ line-height: 1.6;
+ text-wrap: pretty;
+}
+
+code, pre, .mono { font-family: var(--mono); font-variant-ligatures: none }
+
+/* -------------------------------------------------------- the field --- */
+
+.field {
+ position: fixed;
+ inset: 0;
+ z-index: 0;
+ width: 100%;
+ height: 100%;
+ opacity: var(--field-alpha);
+ pointer-events: none;
+ /* Density falls away from the top so the field never fights body copy. */
+ -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 42%, rgba(0,0,0,.55) 72%, rgba(0,0,0,.28) 100%);
+ mask-image: linear-gradient(180deg, #000 0%, #000 42%, rgba(0,0,0,.55) 72%, rgba(0,0,0,.28) 100%);
+}
+
+.field-veil {
+ position: fixed;
+ inset: 0;
+ z-index: 0;
+ pointer-events: none;
+ background:
+ radial-gradient(58% 58% at 30% 30%, var(--bg) 0%, var(--bg) 38%, transparent 78%),
+ radial-gradient(120% 70% at 50% 0%, transparent 40%, var(--bg) 100%),
+ radial-gradient(80% 50% at 12% 22%, var(--tint-pulse), transparent 70%);
+}
+
+.site-header, main, .site-footer { position: relative; z-index: 1 }
+
+/* ----------------------------------------------------------- header --- */
+
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 60;
+ border-bottom: 1px solid transparent;
+ transition: border-color var(--t-mid) var(--ease), background var(--t-mid) var(--ease);
+}
+.site-header.is-stuck {
+ border-bottom-color: var(--border);
+ background: var(--header-bg);
+ -webkit-backdrop-filter: saturate(180%) blur(12px);
+ backdrop-filter: saturate(180%) blur(12px);
+}
+
+.nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--s4);
+ min-height: 72px;
+}
+
+.brand {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--s1);
+ min-height: 44px;
+ margin-left: -2px;
+}
+.brand-word {
+ font-size: 1.125rem;
+ font-weight: 650;
+ letter-spacing: -.028em;
+}
+.brand-mark { display: block; flex: 0 0 auto }
+.brand-mark svg { overflow: visible }
+
+/* One mark, theme-aware, no second asset and no font dependency. */
+.m-live { fill: var(--pulse) }
+.m-dead { fill: var(--edge) }
+.m-live-stroke { fill: none; stroke: var(--pulse); stroke-width: 4; stroke-linecap: round }
+.m-dead-stroke { fill: none; stroke: var(--edge); stroke-width: 4; stroke-linecap: round }
+.m-cut { fill: none; stroke: var(--fail); stroke-width: 3.5; stroke-linecap: round }
+
+/* The mark's live segment carries a pulse when the brand is hovered. */
+.brand .m-live-stroke {
+ stroke-dasharray: 5 9;
+ stroke-dashoffset: 0;
+}
+.brand:hover .m-live-stroke,
+.brand:focus-visible .m-live-stroke {
+ animation: mark-flow 900ms var(--ease-io) infinite;
+}
+@keyframes mark-flow { to { stroke-dashoffset: -14 } }
+
+.nav-links { display: flex; align-items: center; gap: var(--s0) }
+.nav-links a {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ min-height: 44px;
+ padding: 0 var(--s2);
+ color: var(--muted);
+ font-size: .875rem;
+ font-weight: 550;
+ transition: color var(--t-fast) var(--ease);
+}
+.nav-links a:hover { color: var(--text) }
+
+/* Hovering a link completes an edge underneath it. */
+.nav-links a:not(.nav-github) span { position: relative }
+.nav-links a:not(.nav-github) span::after {
+ content: "";
+ position: absolute;
+ left: 0; right: 0; bottom: -6px;
+ height: 1.5px;
+ border-radius: 2px;
+ background: var(--pulse);
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform var(--t-mid) var(--ease);
+}
+.nav-links a:not(.nav-github):hover span::after,
+.nav-links a:not(.nav-github):focus-visible span::after { transform: scaleX(1) }
+
+.nav-github {
+ gap: 6px;
+ margin-left: var(--s1);
+ padding-inline: var(--s2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--surface);
+ color: var(--text) !important;
+ font-weight: 600;
+ box-shadow: var(--shadow-card);
+ transition: border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
+}
+.nav-github svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round }
+.nav-github:hover { border-color: var(--border-strong); transform: translateY(-1px) }
+
+/* ------------------------------------------------------------- hero --- */
+
+.hero { padding: var(--s9) 0 var(--s7) }
+.hero-copy { max-width: 1000px }
+
+h1 { margin-bottom: var(--s4) }
+.hero-lede { max-width: 52ch; margin-bottom: var(--s6) }
+
+.hero-actions { display: flex; flex-wrap: wrap; gap: var(--s1) }
+
+/* ---------------------------------------------------------- buttons --- */
+
+.button {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ min-height: 48px;
+ padding: 0 var(--s3);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--surface);
+ color: var(--text);
+ font: 650 .875rem var(--sans);
+ letter-spacing: -.006em;
+ cursor: pointer;
+ overflow: hidden;
+ box-shadow: var(--shadow-card);
+ transition: transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
+}
+.button svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round }
+.button:hover { transform: translateY(-1px) }
+.button:active { transform: translateY(0) }
+
+.button.primary {
+ border-color: transparent;
+ background: var(--primary);
+ color: var(--on-primary);
+}
+.button.primary:hover { background: var(--primary-hover) }
+.button.secondary:hover { border-color: var(--border-strong) }
+
+/* A pulse crosses the control's own edge on hover: the same primitive,
+ at the smallest possible scale. */
+.button::after {
+ content: "";
+ position: absolute;
+ left: 0; bottom: 0;
+ width: 100%; height: 1.5px;
+ background: linear-gradient(90deg, transparent, currentColor, transparent);
+ opacity: 0;
+ transform: translateX(-100%);
+}
+.button:hover::after, .button:focus-visible::after {
+ opacity: .55;
+ animation: edge-cross 620ms var(--ease-io);
+}
+@keyframes edge-cross {
+ from { transform: translateX(-100%) }
+ to { transform: translateX(100%) }
+}
+
+/* ----------------------------------------------------- install line --- */
+
+.install-line {
+ display: flex;
+ align-items: center;
+ gap: var(--s1);
+ max-width: 780px;
+ margin-top: var(--s5);
+ padding: var(--s0) var(--s0) var(--s0) var(--s2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--surface);
+ box-shadow: var(--shadow-card);
+ transition: border-color var(--t-fast) var(--ease);
+}
+.install-line:hover { border-color: var(--border-strong) }
+.install-prompt { flex: 0 0 auto; color: var(--pulse); font: 600 .8125rem var(--mono) }
+.install-line code {
+ flex: 1 1 auto;
+ min-width: 0;
+ overflow-x: auto;
+ padding: var(--s2) 0;
+ color: var(--code);
+ font-size: .8125rem;
+ line-height: 1.5;
+ white-space: nowrap;
+ scrollbar-width: none;
+}
+.install-line code::-webkit-scrollbar { display: none }
+
+.install-copy {
+ position: relative;
+ flex: 0 0 auto;
+ display: grid;
+ place-items: center;
+ width: 40px; height: 40px;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ background: var(--surface-2);
+ color: var(--muted);
+ cursor: pointer;
+ transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
+}
+.install-copy:hover { color: var(--text); border-color: var(--border-strong) }
+.install-copy svg {
+ grid-area: 1 / 1;
+ fill: none; stroke: currentColor;
+ stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
+ transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
+}
+.install-copy .i-done { opacity: 0; transform: scale(.6) }
+.install-copy.copied { color: var(--pass-text); border-color: var(--edge-pass) }
+.install-copy.copied .i-copy { opacity: 0; transform: scale(.6) }
+.install-copy.copied .i-done { opacity: 1; transform: none }
+
+/* =========================================================== sections ===
+ Each section is a node on one traversal. A rail descends from the
+ previous section and lands on the section's eyebrow dot.
+ ======================================================================== */
+
+.section > .shell { padding-top: var(--s12) }
+
+/* The rail and its node are structure, so they render regardless. Only the
+ arrival animation is withheld, and only when something can play it. */
+.section > .shell::before {
+ content: "";
+ position: absolute;
+ left: 3px;
+ top: var(--s6);
+ width: 1px;
+ height: calc(var(--s12) - var(--s6) - 3px);
+ background: linear-gradient(180deg, transparent, var(--border-strong) 22%, var(--pulse));
+ transform-origin: top;
+}
+
+.js .section > .shell::before {
+ transform: scaleY(0);
+ transition: transform 900ms var(--ease);
+}
+.js .section.is-reached > .shell::before { transform: scaleY(1) }
+
+.js .section .eyebrow i {
+ transform: scale(.35);
+ opacity: 0;
+ transition: transform var(--t-slow) var(--ease) 260ms, opacity var(--t-mid) var(--ease) 260ms;
+}
+.js .section.is-reached .eyebrow i { transform: none; opacity: 1 }
+
+.section-heading {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: var(--s7);
+ margin-bottom: var(--s7);
+}
+.section-heading > div { flex: 1 1 58% }
+.section-heading > p {
+ flex: 0 1 440px;
+ color: var(--muted);
+ font-size: 1.0625rem;
+ line-height: 1.6;
+ text-wrap: pretty;
+}
+
+/* Section composition changes with the kind of evidence being presented. */
+.bug-heading {
+ display: grid;
+ grid-template-columns: minmax(0, 1.4fr) minmax(280px, .6fr);
+ align-items: start;
+}
+.bug-heading h2 { max-width: 9ch; font-size: clamp(2.75rem, 6.4vw, 5.75rem) }
+.bug-heading > p {
+ margin-top: var(--s9);
+ padding: var(--s3) 0 var(--s3) var(--s3);
+ border-left: 1px solid var(--pulse);
+}
+
+.terminal-section > .shell {
+ display: grid;
+ grid-template-columns: minmax(260px, .56fr) minmax(0, 1.44fr);
+ align-items: start;
+ gap: var(--s8);
+}
+.terminal-section .section-heading { display: block; margin: var(--s3) 0 0 }
+.terminal-section .section-heading > p { margin-top: var(--s3); font-size: .9375rem }
+
+.report-section {
+ margin-top: var(--s8);
+ border-block: 1px solid var(--border);
+ background: color-mix(in srgb, var(--bg-sunk) 78%, transparent);
+}
+.report-section > .shell { padding-bottom: var(--s10) }
+.report-section .section-heading { align-items: start }
+.report-section .section-heading h2 { max-width: 12ch }
+
+/* Shared surface treatment for every instrument panel. */
+.pulse-scene, .playground, .payload, .method-rail, .code-layout,
+.terminal, .arch-frame, .report-figure, .final-cta {
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ background: var(--surface);
+ box-shadow: var(--shadow-card);
+}
+
+/* ==================================================== the instrument ===
+ The canonical replay. Precise, causal, and it comes to rest.
+ ====================================================================== */
+
+.pulse-scene {
+ margin-top: var(--s9);
+ overflow: hidden;
+ box-shadow: var(--shadow-lift);
+}
+
+.scene-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--s3);
+ padding: var(--s2) var(--s2) var(--s2) var(--s3);
+ border-bottom: 1px solid var(--border);
+ background: var(--surface-2);
+}
+.scene-meta { display: flex; flex-direction: column; gap: 3px; min-width: 0 }
+.scene-kicker {
+ color: var(--subtle);
+ font: 650 .75rem/1.3 var(--mono);
+ letter-spacing: .12em;
+ text-transform: uppercase;
+}
+.scene-phase {
+ color: var(--text);
+ font: 550 .8125rem/1.4 var(--mono);
+ letter-spacing: -.01em;
+}
+
+.replay {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ min-height: 40px;
+ padding: 0 var(--s2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--surface);
+ color: var(--text);
+ font: 600 .8125rem var(--sans);
+ white-space: nowrap;
+ cursor: pointer;
+ transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
+}
+.replay svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round }
+.replay:hover { border-color: var(--pulse); color: var(--pulse) }
+.replay:hover svg { animation: spin-once 620ms var(--ease) }
+@keyframes spin-once { from { transform: rotate(0) } to { transform: rotate(-360deg) } }
+.replay:disabled { cursor: default; opacity: .5 }
+.replay:disabled:hover { border-color: var(--border); color: var(--text) }
+
+.scene-status { display: flex; flex-wrap: wrap; gap: var(--s1); padding: var(--s3) var(--s3) 0 }
+.status {
+ flex: 1 1 200px;
+ min-width: 0;
+ padding: var(--s2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--bg);
+}
+.status span, .status small {
+ display: block;
+ color: var(--subtle);
+ font: 650 .6875rem/1.35 var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.status strong { display: block; margin: var(--s0) 0; font: 650 .9375rem/1.3 var(--mono) }
+.schema-status { border-color: var(--edge-pass); background: var(--tint-pass) }
+.schema-status strong { color: var(--pass-text) }
+.semantic-status { border-color: var(--edge-fail); background: var(--tint-fail) }
+.semantic-status strong { color: var(--fail-text) }
+
+.graph-flow {
+ display: grid;
+ grid-template-columns: 1.08fr minmax(72px, .6fr) 1fr minmax(64px, .52fr) 1.14fr minmax(64px, .52fr) 1.06fr;
+ align-items: center;
+ padding: var(--s8) var(--s3) var(--s7);
+}
+
+.graph-node {
+ position: relative;
+ min-width: 0;
+ padding: var(--s2);
+ border: 1px solid var(--edge);
+ border-radius: 10px;
+ background: var(--surface-2);
+ transition: border-color var(--t-mid) var(--ease), background var(--t-mid) var(--ease), transform var(--t-mid) var(--ease);
+}
+.graph-node strong, .graph-node small { display: block; overflow-wrap: anywhere }
+.graph-node strong { font: 650 .8125rem/1.4 var(--mono); letter-spacing: -.01em }
+.graph-node small { margin-top: 6px; color: var(--subtle); font-size: .6875rem; line-height: 1.4 }
+
+.node-dot {
+ display: block;
+ width: 7px; height: 7px;
+ margin-bottom: var(--s1);
+ border-radius: 50%;
+ background: var(--edge);
+ transition: background var(--t-mid) var(--ease), box-shadow var(--t-mid) var(--ease);
+}
+.graph-node.producer .node-dot { background: var(--pulse); box-shadow: 0 0 0 4px var(--tint-pulse) }
+
+.graph-node.direct { border-color: var(--fail); background: var(--tint-fail) }
+.graph-node.direct .node-dot { background: var(--fail); box-shadow: 0 0 0 4px var(--tint-fail) }
+.graph-node.direct small { color: var(--fail-text) }
+.graph-node.affected { border-color: var(--edge-fail); background: var(--tint-fail) }
+.graph-node.affected .node-dot { background: var(--edge-fail) }
+.graph-node.affected small { color: var(--fail-text); opacity: .9 }
+.graph-node.terminal::after {
+ content: "";
+ position: absolute;
+ top: var(--s2); right: var(--s2);
+ width: 7px; height: 7px;
+ border: 1.5px solid var(--edge-fail);
+}
+
+/* ---- edges -------------------------------------------------------- */
+
+.graph-edge { position: relative; height: 44px }
+
+.graph-edge .rail {
+ position: absolute;
+ top: 50%; left: 0;
+ width: 100%; height: 2px;
+ margin-top: -1px;
+ border-radius: 2px;
+ background: var(--edge);
+}
+
+/* One resolved-portion element per edge, scaled to however far meaning got. */
+.graph-edge .rail-live {
+ position: absolute;
+ top: 50%; left: 0;
+ width: 100%; height: 3px;
+ margin-top: -1.5px;
+ border-radius: 2px;
+ background: var(--pulse);
+ transform: scaleX(0);
+ transform-origin: left center;
+}
+.first-edge .rail-live { transform: scaleX(var(--break-stop)) }
+
+.graph-edge .pulse {
+ position: absolute;
+ top: 50%; left: 0;
+ width: 11px; height: 11px;
+ margin-top: -5.5px;
+ border-radius: 50%;
+ background: var(--pulse);
+ box-shadow: 0 0 0 4px var(--tint-pulse);
+ opacity: 0;
+ transform: translateX(calc(var(--edge-w, 120px) * var(--break-stop) - 5.5px));
+}
+.first-edge .pulse { opacity: 1 }
+
+/* Trailing arc, so the pulse reads as travel and not a floating dot. */
+.graph-edge .pulse::before {
+ content: "";
+ position: absolute;
+ top: 4px; right: 6px;
+ width: 34px; height: 3px;
+ border-radius: 2px;
+ background: linear-gradient(270deg, var(--pulse), transparent);
+ opacity: 0;
+}
+
+.break-mark {
+ position: absolute;
+ top: 50%; left: 50%;
+ width: 20px; height: 26px;
+ margin: -13px 0 0 -4px;
+}
+.break-mark i {
+ position: absolute;
+ left: 0;
+ width: 17px; height: 3px;
+ border-radius: 2px;
+ background: var(--fail);
+ transform: rotate(42deg);
+ transform-origin: left center;
+}
+.break-mark i:first-child { top: 3px }
+.break-mark i:last-child { top: 15px }
+
+.edge-caption {
+ position: absolute;
+ top: -2px; left: 50%;
+ color: var(--fail-text);
+ font: 650 .6875rem var(--sans);
+ letter-spacing: .09em;
+ text-transform: uppercase;
+ white-space: nowrap;
+ transform: translateX(-50%);
+}
+
+/* Blast radius: dashed, and clearly not successful propagation. Dashes come
+ from a background image so one `background` override swaps edge state. */
+.blast-edge .rail {
+ height: 2px;
+ border-radius: 0;
+ background: repeating-linear-gradient(90deg, var(--edge-fail) 0 6px, transparent 6px 13px);
+}
+
+/* ---- witness ------------------------------------------------------ */
+
+.scene-witness {
+ margin: 0 var(--s3) var(--s3);
+ overflow: hidden;
+ border: 1px solid var(--edge-fail);
+ border-left: 3px solid var(--fail);
+ border-radius: var(--radius);
+ background: var(--bg);
+}
+.witness-heading { padding: var(--s2) var(--s3) }
+.witness-tag {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ color: var(--fail-text);
+ font: 650 .75rem/1.4 var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.witness-tag i {
+ width: 5px; height: 5px;
+ border-radius: 50%;
+ background: var(--fail);
+}
+.witness-heading strong {
+ display: block;
+ margin-top: var(--s1);
+ font-size: 1rem;
+ font-weight: 600;
+ line-height: 1.45;
+ letter-spacing: -.012em;
+}
+.witness-heading strong code { color: var(--fail-text); font-size: .9375rem }
+
+.witness-values {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1px;
+ border-top: 1px solid var(--border);
+ background: var(--border);
+}
+.witness-values > div { padding: var(--s2) var(--s3); background: var(--bg) }
+.witness-values span {
+ display: block;
+ color: var(--subtle);
+ font: 650 .6875rem/1.4 var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.witness-values code { display: block; margin-top: 6px; font-size: .8125rem; line-height: 1.5 }
+.witness-values code.ok { color: var(--pass-text) }
+.witness-values code.bad { color: var(--fail-text) }
+.scene-witness > p {
+ padding: var(--s2) var(--s3);
+ border-top: 1px solid var(--border);
+ color: var(--subtle);
+ font-size: .75rem;
+}
+.scene-witness > p b { color: var(--muted); font-weight: 600 }
+
+/* ---- replay timeline ----------------------------------------------
+ Baseline: one pulse traverses every edge in turn, each resolving green
+ behind it. Candidate: the same pulse leaves and never arrives. Trace:
+ the blast propagates outward from the break, hop by hop.
+ ------------------------------------------------------------------- */
+
+.pulse-scene.is-playing .graph-node,
+.pulse-scene.is-playing .scene-status,
+.pulse-scene.is-playing .scene-witness,
+.pulse-scene.is-playing .break-mark,
+.pulse-scene.is-playing .edge-caption { opacity: 0 }
+.pulse-scene.is-playing .graph-edge .pulse { opacity: 0 }
+
+.pulse-scene.is-playing .graph-node { transform: translateY(6px) }
+/* Colour has to stay in the transition list or the blast stagger below has
+ nothing to delay, and every affected node lights at once, ahead of the
+ edges that are supposed to be carrying the blast to it. */
+.pulse-scene.is-playing .graph-node,
+.pulse-scene.is-playing .scene-status {
+ transition:
+ opacity var(--t-mid) var(--ease),
+ transform var(--t-mid) var(--ease),
+ border-color var(--t-mid) var(--ease),
+ background var(--t-mid) var(--ease);
+ transition-delay: calc(var(--n, 0) * 70ms);
+}
+.pulse-scene.is-playing .graph-node small { transition: color var(--t-mid) var(--ease) }
+.pulse-scene.is-playing.nodes-active .graph-node { opacity: 1; transform: none }
+
+.pulse-scene.is-playing.baseline-active .scene-status,
+.pulse-scene.is-playing.candidate-active .scene-status { opacity: 1 }
+.pulse-scene.is-playing.baseline-active .semantic-status,
+.pulse-scene.is-playing.candidate-active .semantic-status { visibility: hidden; opacity: 0 }
+
+/* --- baseline: the pulse actually walks the graph --- */
+.pulse-scene.is-playing.baseline-active .graph-edge .pulse {
+ background: var(--pass);
+ box-shadow: 0 0 0 4px var(--tint-pass);
+ animation: hop-travel 380ms linear forwards;
+ animation-delay: calc(var(--e, 0) * 380ms);
+}
+.pulse-scene.is-playing.baseline-active .graph-edge .pulse::before {
+ background: linear-gradient(270deg, var(--pass), transparent);
+ opacity: .8;
+}
+.pulse-scene.is-playing.baseline-active .graph-edge .rail-live {
+ background: var(--pass);
+ animation: rail-fill 380ms linear forwards;
+ animation-delay: calc(var(--e, 0) * 380ms);
+}
+.pulse-scene.is-playing.baseline-active .blast-edge .rail { background: var(--edge) }
+
+@keyframes hop-travel {
+ from { opacity: 1; transform: translateX(-5.5px) }
+ to { opacity: 1; transform: translateX(calc(var(--edge-w, 120px) - 5.5px)) }
+}
+@keyframes rail-fill { from { transform: scaleX(0) } to { transform: scaleX(1) } }
+
+/* Draining the baseline before the candidate runs. Without this the graph
+ cuts from fully resolved to empty in one frame, which reads as a glitch
+ rather than as a second run starting from a clean state. */
+.pulse-scene.is-playing.baseline-active.resetting .graph-edge .rail-live {
+ animation: rail-drain 300ms var(--ease-io) forwards;
+ animation-delay: 0s;
+}
+.pulse-scene.is-playing.baseline-active.resetting .graph-edge .pulse {
+ animation: pulse-out 200ms var(--ease) forwards;
+ animation-delay: 0s;
+}
+@keyframes rail-drain { from { transform: scaleX(1); opacity: 1 } to { transform: scaleX(1); opacity: 0 } }
+@keyframes pulse-out { to { opacity: 0 } }
+
+/* --- candidate: it leaves, and it does not arrive --- */
+.pulse-scene.is-playing.candidate-active .first-edge .rail-live {
+ background: var(--pulse);
+ animation: rail-stop 900ms var(--ease-io) forwards;
+}
+.pulse-scene.is-playing.candidate-active .first-edge .pulse {
+ animation: pulse-stop 900ms var(--ease-io) forwards;
+}
+.pulse-scene.is-playing.candidate-active .first-edge .pulse::before {
+ opacity: .8;
+ transition: opacity 200ms linear;
+}
+@keyframes pulse-stop {
+ from { opacity: 1; transform: translateX(-5.5px) }
+ to { opacity: 1; transform: translateX(calc(var(--edge-w, 120px) * var(--break-stop) - 5.5px)) }
+}
+@keyframes rail-stop { from { transform: scaleX(0) } to { transform: scaleX(var(--break-stop)) } }
+
+/* Nothing downstream may be asserted before the break is found. */
+.pulse-scene.is-playing:not(.broken-active) .graph-node.direct,
+.pulse-scene.is-playing:not(.impact-active) .graph-node.affected {
+ border-color: var(--edge);
+ background: var(--surface-2);
+}
+.pulse-scene.is-playing:not(.broken-active) .graph-node.direct .node-dot,
+.pulse-scene.is-playing:not(.impact-active) .graph-node.affected .node-dot {
+ background: var(--edge); box-shadow: none;
+}
+.pulse-scene.is-playing:not(.broken-active) .graph-node.direct small,
+.pulse-scene.is-playing:not(.impact-active) .graph-node.affected small { color: var(--subtle) }
+.pulse-scene.is-playing:not(.impact-active) .blast-edge .rail { background: var(--edge) }
+
+/* --- break --- */
+.pulse-scene.is-playing.broken-active .semantic-status { visibility: visible; opacity: 1 }
+.pulse-scene.is-playing.broken-active .break-mark,
+.pulse-scene.is-playing.broken-active .edge-caption {
+ opacity: 1;
+ transition: opacity var(--t-fast) var(--ease);
+}
+.pulse-scene.is-playing.broken-active .break-mark i { animation: cut-in 240ms var(--ease) backwards }
+.pulse-scene.is-playing.broken-active .break-mark i:last-child { animation-delay: 60ms }
+@keyframes cut-in {
+ from { transform: rotate(42deg) scaleX(0) }
+ to { transform: rotate(42deg) scaleX(1) }
+}
+.pulse-scene.is-playing.broken-active .first-edge .pulse::before { opacity: 0 }
+
+/* --- trace: the blast propagates hop by hop, it does not appear at once --- */
+.pulse-scene.is-playing.impact-active .blast-edge .rail {
+ animation: blast-in 300ms var(--ease) backwards;
+ animation-delay: calc((var(--e, 1) - 1) * 190ms);
+}
+.pulse-scene.is-playing.impact-active .graph-node.affected,
+.pulse-scene.is-playing.impact-active .graph-node.direct {
+ transition-delay: calc((var(--n, 1) - 1) * 240ms);
+}
+.pulse-scene.is-playing.impact-active .graph-node.affected small,
+.pulse-scene.is-playing.impact-active .graph-node.direct small {
+ transition-delay: calc((var(--n, 1) - 1) * 240ms);
+}
+/* The blast arriving at a node is a discrete event, so the node marks it. */
+.pulse-scene.is-playing.impact-active .graph-node.affected .node-dot,
+.pulse-scene.is-playing.impact-active .graph-node.direct .node-dot {
+ animation: blast-hit 420ms var(--ease) backwards;
+ animation-delay: calc((var(--n, 1) - 1) * 240ms);
+}
+@keyframes blast-hit {
+ 0% { box-shadow: 0 0 0 0 var(--tint-fail) }
+ 40% { box-shadow: 0 0 0 7px var(--tint-fail) }
+ 100% { box-shadow: 0 0 0 4px var(--tint-fail) }
+}
+@keyframes blast-in {
+ from { background: var(--edge) }
+ to { background: repeating-linear-gradient(90deg, var(--edge-fail) 0 6px, transparent 6px 13px) }
+}
+
+.pulse-scene.is-playing.witness-active .scene-witness {
+ opacity: 1;
+ transform: none;
+ transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
+}
+.pulse-scene.is-playing:not(.witness-active) .scene-witness { transform: translateY(10px) }
+
+/* ======================================================== playground === */
+
+.playground { overflow: hidden }
+.playground-toolbar {
+ display: flex;
+ align-items: center;
+ gap: var(--s2);
+ flex-wrap: wrap;
+ padding: var(--s2);
+ border-bottom: 1px solid var(--border);
+ background: var(--surface-2);
+}
+
+/* Segmented control with a sliding thumb: state change, not decoration. */
+.segmented {
+ position: relative;
+ display: flex;
+ padding: 3px;
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ background: var(--bg);
+}
+.playground-choice {
+ position: relative;
+ z-index: 1;
+ min-height: 38px;
+ padding: 0 var(--s2);
+ border: 0;
+ border-radius: 7px;
+ background: transparent;
+ color: var(--muted);
+ font: 600 .8125rem var(--sans);
+ white-space: nowrap;
+ cursor: pointer;
+ transition: color var(--t-fast) var(--ease);
+}
+.playground-choice.is-selected { color: var(--text) }
+.segment-thumb {
+ position: absolute;
+ z-index: 0;
+ top: 3px; left: 3px;
+ height: calc(100% - 6px);
+ border-radius: 7px;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ box-shadow: var(--shadow-card);
+ transition: transform var(--t-mid) var(--ease), width var(--t-mid) var(--ease);
+}
+.playground-run { margin-left: auto }
+
+.playground-graph {
+ display: grid;
+ grid-template-columns: minmax(180px, 1.2fr) minmax(120px, .72fr) minmax(180px, 1.1fr) 72px minmax(120px, .72fr) 72px minmax(108px, .64fr);
+ align-items: center;
+ gap: var(--s2);
+ padding: var(--s5);
+}
+.playground-node {
+ padding: var(--s3);
+ border: 1px solid var(--edge);
+ border-radius: 10px;
+ background: var(--surface-2);
+ transition: border-color var(--t-mid) var(--ease), background var(--t-mid) var(--ease);
+}
+.playground-node strong, .playground-node small { display: block }
+.playground-node strong { font: 650 .875rem var(--mono); letter-spacing: -.01em }
+.playground-node small { margin-top: 6px; color: var(--subtle); font-size: .75rem }
+.playground-node-impact { padding: var(--s2) }
+.playground-node-impact strong { overflow-wrap: anywhere; font-size: .75rem }
+.playground-node-impact small { font-size: .6875rem; line-height: 1.45 }
+
+/* The producer's recorded values and the consumer's contract, side by side.
+ Switching the producer changes them, which is the whole point of the
+ control: the schema never moves, one number does. */
+.node-values {
+ margin-top: var(--s2);
+ padding-top: var(--s2);
+ border-top: 1px solid var(--border);
+}
+.node-values > div {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: var(--s2);
+ padding: 5px 0;
+}
+.node-values dt {
+ min-width: 0;
+ overflow-wrap: anywhere;
+ color: var(--subtle);
+ font: .6875rem/1.4 var(--mono);
+}
+.node-values dd {
+ flex: 0 0 auto;
+ margin: 0;
+ color: var(--text);
+ font: 600 .75rem/1.4 var(--mono);
+}
+.node-values dd.is-breaking { color: var(--fail-text) }
+.node-values dd.is-passing { color: var(--pass-text) }
+.playground-node[data-play-node="producer"] .node-dot { background: var(--pulse); box-shadow: 0 0 0 4px var(--tint-pulse) }
+.playground.is-running.is-baseline .playground-node[data-play-node="consumer"] .node-dot { background: var(--pass); box-shadow: 0 0 0 4px var(--tint-pass) }
+.playground.is-running.is-candidate .playground-node[data-play-node="consumer"] .node-dot { background: var(--fail); box-shadow: 0 0 0 4px var(--tint-fail) }
+.playground.is-running.is-baseline .playground-node-impact .node-dot { background: var(--pass); box-shadow: 0 0 0 4px var(--tint-pass) }
+.playground.is-running.is-candidate .playground-node-impact .node-dot { background: var(--fail); box-shadow: 0 0 0 4px var(--tint-fail) }
+
+.playground-edge { position: relative; height: 44px }
+.playground-edge .rail {
+ position: absolute;
+ top: 50%; left: 0;
+ width: 100%; height: 2px;
+ margin-top: -1px;
+ border-radius: 2px;
+ background: var(--edge);
+ transition: background var(--t-mid) var(--ease);
+}
+.playground-edge .rail-live {
+ position: absolute;
+ top: 50%; left: 0;
+ width: 100%; height: 3px;
+ margin-top: -1.5px;
+ border-radius: 2px;
+ background: var(--pulse);
+ transform: scaleX(0);
+ transform-origin: left center;
+}
+.playground-edge small {
+ position: absolute;
+ left: 50%; top: -20px;
+ color: var(--subtle);
+ font: 650 .6875rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ white-space: nowrap;
+ transform: translateX(-50%);
+}
+.playground-pulse {
+ position: absolute;
+ top: 50%; left: 0;
+ width: 11px; height: 11px;
+ margin-top: -5.5px;
+ border-radius: 50%;
+ background: var(--pulse);
+ box-shadow: 0 0 0 4px var(--tint-pulse);
+ opacity: 0;
+ transform: translateX(-5.5px);
+}
+.playground-break {
+ position: absolute;
+ top: 50%; left: 50%;
+ width: 20px; height: 26px;
+ margin: -13px 0 0 -4px;
+ opacity: 0;
+}
+.playground-break i {
+ position: absolute;
+ left: 0;
+ width: 17px; height: 3px;
+ border-radius: 2px;
+ background: var(--fail);
+ transform: rotate(42deg);
+ transform-origin: left center;
+}
+.playground-break i:first-child { top: 3px }
+.playground-break i:last-child { top: 15px }
+
+.playground.is-running .playground-pulse { opacity: 1 }
+.playground.is-running.is-baseline .playground-pulse {
+ animation: play-travel-full 900ms var(--ease-io) forwards;
+}
+.playground.is-running.is-baseline .playground-edge .rail-live {
+ background: var(--pass);
+ animation: rail-resolve 900ms var(--ease-io) forwards;
+}
+.playground.is-running.is-baseline .playground-node:not([data-play-node="producer"]) { border-color: var(--pass); background: var(--tint-pass) }
+.playground.is-running.is-baseline .playground-edge-impact .rail { background: var(--pass) }
+.playground.is-running.is-candidate .playground-pulse {
+ background: var(--fail);
+ box-shadow: 0 0 0 4px var(--tint-fail);
+ animation: play-travel-half 640ms var(--ease-io) forwards;
+}
+.playground.is-running.is-candidate .playground-edge .rail-live {
+ animation: rail-half 640ms var(--ease-io) forwards;
+}
+.playground.is-running.is-candidate .playground-break {
+ opacity: 1;
+ transition: opacity var(--t-fast) var(--ease) 640ms;
+}
+.playground.is-running.is-candidate .playground-node:not([data-play-node="producer"]) { border-color: var(--edge-fail); background: var(--tint-fail) }
+.playground.is-running.is-candidate .playground-edge-impact .rail {
+ background: repeating-linear-gradient(90deg, var(--fail) 0 6px, transparent 6px 13px);
+ opacity: .78;
+ animation: impact-arrive var(--t-mid) var(--ease) backwards;
+}
+.playground.is-running.is-candidate .playground-edge-impact:nth-of-type(4) .rail { animation-delay: 720ms }
+.playground.is-running.is-candidate .playground-edge-impact:nth-of-type(6) .rail { animation-delay: 880ms }
+@keyframes impact-arrive { from { opacity: 0 } }
+
+@keyframes play-travel-full {
+ from { transform: translateX(-5.5px) }
+ to { transform: translateX(calc(var(--edge-w, 160px) - 5.5px)) }
+}
+@keyframes play-travel-half {
+ from { transform: translateX(-5.5px) }
+ to { transform: translateX(calc(var(--edge-w, 160px) * var(--break-stop) - 5.5px)) }
+}
+@keyframes rail-resolve { from { transform: scaleX(0) } to { transform: scaleX(1) } }
+@keyframes rail-half { from { transform: scaleX(0) } to { transform: scaleX(var(--break-stop)) } }
+
+.playground-result {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s0);
+ min-height: 88px;
+ padding: var(--s3) var(--s6);
+ border-top: 1px solid var(--border);
+ background: var(--bg);
+ font-size: .9375rem;
+}
+.playground-result strong { font-weight: 600; letter-spacing: -.012em }
+.playground-result strong.pass { color: var(--pass-text) }
+.playground-result strong.fail { color: var(--fail-text) }
+.playground-result span { color: var(--muted); font-size: .875rem }
+
+.playground-witness {
+ margin: 0 var(--s2) var(--s2);
+ overflow: hidden;
+ border: 1px solid var(--edge-fail);
+ border-radius: var(--radius);
+ background: var(--surface);
+ box-shadow: inset 4px 0 0 var(--fail);
+ opacity: 0;
+ transform: translateY(8px);
+ transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease);
+}
+.playground-witness[hidden] { display: none }
+.playground-witness.is-open { opacity: 1; transform: none }
+.playground-witness > div { display: flex; flex-direction: column; gap: var(--s1); padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border) }
+.playground-witness > div > span { color: var(--fail-text); font: 650 .6875rem var(--mono); letter-spacing: .1em; text-transform: uppercase }
+.playground-witness > div > strong { font-size: 1rem; font-weight: 600 }
+.playground-witness dl { display: grid; grid-template-columns: repeat(4, 1fr); margin: 0 }
+.playground-witness dl > div { min-width: 0; padding: var(--s2) var(--s3); border-right: 1px solid var(--border) }
+.playground-witness dl > div:last-child { border-right: 0 }
+.playground-witness dt { color: var(--subtle); font: 650 .6875rem var(--mono); letter-spacing: .08em; text-transform: uppercase }
+.playground-witness dd { margin: var(--s1) 0 0; overflow-wrap: anywhere; color: var(--text); font-size: .8125rem }
+.playground-witness dl > div:nth-child(1) dd { color: var(--pass-text) }
+.playground-witness dl > div:nth-child(2) dd { color: var(--fail-text) }
+
+/* ====================================================== bug section === */
+
+.meaning-diff { display: grid; grid-template-columns: 1fr 96px 1fr; align-items: center }
+.payload { overflow: hidden }
+.payload-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--s2);
+ padding: var(--s2) var(--s3);
+ border-bottom: 1px solid var(--border);
+ background: var(--surface-2);
+ color: var(--subtle);
+ font: 650 .6875rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.badge { padding: 3px 7px; border: 1px solid currentColor; border-radius: 6px }
+.badge.pass { color: var(--pass-text) }
+.pass { color: var(--pass-text) }
+.fail { color: var(--fail-text) }
+.muted { color: var(--subtle) }
+
+.payload pre, .terminal pre, .yaml {
+ overflow: auto;
+ background: var(--surface-code);
+ font-size: .8125rem;
+ line-height: 1.75;
+ color: var(--code);
+}
+.payload pre { padding: var(--s3) }
+.payload pre .k { color: var(--pulse) }
+.payload pre .s { color: var(--pass-text) }
+.payload pre .b, .payload pre .n { color: var(--unknown-text) }
+.payload > p {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--s1);
+ min-height: 72px;
+ padding: var(--s2) var(--s3);
+ border-top: 1px solid var(--border);
+ color: var(--muted);
+ font-size: .8125rem;
+ line-height: 1.55;
+}
+.flow-dot, .break-dot {
+ flex: 0 0 auto;
+ width: 7px; height: 7px;
+ margin-top: 7px;
+ border-radius: 50%;
+ background: var(--pass);
+}
+.break-dot { background: var(--fail) }
+
+.contract-cut { position: relative; display: grid; place-items: center; height: 3px }
+.cut-rail {
+ display: block;
+ width: 100%; height: 3px;
+ border-radius: 2px;
+ background: linear-gradient(90deg, var(--pulse) 0 42%, transparent 42% 58%, var(--edge) 58%);
+}
+.contract-cut i {
+ position: absolute;
+ left: 46%;
+ width: 20px; height: 3px;
+ border-radius: 2px;
+ background: var(--fail);
+ transform: rotate(42deg);
+ transform-origin: left center;
+}
+.contract-cut i:nth-of-type(1) { top: -9px }
+.contract-cut i:nth-of-type(2) { top: 6px }
+
+.diff-caption { margin-top: var(--s4); color: var(--muted); text-align: center }
+.diff-caption strong { color: var(--text); font-weight: 600 }
+.diff-caption code { color: var(--pulse); font-size: .9375rem }
+
+/* ========================================================== method === */
+
+.method-rail {
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ padding: 0;
+ overflow: hidden;
+ list-style: none;
+}
+.method-rail li {
+ position: relative;
+ min-height: 200px;
+ padding: var(--s3);
+ border-right: 1px solid var(--border);
+}
+.method-rail li:last-child { border-right: 0 }
+.method-rail li::before {
+ content: "";
+ position: absolute;
+ top: 47px; left: 0; right: 0;
+ height: 1.5px;
+ background: var(--edge);
+ opacity: .55;
+}
+.method-rail li:first-child::before { left: var(--s3) }
+.method-rail li:last-child::before { right: calc(100% - var(--s3) - 28px) }
+.method-rail span {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ place-items: center;
+ width: 28px; height: 28px;
+ border: 1.5px solid var(--pulse);
+ border-radius: 50%;
+ background: var(--surface);
+ color: var(--pulse);
+ font: 650 .6875rem var(--mono);
+}
+.method-rail strong { display: block; margin-top: var(--s5); font-size: 1.0625rem; letter-spacing: -.018em }
+.method-rail p { margin-top: var(--s1); color: var(--muted); font-size: .8125rem; line-height: 1.55 }
+.method-rail .step-break span { border-color: var(--fail); color: var(--fail-text) }
+/* The method rail is itself a graph edge, so step 03 has to actually cut it:
+ the rail opens a gap and the canonical two-stroke mark sits in the gap. */
+.method-rail .step-break::before {
+ background: linear-gradient(90deg, var(--edge) 0 52%, transparent 52% 74%, var(--edge) 74%);
+}
+.rail-cut {
+ position: absolute;
+ top: 47px; left: 60%;
+ width: 16px; height: 0;
+}
+.rail-cut b {
+ position: absolute;
+ left: 0;
+ width: 15px; height: 2.5px;
+ border-radius: 2px;
+ background: var(--fail);
+ transform: rotate(42deg);
+ transform-origin: left center;
+}
+.rail-cut b:first-child { top: -6px }
+.rail-cut b:last-child { top: 4px }
+
+/* ======================================================= contracts === */
+
+.code-layout { display: grid; grid-template-columns: 1.6fr .8fr; overflow: hidden }
+.code-pane { border-right: 1px solid var(--border); min-width: 0 }
+.pane-bar {
+ display: flex;
+ align-items: center;
+ gap: var(--s1);
+ padding: var(--s2) var(--s3);
+ border-bottom: 1px solid var(--border);
+ background: var(--surface-2);
+ color: var(--subtle);
+ font: 600 .75rem var(--mono);
+}
+.pane-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pulse) }
+.yaml { padding: var(--s4) var(--s3) }
+.yaml .key { color: var(--pulse) }
+.yaml .string { color: var(--pass-text) }
+.yaml .sev { color: var(--fail-text) }
+.yaml .bool, .yaml .num { color: var(--unknown-text) }
+
+.evaluator-list { padding: var(--s4) var(--s3) }
+.list-label {
+ color: var(--subtle);
+ font: 650 .75rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.evaluator-list ul { margin: var(--s3) 0; padding: 0; list-style: none }
+.evaluator-list li {
+ display: flex;
+ gap: var(--s2);
+ padding: var(--s2) 0;
+ border-bottom: 1px solid var(--border);
+ font-size: .875rem;
+}
+.evaluator-list li span { color: var(--subtle); font: .75rem var(--mono) }
+
+/* Text link as a graph edge that completes on hover. */
+.edge-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: .8125rem;
+ font-weight: 600;
+ color: var(--pulse);
+}
+.edge-link svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; transition: transform var(--t-fast) var(--ease) }
+.edge-link:hover svg { transform: translate(2px, -2px) }
+
+/* ======================================================== terminal === */
+
+.terminal { overflow: hidden }
+.terminal-bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: var(--s2) var(--s3);
+ border-bottom: 1px solid var(--border);
+ background: var(--surface-2);
+}
+.tl { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong) }
+.terminal-bar strong { margin-left: var(--s1); color: var(--subtle); font: 600 .75rem var(--mono) }
+.terminal pre { padding: var(--s4) var(--s3) }
+
+/* ===================================================== architecture ===
+ Built from the same primitives as every other graph on the page rather
+ than shipped as an image: theme-aware, selectable, and it reflows to a
+ vertical topology instead of scaling its labels into illegibility.
+ ====================================================================== */
+
+.arch-frame { padding: var(--s6) var(--s5) var(--s5) }
+.pane-label {
+ color: var(--subtle);
+ font: 650 .75rem var(--mono);
+ letter-spacing: .12em;
+ text-transform: uppercase;
+}
+
+.pipeline {
+ display: grid;
+ grid-template-columns: 1fr minmax(40px, .55fr) 1fr minmax(40px, .55fr) 1fr minmax(40px, .55fr) 1fr minmax(40px, .55fr) 1fr;
+ align-items: stretch;
+ margin-top: var(--s6);
+ padding: 0;
+ list-style: none;
+}
+.stage {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 0;
+ padding: var(--s2);
+ border: 1px solid var(--border-strong);
+ border-radius: 10px;
+ background: var(--surface-2);
+}
+.stage-tag {
+ color: var(--subtle);
+ font: 650 .75rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.stage strong { font-size: 1rem; font-weight: 600; letter-spacing: -.018em }
+.stage small { color: var(--subtle); font-size: .75rem; line-height: 1.4 }
+.stage-outside { border-style: dashed }
+.stage-check { border-color: var(--pulse) }
+.stage-check .stage-tag { color: var(--pulse) }
+.stage-trace { border-color: var(--edge-fail) }
+.stage-trace .stage-tag { color: var(--fail-text) }
+.stage-explain { border-color: var(--edge-pass) }
+.stage-explain .stage-tag { color: var(--pass-text) }
+
+.stage-edge { position: relative; display: grid; place-items: center; grid-row: 1 }
+.stage-edge .rail {
+ width: 100%; height: 1.5px;
+ border-radius: 2px;
+ background: var(--edge);
+}
+/* Contract labels drop below the row: the edge columns are far too narrow
+ to hold them without colliding with the stages either side. */
+.stage-edge em {
+ position: absolute;
+ top: calc(100% + 10px);
+ left: 50%;
+ color: var(--subtle);
+ font: .6875rem var(--mono);
+ font-style: normal;
+ white-space: nowrap;
+ transform: translateX(-50%);
+}
+.stage { grid-row: 1 }
+
+/* Brackets say exactly which stages sit either side of the trace boundary. */
+.bracket {
+ grid-row: 3;
+ padding-top: var(--s1);
+ border-top: 1px dashed var(--border-strong);
+}
+.bracket span {
+ color: var(--subtle);
+ font: 650 .6875rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.bracket-outside { grid-column: 1 }
+.bracket-core { grid-column: 3 / -1 }
+.bracket-core { border-top-color: var(--pulse); border-top-style: solid }
+.bracket-core span { color: var(--pulse) }
+
+.pipeline { row-gap: var(--s6) }
+.arch-note { margin-top: var(--s5); color: var(--muted); font-size: .8125rem }
+
+/* ========================================================== report === */
+
+.report-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2) }
+.report-pane {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ background: var(--surface);
+ box-shadow: var(--shadow-card);
+}
+.report-pane .yaml { flex: 1 }
+.pane-note { margin-left: auto; color: var(--subtle); font: .6875rem var(--mono) }
+
+.report-mock { display: flex; flex-direction: column; flex: 1 }
+.mock-verdict { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border) }
+.mock-verdict > div { padding: var(--s2) var(--s3); background: var(--surface) }
+.mock-verdict span {
+ display: block;
+ color: var(--subtle);
+ font: 650 .6875rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.mock-verdict strong { display: block; margin-top: 4px; font: 650 .9375rem var(--mono) }
+
+.mock-row { padding: var(--s2) var(--s3); border-top: 1px solid var(--border) }
+.mock-label {
+ display: block;
+ margin-bottom: 6px;
+ color: var(--subtle);
+ font: 650 .75rem var(--mono);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+.mock-row p { font-size: .8125rem; line-height: 1.6; color: var(--muted) }
+.mock-row code { font-size: .75rem }
+.mock-row code.ok { color: var(--pass-text) }
+.mock-row code.bad { color: var(--fail-text) }
+.mock-row b { color: var(--text); font-weight: 600 }
+
+.mock-edge { display: flex; align-items: center; gap: var(--s1); font-family: var(--mono) }
+.mock-edge i {
+ position: relative;
+ width: 34px; height: 2px;
+ border-radius: 2px;
+ background: linear-gradient(90deg, var(--pulse) 0 40%, transparent 40% 60%, var(--edge) 60%);
+}
+.mock-edge i::before, .mock-edge i::after {
+ content: "";
+ position: absolute;
+ left: 44%;
+ width: 9px; height: 2px;
+ border-radius: 2px;
+ background: var(--fail);
+ transform: rotate(42deg);
+ transform-origin: left center;
+}
+.mock-edge i::before { top: -4px }
+.mock-edge i::after { top: 3px }
+
+.mock-chips { display: flex; flex-wrap: wrap; gap: 6px }
+.mock-chips em {
+ padding: 3px 8px;
+ border: 1px solid var(--edge-fail);
+ border-radius: 6px;
+ color: var(--fail-text);
+ font: .6875rem var(--mono);
+ font-style: normal;
+}
+.mock-repair { margin-top: auto; background: var(--tint-pulse) }
+
+/* =========================================================== proof === */
+
+.proof-section > .shell {
+ display: grid;
+ grid-template-columns: minmax(250px, .55fr) minmax(0, 1.45fr);
+ gap: var(--s8);
+}
+.proof-intro h2 { max-width: 10ch }
+.proof-intro > p:last-child { max-width: 34ch; margin-top: var(--s3); color: var(--muted) }
+.proof-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ margin: 0;
+ border-top: 1px solid var(--border-strong);
+}
+.proof-grid > div { min-width: 0; padding: var(--s4) var(--s3); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border) }
+.proof-grid > div:nth-child(3n) { border-right: 0 }
+.proof-grid dt { color: var(--subtle); font: 650 .6875rem var(--mono); letter-spacing: .1em; text-transform: uppercase }
+.proof-grid dd { margin: var(--s2) 0 0 }
+.proof-grid dd strong { display: block; overflow-wrap: anywhere; color: var(--text); font-size: clamp(1.4rem, 2.6vw, 2.25rem); font-weight: 620; letter-spacing: -.035em }
+.proof-grid small { display: block; margin-top: var(--s1); color: var(--muted); font-size: .75rem }
+.proof-note { grid-column: 2; color: var(--muted); font-size: .8125rem }
+.proof-note a { color: var(--text); text-decoration: underline; text-decoration-color: var(--edge-pulse); text-underline-offset: 4px }
+
+/* ===================================================== limitations === */
+
+.limitations-section {
+ margin-top: var(--s12);
+ border-block: 1px solid var(--border);
+ background: var(--bg-sunk);
+}
+.limitations-section > .shell {
+ display: grid;
+ grid-template-columns: minmax(260px, .7fr) minmax(0, 1.3fr);
+ gap: var(--s8);
+ padding-bottom: var(--s10);
+}
+.limitations-lead > span { color: var(--unknown-text); font: 650 .6875rem var(--mono); letter-spacing: .13em; text-transform: uppercase }
+.limitations-lead h2 { max-width: 11ch; margin-top: var(--s3) }
+.limitations-lead p { margin-top: var(--s3); color: var(--muted) }
+.limitations-list { padding: 0; border-top: 1px solid var(--border-strong); list-style: none }
+.limitations-list li { display: grid; grid-template-columns: 48px 1fr; gap: var(--s2); padding: var(--s3) 0; border-bottom: 1px solid var(--border) }
+.limitations-list span { color: var(--unknown-text); font: .75rem var(--mono) }
+.limitations-list p { max-width: 60ch; color: var(--text); font-size: 1rem }
+.limitations-link { grid-column: 2; justify-self: start; display: inline-flex; gap: var(--s1); min-height: 44px; align-items: center; color: var(--muted); font-size: .875rem }
+.limitations-link:hover { color: var(--text) }
+
+/* ==================================================== contribution === */
+
+.contribution-section > .shell {
+ display: grid;
+ grid-template-columns: minmax(260px, .62fr) minmax(0, 1.38fr);
+ gap: var(--s8);
+}
+.contribution-lead h2 { max-width: 10ch }
+.contribution-lead > p:last-child { max-width: 34ch; margin-top: var(--s3); color: var(--muted) }
+.contribution-paths { border-top: 1px solid var(--border-strong) }
+.contribution-paths a {
+ position: relative;
+ display: grid;
+ grid-template-columns: 110px minmax(0, 1fr) minmax(190px, .72fr);
+ gap: var(--s3);
+ align-items: center;
+ min-height: 116px;
+ padding: var(--s3) var(--s6) var(--s3) 0;
+ border-bottom: 1px solid var(--border);
+}
+.contribution-paths a::after { content: "↗"; position: absolute; right: var(--s1); color: var(--subtle); transition: color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease) }
+.contribution-paths a:hover::after, .contribution-paths a:focus-visible::after { color: var(--pulse); transform: translate(2px, -2px) }
+.contribution-paths span { color: var(--pulse); font: 650 .6875rem var(--mono); letter-spacing: .1em; text-transform: uppercase }
+.contribution-paths strong { font-size: 1.125rem; font-weight: 600; line-height: 1.35 }
+.contribution-paths small { color: var(--muted); font-size: .8125rem; line-height: 1.55 }
+.contribution-footer { grid-column: 2; display: flex; flex-wrap: wrap; gap: var(--s4) }
+.contribution-footer a { min-height: 44px; display: inline-flex; align-items: center; color: var(--muted); font-size: .875rem; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 4px }
+.contribution-footer a:hover { color: var(--text) }
+
+/* ============================================================= cta === */
+
+.final-cta {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: var(--s9) var(--s7);
+ text-align: center;
+ box-shadow: var(--shadow-lift);
+}
+.final-cta h2 { max-width: 16ch }
+.final-cta .eyebrow { justify-content: center }
+.cta-lede { max-width: 52ch; margin: var(--s3) 0 var(--s6) }
+.final-cta .hero-actions { justify-content: center }
+.cta-mark { position: relative; width: 160px; height: 3px; margin-bottom: var(--s6) }
+.cta-rail {
+ display: block;
+ width: 100%; height: 3px;
+ border-radius: 2px;
+ background: linear-gradient(90deg, var(--pulse) 0 44%, transparent 44% 56%, var(--edge) 56%);
+}
+.cta-mark::before, .cta-mark::after {
+ content: "";
+ position: absolute;
+ top: -3px;
+ width: 9px; height: 9px;
+ border-radius: 50%;
+ background: var(--pulse);
+}
+.cta-mark::before { left: -4px }
+.cta-mark::after { right: -4px; background: var(--edge) }
+.cta-mark i {
+ position: absolute;
+ left: 46%;
+ width: 20px; height: 3px;
+ border-radius: 2px;
+ background: var(--fail);
+ transform: rotate(42deg);
+ transform-origin: left center;
+}
+.cta-mark i:first-of-type { top: -8px }
+.cta-mark i:last-of-type { top: 7px }
+
+/* =========================================================== modal === */
+
+.setup-modal[hidden] { display: none }
+.setup-modal {
+ position: fixed;
+ inset: 0;
+ z-index: 100;
+ display: grid;
+ place-items: center;
+ padding: var(--s3);
+}
+.modal-backdrop {
+ position: absolute;
+ inset: 0;
+ background: rgba(11, 15, 20, .5);
+ -webkit-backdrop-filter: blur(6px);
+ backdrop-filter: blur(6px);
+ opacity: 0;
+ transition: opacity var(--t-mid) var(--ease);
+}
+.setup-modal.is-open .modal-backdrop { opacity: 1 }
+
+/* Opening the dialog expands a subgraph: it grows from the edge outward. */
+.setup-dialog {
+ position: relative;
+ width: min(720px, 100%);
+ max-height: calc(100dvh - var(--s6));
+ overflow: auto;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ background: var(--surface);
+ box-shadow: var(--shadow-modal);
+ opacity: 0;
+ transform: scale(.9);
+ transition: opacity 260ms var(--ease), transform 380ms var(--ease);
+}
+.setup-modal.is-open .setup-dialog { opacity: 1; transform: none }
+
+.setup-dialog-header {
+ display: flex;
+ justify-content: space-between;
+ gap: var(--s3);
+ padding: var(--s5) var(--s5) var(--s3);
+}
+.setup-dialog-header h2 { font-size: clamp(1.75rem, 3.6vw, 2.25rem) }
+.setup-dialog-header p:not(.eyebrow) {
+ max-width: 52ch;
+ margin-top: var(--s2);
+ color: var(--muted);
+ font-size: .875rem;
+ line-height: 1.6;
+}
+.modal-close {
+ position: sticky;
+ top: 0;
+ flex: 0 0 40px;
+ display: grid;
+ place-items: center;
+ width: 40px; height: 40px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--surface);
+ color: var(--muted);
+ cursor: pointer;
+ transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
+}
+.modal-close svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round }
+.modal-close:hover { color: var(--text); border-color: var(--border-strong) }
+
+.setup-tabs {
+ position: relative;
+ display: flex;
+ gap: var(--s0);
+ overflow-x: auto;
+ padding: 0 var(--s5);
+ border-bottom: 1px solid var(--border);
+ scrollbar-width: none;
+}
+.setup-tabs::-webkit-scrollbar { display: none }
+.setup-tab {
+ min-height: 48px;
+ padding: 0 var(--s2);
+ border: 0;
+ background: transparent;
+ color: var(--muted);
+ font: 600 .8125rem var(--sans);
+ white-space: nowrap;
+ cursor: pointer;
+ transition: color var(--t-fast) var(--ease);
+}
+.setup-tab:hover, .setup-tab.is-active { color: var(--text) }
+/* The active tab indicator is an edge that travels between tabs. */
+.tab-rail {
+ position: absolute;
+ bottom: -1px; left: 0;
+ height: 2px;
+ border-radius: 2px;
+ background: var(--pulse);
+ transition: transform var(--t-mid) var(--ease), width var(--t-mid) var(--ease);
+}
+
+.setup-panel { padding: var(--s5) var(--s5) var(--s4) }
+.setup-panel[hidden] { display: none }
+.setup-panel-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--s2) }
+.setup-panel-meta strong { font-size: 1rem; font-weight: 600; letter-spacing: -.015em }
+.setup-panel p { margin: var(--s2) 0 var(--s3); color: var(--muted); font-size: .875rem; line-height: 1.6 }
+.support-badge {
+ padding: 4px 8px;
+ border: 1px solid currentColor;
+ border-radius: 999px;
+ font: 650 .6875rem var(--mono);
+ white-space: nowrap;
+}
+.support-badge.available { color: var(--pass-text) }
+.support-badge.cli { color: var(--pulse) }
+.support-badge.planned { color: var(--unknown-text) }
+
+.command-line {
+ display: flex;
+ align-items: center;
+ gap: var(--s1);
+ padding: var(--s1) var(--s1) var(--s1) var(--s2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--surface-code);
+}
+.command-line code {
+ flex: 1;
+ min-width: 0;
+ overflow: auto;
+ color: var(--code);
+ font-size: .75rem;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+}
+.copy-button {
+ flex: 0 0 auto;
+ min-height: 36px;
+ padding: 0 var(--s2);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ background: var(--surface);
+ color: var(--text);
+ font: 600 .75rem var(--sans);
+ cursor: pointer;
+ transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
+}
+.copy-button:hover { border-color: var(--border-strong) }
+.copy-button.copied { border-color: var(--edge-pass); color: var(--pass-text) }
+.copy-feedback { display: block; min-height: 20px; margin-top: var(--s1); color: var(--pass-text); font: .6875rem var(--mono) }
+
+body.modal-open { overflow: hidden }
+
+/* ========================================================== footer === */
+
+.site-footer { margin-top: var(--s11); border-top: 1px solid var(--border) }
+.footer-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--s4);
+ min-height: 112px;
+ color: var(--subtle);
+ font-size: .8125rem;
+}
+.footer-brand { display: flex; align-items: center; gap: var(--s1) }
+.footer-inner nav { display: flex; flex-wrap: wrap; gap: var(--s3) }
+.footer-inner nav a { transition: color var(--t-fast) var(--ease) }
+.footer-inner nav a:hover { color: var(--text) }
+
+/* ====================================================== responsive === */
+
+@media (max-width: 1080px) {
+ :root { --gutter: var(--s5) }
+ .section > .shell { padding-top: var(--s10) }
+ .section > .shell::before { height: calc(var(--s10) - var(--s6) - 3px) }
+ .method-rail { grid-template-columns: repeat(3, 1fr) }
+ .method-rail li:nth-child(3) { border-right: 0 }
+ .method-rail li:nth-child(-n+3) { border-bottom: 1px solid var(--border) }
+ .method-rail li:last-child::before,
+ .method-rail li:nth-child(3)::before { right: calc(100% - var(--s3) - 28px) }
+ .method-rail li:nth-child(4)::before { left: var(--s3) }
+ .code-layout { grid-template-columns: 1fr }
+ .code-pane { border-right: 0; border-bottom: 1px solid var(--border) }
+ .final-cta { padding: var(--s8) var(--s5) }
+}
+
+@media (max-width: 900px) {
+ .nav-links a:not(.nav-github) { display: none }
+ .hero { padding: var(--s8) 0 var(--s7) }
+ .section-heading { flex-direction: column; align-items: flex-start; gap: var(--s3); margin-bottom: var(--s6) }
+ .section-heading > p { flex: 1 1 auto }
+ .bug-heading { grid-template-columns: 1fr }
+ .bug-heading > p { margin-top: 0 }
+ .terminal-section > .shell,
+ .proof-section > .shell,
+ .limitations-section > .shell,
+ .contribution-section > .shell { grid-template-columns: 1fr; gap: var(--s6) }
+ .terminal-section .section-heading { margin-top: 0 }
+ .proof-note,
+ .limitations-link,
+ .contribution-footer { grid-column: 1 }
+ .contribution-paths a { grid-template-columns: 92px minmax(0, 1fr) }
+ .contribution-paths small { grid-column: 2 }
+ .meaning-diff { grid-template-columns: 1fr; gap: var(--s6) }
+ .contract-cut { width: 3px; height: 56px; margin-inline: auto }
+ .cut-rail { width: 3px; height: 100%; background: linear-gradient(180deg, var(--pulse) 0 42%, transparent 42% 58%, var(--edge) 58%) }
+ .contract-cut i { left: -9px; top: 42%; transform: rotate(-42deg) }
+ .contract-cut i:nth-of-type(1) { top: 38% }
+ .contract-cut i:nth-of-type(2) { top: 52% }
+ .playground-graph { padding: var(--s7) var(--s4) }
+ .playground-result { padding-inline: var(--s4) }
+
+ /* The pipeline becomes a vertical topology rather than shrinking. */
+ .arch-frame { padding: var(--s5) var(--s3) }
+ .pipeline { grid-template-columns: 1fr; row-gap: 0; margin-top: var(--s4) }
+ .stage, .stage-edge, .bracket { grid-row: auto; grid-column: 1 }
+ .stage-edge { height: 48px }
+ .stage-edge .rail { width: 1.5px; height: 100% }
+ .stage-edge em {
+ top: 50%; left: calc(50% + 14px);
+ max-width: calc(50% - 20px);
+ white-space: normal;
+ overflow-wrap: anywhere;
+ line-height: 1.35;
+ transform: translateY(-50%);
+ }
+ /* Brackets become inline captions; a vertical stack has no row to span. */
+ .bracket { padding: var(--s1) 0 var(--s3); border-top: 0 }
+ .bracket-outside { order: -1; padding-top: 0 }
+ .bracket-core { display: none }
+
+ .report-split { grid-template-columns: 1fr }
+}
+
+/* ---- mobile is rethought, not compressed ---- */
+@media (max-width: 700px) {
+ :root { --gutter: var(--s3) }
+
+ .nav { min-height: 64px }
+ .nav-github { padding-inline: var(--s2) }
+
+ .hero { padding: var(--s7) 0 var(--s6) }
+ h1 { margin-bottom: var(--s3) }
+ .hero-lede { margin-bottom: var(--s5) }
+ .hero-actions { gap: var(--s1) }
+ .hero-actions .button { flex: 1 1 140px }
+ .install-line { margin-top: var(--s4) }
+ /* Signal that the command runs past the edge instead of just clipping it. */
+ .install-line code {
+ -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent);
+ mask-image: linear-gradient(90deg, #000 88%, transparent);
+ }
+
+ .section > .shell { padding-top: var(--s9) }
+ .section > .shell::before { top: var(--s5); height: calc(var(--s9) - var(--s5) - 3px) }
+ .section-heading { margin-bottom: var(--s5) }
+ .section-heading > p { font-size: .9375rem }
+ .bug-heading h2 { font-size: clamp(2.6rem, 14vw, 4.25rem) }
+ .bug-heading > p { padding-left: var(--s2) }
+ .proof-grid { grid-template-columns: 1fr 1fr }
+ .proof-grid > div:nth-child(3n) { border-right: 1px solid var(--border) }
+ .proof-grid > div:nth-child(2n) { border-right: 0 }
+ .proof-grid > div { padding: var(--s3) var(--s2) }
+ .proof-grid dd strong { font-size: 1.45rem }
+ .limitations-list li { grid-template-columns: 36px 1fr }
+ .contribution-paths a { grid-template-columns: 1fr; gap: var(--s1); padding-right: var(--s5) }
+ .contribution-paths small { grid-column: 1 }
+ .contribution-footer { gap: var(--s2) var(--s4) }
+
+ .pulse-scene { margin-top: var(--s6); border-radius: var(--radius) }
+ .scene-toolbar { flex-direction: column; align-items: stretch; gap: var(--s2); padding: var(--s2) }
+ .replay { justify-content: center; width: 100% }
+ .scene-status { padding: var(--s2) var(--s2) 0 }
+ .status { flex: 1 1 120px; padding: var(--s1) var(--s2) }
+ .status small { display: none }
+
+ /* Vertical topology, built from the same primitives. No rotation hacks. */
+ .graph-flow { grid-template-columns: 1fr; padding: var(--s4) var(--s2) var(--s5) }
+ .graph-node { padding: var(--s2) }
+ /* A vertical edge needs more length than a horizontal one: the cut is a
+ fixed pixel height, so the pulse needs real distance to stop short of it. */
+ .graph-edge { height: 88px; --break-stop: .28 }
+ .graph-edge .rail {
+ top: 0; left: 50%;
+ width: 2px; height: 100%;
+ margin: 0 0 0 -1px;
+ }
+ .first-edge .rail-live {
+ top: 0; left: 50%;
+ width: 3px; height: calc(var(--break-stop) * 100%);
+ margin: 0 0 0 -1.5px;
+ transform-origin: top center;
+ }
+ .blast-edge .rail {
+ background: repeating-linear-gradient(180deg, var(--edge-fail) 0 6px, transparent 6px 13px);
+ }
+ .pulse-scene.is-playing:not(.impact-active) .blast-edge .rail { background: var(--edge) }
+ .pulse-scene.is-playing.baseline-active .blast-edge .rail { background: var(--pass) }
+ .pulse {
+ top: 0; left: 50%;
+ margin: -5.5px 0 0 -5.5px;
+ transform: translateY(calc(var(--edge-h, 88px) * var(--break-stop)));
+ }
+ .pulse::before { display: none }
+ .break-mark { top: 50%; left: 50%; margin: -9px 0 0 -8px }
+ .break-mark i:first-child { top: 0 }
+ .break-mark i:last-child { top: 12px }
+ .edge-caption { top: calc(50% - 30px); left: calc(50% + 16px); transform: none }
+ .pulse-scene.is-playing.candidate-active .pulse { animation-name: pulse-travel-v }
+ @keyframes pulse-travel-v {
+ from { transform: translateY(0) }
+ to { transform: translateY(calc(var(--edge-h, 88px) * var(--break-stop)) ) }
+ }
+
+ .scene-witness { margin: 0 var(--s2) var(--s2) }
+ .witness-heading, .witness-values > div, .scene-witness > p { padding-inline: var(--s2) }
+ .witness-heading strong { font-size: .9375rem }
+ .witness-values { grid-template-columns: 1fr }
+
+ .playground-toolbar { flex-direction: column; align-items: stretch; padding: var(--s2) }
+ .segmented { width: 100% }
+ .playground-choice { flex: 1 1 0; min-width: 0; padding-inline: var(--s1); font-size: .75rem }
+ .playground-run { margin-left: 0; width: 100% }
+ .playground-graph { grid-template-columns: 1fr; gap: var(--s2); padding: var(--s5) var(--s3) }
+ .playground-node { padding: var(--s2) }
+ .playground-edge { height: 96px; --break-stop: .30 }
+ .playground-edge .rail { top: 0; left: 50%; width: 2px; height: 100%; margin: 0 0 0 -1px }
+ .playground-edge .rail-live { top: 0; left: 50%; width: 3px; height: 100%; margin: 0 0 0 -1.5px; transform: scaleY(0); transform-origin: top center }
+ .playground-edge small {
+ top: 50%; left: calc(50% + 14px);
+ max-width: calc(50% - 20px);
+ white-space: normal;
+ overflow-wrap: anywhere;
+ line-height: 1.35;
+ text-align: left;
+ transform: translateY(-50%);
+ }
+ .playground-pulse { top: 0; left: 50%; margin: -5.5px 0 0 -5.5px; transform: translateY(0) }
+ .playground-break { top: 50%; left: 50%; margin: -9px 0 0 -8px }
+ .playground-break i:first-child { top: 0 }
+ .playground-break i:last-child { top: 12px }
+ .playground.is-running.is-baseline .playground-pulse { animation-name: play-travel-full-v }
+ .playground.is-running.is-candidate .playground-pulse { animation-name: play-travel-half-v }
+ .playground.is-running.is-baseline .playground-edge .rail-live { animation-name: rail-resolve-v }
+ .playground.is-running.is-candidate .playground-edge .rail-live { animation-name: rail-half-v }
+ .playground.is-running.is-candidate .playground-edge-impact .rail { background: repeating-linear-gradient(180deg, var(--fail) 0 6px, transparent 6px 13px) }
+ @keyframes play-travel-full-v { from { transform: translateY(0) } to { transform: translateY(calc(var(--edge-h, 96px) - 11px)) } }
+ @keyframes play-travel-half-v { from { transform: translateY(0) } to { transform: translateY(calc(var(--edge-h, 64px) * .46)) } }
+ @keyframes rail-resolve-v { from { transform: scaleY(0) } to { transform: scaleY(1) } }
+ @keyframes rail-half-v { from { transform: scaleY(0) } to { transform: scaleY(var(--break-stop)) } }
+ .playground-result { padding: var(--s3) }
+ .playground-witness > div { padding: var(--s3) }
+ .playground-witness dl { grid-template-columns: 1fr 1fr }
+ .playground-witness dl > div { border-bottom: 1px solid var(--border) }
+ .playground-witness dl > div:nth-child(2) { border-right: 0 }
+ .playground-witness dl > div:nth-last-child(-n+2) { border-bottom: 0 }
+
+ .method-rail { grid-template-columns: 1fr }
+ .method-rail li { min-height: 0; border-right: 0; border-bottom: 1px solid var(--border) }
+ .method-rail li:last-child { border-bottom: 0 }
+ .method-rail li::before {
+ top: var(--s3); bottom: calc(var(--s3) * -1); left: calc(var(--s3) + 12px) !important;
+ right: auto !important;
+ width: 1.5px; height: auto;
+ }
+ .method-rail li:last-child::before { display: none }
+ .method-rail strong { margin-top: var(--s2) }
+ .method-rail .step-break::before {
+ background: linear-gradient(180deg, var(--edge) 0 30%, transparent 30% 58%, var(--edge) 58%);
+ }
+ .rail-cut { top: 68px; left: calc(var(--s3) + 12px) }
+ .rail-cut b { transform: rotate(-42deg); left: -7px }
+ .rail-cut b:first-child { top: 0 }
+ .rail-cut b:last-child { top: 10px }
+
+ .yaml, .evaluator-list, .terminal pre { padding: var(--s3) var(--s2) }
+ .yaml, .terminal pre, .payload pre { font-size: .75rem }
+ .arch-frame { padding: var(--s2) }
+
+ .final-cta { padding: var(--s6) var(--s3); border-radius: var(--radius) }
+ .cta-mark { width: 120px }
+
+ .setup-modal { align-items: flex-end; padding: 0 }
+ .setup-dialog {
+ width: 100%;
+ max-height: 92dvh;
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
+ padding-bottom: env(safe-area-inset-bottom);
+ transform: translateY(16px);
+ }
+ .setup-dialog-header { padding: var(--s4) var(--s3) var(--s2) }
+ .setup-tabs { padding-inline: var(--s3) }
+ .setup-panel { padding: var(--s3) }
+ .command-line { flex-direction: column; align-items: stretch; padding: var(--s2) }
+ .command-line .install-prompt { display: none }
+ .copy-button { width: 100%; min-height: 40px }
+
+ .footer-inner { flex-direction: column; align-items: flex-start; gap: var(--s3); padding: var(--s5) 0 }
+}
+
+@media (max-width: 360px) {
+ .proof-grid { grid-template-columns: 1fr }
+ .proof-grid > div,
+ .proof-grid > div:nth-child(2n),
+ .proof-grid > div:nth-child(3n) { border-right: 0 }
+}
+
+/* ================================================= reduced motion === */
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto }
+ *, *::before, *::after {
+ animation-duration: .01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: .01ms !important;
+ }
+ /* Every state the motion would have revealed is present without it. */
+ .js .section > .shell::before { transform: scaleY(1) }
+ .js .section .eyebrow i { transform: none; opacity: 1 }
+ .pulse-scene.is-playing .graph-node,
+ .pulse-scene.is-playing .scene-status,
+ .pulse-scene.is-playing .scene-witness,
+ .pulse-scene.is-playing .pulse,
+ .pulse-scene.is-playing .break-mark,
+ .pulse-scene.is-playing .edge-caption { opacity: 1; transform: none }
+ .playground.is-running .playground-pulse { opacity: 1 }
+ .playground.is-running .playground-edge .rail-live { transform: scaleX(1) }
+ .playground.is-running.is-candidate .playground-edge .rail-live { transform: scaleX(var(--break-stop)) }
+ .playground.is-running.is-candidate .playground-break { opacity: 1 }
+ .button::after { display: none }
}