cgcardona / muse public
render_domain_registry.py python
2280 lines 97.8 KB
86ea1e94 feat: replace CRDT pre blocks with purpose-built convergence visualizations Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 #!/usr/bin/env python3
2 """Muse Domain Registry — standalone HTML generator.
3
4 Produces a self-contained, shareable page that explains the MuseDomainPlugin
5 protocol, shows the registered plugin ecosystem, and guides developers through
6 scaffolding and publishing their own domain plugin.
7
8 Stand-alone usage
9 -----------------
10 python tools/render_domain_registry.py
11 python tools/render_domain_registry.py --out artifacts/domain_registry.html
12 """
13 from __future__ import annotations
14
15 import json
16 import pathlib
17 import subprocess
18 import sys
19
20 _ROOT = pathlib.Path(__file__).resolve().parent.parent
21
22
23 # ---------------------------------------------------------------------------
24 # Live domain data from the CLI
25 # ---------------------------------------------------------------------------
26
27
28 def _compute_crdt_demos() -> list[dict]:
29 """Run the four CRDT primitives live and return formatted demo output."""
30 sys.path.insert(0, str(_ROOT))
31 try:
32 from muse.core.crdts import GCounter, LWWRegister, ORSet, VectorClock
33
34 # ORSet
35 base, _ = ORSet().add("annotation-GO:0001234")
36 a, _ = base.add("annotation-GO:0001234")
37 b = base.remove("annotation-GO:0001234", base.tokens_for("annotation-GO:0001234"))
38 merged = a.join(b)
39 orset_out = "\n".join([
40 "ORSet — add-wins concurrent merge:",
41 f" base elements: {sorted(base.elements())}",
42 f" A re-adds → elements: {sorted(a.elements())}",
43 f" B removes → elements: {sorted(b.elements())}",
44 f" join(A, B) → elements: {sorted(merged.elements())}",
45 " [A's new token is not tombstoned — add always wins]",
46 ])
47
48 # LWWRegister
49 ra = LWWRegister.from_dict({"value": "80 BPM", "timestamp": 1.0, "author": "agent-A"})
50 rb = LWWRegister.from_dict({"value": "120 BPM", "timestamp": 2.0, "author": "agent-B"})
51 rm = ra.join(rb)
52 lww_out = "\n".join([
53 "LWWRegister — last-write-wins scalar:",
54 f" Agent A writes: '{ra.read()}' at t=1.0",
55 f" Agent B writes: '{rb.read()}' at t=2.0 (later)",
56 f" join(A, B) → '{rm.read()}' [higher timestamp wins]",
57 " join(B, A) → same result [commutativity]",
58 ])
59
60 # GCounter
61 ca = GCounter().increment("agent-A").increment("agent-A")
62 cb = GCounter().increment("agent-B").increment("agent-B").increment("agent-B")
63 cm = ca.join(cb)
64 gc_out = "\n".join([
65 "GCounter — grow-only distributed counter:",
66 f" Agent A x2 → A slot: {ca.value_for('agent-A')}",
67 f" Agent B x3 → B slot: {cb.value_for('agent-B')}",
68 f" join(A, B) global value: {cm.value()}",
69 " [monotonically non-decreasing — joins never lose counts]",
70 ])
71
72 # VectorClock
73 va = VectorClock().increment("agent-A")
74 vb = VectorClock().increment("agent-B")
75 vm = va.merge(vb)
76 vc_out = "\n".join([
77 "VectorClock — causal ordering:",
78 f" Agent A: {va.to_dict()}",
79 f" Agent B: {vb.to_dict()}",
80 f" concurrent_with(A, B): {va.concurrent_with(vb)}",
81 f" merge(A, B): {vm.to_dict()} [component-wise max]",
82 ])
83
84 elem = "annotation-GO:0001234"
85 short = "GO:0001234"
86
87 orset_html = f"""<div class="crdt-vis">
88 <div class="crdt-concurrent">
89 <div class="crdt-rep">
90 <div class="crdt-rep-hdr">Replica A</div>
91 <div class="crdt-op crdt-op-add">+ add("{short}")</div>
92 <div class="crdt-rep-state">&rarr;&thinsp;{{{short}}}</div>
93 </div>
94 <div class="crdt-rep">
95 <div class="crdt-rep-hdr">Replica B</div>
96 <div class="crdt-op crdt-op-rm">&times; remove("{short}")</div>
97 <div class="crdt-rep-state">&rarr;&thinsp;{{}}</div>
98 </div>
99 </div>
100 <div class="crdt-join" style="--crdt-c:#bc8cff">
101 <span class="crdt-join-label">join(A, B)</span>
102 <span class="crdt-join-val" style="color:#bc8cff">{{{short}}}</span>
103 <span class="crdt-join-rule">add-wins &mdash; A&rsquo;s new token survives</span>
104 </div>
105 </div>"""
106
107 lww_html = f"""<div class="crdt-vis">
108 <div class="crdt-writes">
109 <div class="crdt-write">
110 <span class="crdt-time">t=1.0</span>
111 <span class="crdt-agent crdt-agent-a">A</span>
112 <span class="crdt-wval">"{ra.read()}"</span>
113 </div>
114 <div class="crdt-write crdt-write-winner">
115 <span class="crdt-time">t=2.0</span>
116 <span class="crdt-agent crdt-agent-b">B</span>
117 <span class="crdt-wval">"{rb.read()}"</span>
118 <span class="crdt-latest">latest &uarr;</span>
119 </div>
120 </div>
121 <div class="crdt-join">
122 <span class="crdt-join-label">join(A,B) = join(B,A)</span>
123 <span class="crdt-join-val" style="color:#58a6ff">"{rm.read()}"</span>
124 <span class="crdt-join-rule">commutative &mdash; higher timestamp always wins</span>
125 </div>
126 </div>"""
127
128 a_val = ca.value_for("agent-A")
129 b_val = cb.value_for("agent-B")
130 total = cm.value()
131 a_pct = int(a_val / total * 100)
132 b_pct = int(b_val / total * 100)
133 gc_html = f"""<div class="crdt-vis">
134 <div class="crdt-gcounter">
135 <div class="crdt-gc-row">
136 <span class="crdt-agent crdt-agent-a">A &times;{a_val}</span>
137 <div class="crdt-bar"><div class="crdt-bar-fill crdt-bar-a" style="width:{a_pct}%">{a_val}</div></div>
138 </div>
139 <div class="crdt-gc-row">
140 <span class="crdt-agent crdt-agent-b">B &times;{b_val}</span>
141 <div class="crdt-bar"><div class="crdt-bar-fill crdt-bar-b" style="width:{b_pct}%">{b_val}</div></div>
142 </div>
143 </div>
144 <div class="crdt-join">
145 <span class="crdt-join-label">join(A, B) global</span>
146 <span class="crdt-join-val" style="color:#3fb950">{total}</span>
147 <span class="crdt-join-rule">component-wise max &mdash; monotonically non-decreasing</span>
148 </div>
149 </div>"""
150
151 concurrent = va.concurrent_with(vb)
152 merged_d = vm.to_dict()
153 vc_html = f"""<div class="crdt-vis">
154 <div class="crdt-vclocks">
155 <div class="crdt-vc">
156 <div class="crdt-vc-hdr">Agent A</div>
157 <div class="crdt-vc-cells">
158 <div class="crdt-vc-cell crdt-vc-self">A:1</div>
159 <div class="crdt-vc-cell crdt-vc-zero">B:0</div>
160 </div>
161 </div>
162 <div class="crdt-vc-sep">&oplus;</div>
163 <div class="crdt-vc">
164 <div class="crdt-vc-hdr">Agent B</div>
165 <div class="crdt-vc-cells">
166 <div class="crdt-vc-cell crdt-vc-zero">A:0</div>
167 <div class="crdt-vc-cell crdt-vc-self">B:1</div>
168 </div>
169 </div>
170 <div class="crdt-vc-sep">=</div>
171 <div class="crdt-vc">
172 <div class="crdt-vc-hdr">merge</div>
173 <div class="crdt-vc-cells">
174 {"".join(f'<div class="crdt-vc-cell crdt-vc-max">{k.split("-")[1].upper()}:{v}</div>' for k, v in sorted(merged_d.items()))}
175 </div>
176 </div>
177 </div>
178 <div class="crdt-concurrent-badge">concurrent_with(A, B) = {concurrent}</div>
179 <div class="crdt-join-rule" style="font-size:10.5px;color:var(--mute);font-style:italic">component-wise max &mdash; causal happens-before tracking</div>
180 </div>"""
181
182 return [
183 {"type": "ORSet", "sub": "Observed-Remove Set", "color": "#bc8cff", "icon": _ICONS["union"], "output": orset_out, "html_output": orset_html},
184 {"type": "LWWRegister", "sub": "Last-Write-Wins Register", "color": "#58a6ff", "icon": _ICONS["edit"], "output": lww_out, "html_output": lww_html},
185 {"type": "GCounter", "sub": "Grow-Only Distributed Counter", "color": "#3fb950", "icon": _ICONS["arrow-up"], "output": gc_out, "html_output": gc_html},
186 {"type": "VectorClock", "sub": "Causal Ordering", "color": "#f9a825", "icon": _ICONS["git-branch"], "output": vc_out, "html_output": vc_html},
187 ]
188 except Exception as exc:
189 print(f" ⚠ CRDT demo failed ({exc}); using static fallback")
190 return []
191
192
193 def _load_domains() -> list[dict]:
194 """Run `muse domains --json` and return parsed output."""
195 try:
196 result = subprocess.run(
197 [sys.executable, "-m", "muse", "domains", "--json"],
198 capture_output=True,
199 text=True,
200 cwd=str(_ROOT),
201 timeout=15,
202 )
203 if result.returncode == 0:
204 raw = result.stdout.strip()
205 data: list[dict] = json.loads(raw)
206 return data
207 except Exception:
208 pass
209
210 # Fallback: static reference data
211 return [
212 {
213 "domain": "music",
214 "active": "true",
215 "capabilities": ["Typed Deltas", "Domain Schema", "OT Merge"],
216 "schema": {
217 "schema_version": "1",
218 "merge_mode": "three_way",
219 "description": "MIDI and audio file versioning with note-level diff and semantic merge",
220 "dimensions": [
221 {"name": "melodic", "description": "Note pitches and durations over time"},
222 {"name": "harmonic", "description": "Chord progressions and key signatures"},
223 {"name": "dynamic", "description": "Velocity and expression curves"},
224 {"name": "structural", "description": "Track layout, time signatures, tempo map"},
225 ],
226 },
227 }
228 ]
229
230
231 # ---------------------------------------------------------------------------
232 # Scaffold template (shown in the "Build in 3 steps" section)
233 # ---------------------------------------------------------------------------
234
235 _TYPED_DELTA_EXAMPLE = """\
236 # muse show --json (any commit, any domain)
237 {
238 "commit_id": "b26f3c99",
239 "message": "Resolve: integrate shared-state (A+B reconciled)",
240 "operations": [
241 {
242 "op_type": "ReplaceOp",
243 "address": "shared-state.mid",
244 "before_hash": "a1b2c3d4",
245 "after_hash": "e5f6g7h8",
246 "dimensions": ["structural"]
247 },
248 {
249 "op_type": "InsertOp",
250 "address": "beta-a.mid",
251 "after_hash": "09ab1234",
252 "dimensions": ["rhythmic", "dynamic"]
253 }
254 ],
255 "summary": {
256 "inserted": 1,
257 "replaced": 1,
258 "deleted": 0
259 }
260 }"""
261
262
263 _SCAFFOLD_SNIPPET = """\
264 from __future__ import annotations
265 from muse.domain import (
266 MuseDomainPlugin, LiveState, StateSnapshot,
267 StateDelta, DriftReport, MergeResult, DomainSchema,
268 )
269
270 class GenomicsPlugin(MuseDomainPlugin):
271 \"\"\"Version control for genomic sequences.\"\"\"
272
273 def snapshot(self, live_state: LiveState) -> StateSnapshot:
274 # Serialize current genome state to a content-addressable blob
275 raise NotImplementedError
276
277 def diff(self, base: StateSnapshot,
278 target: StateSnapshot) -> StateDelta:
279 # Compute minimal delta between two snapshots
280 raise NotImplementedError
281
282 def merge(self, base: StateSnapshot,
283 left: StateSnapshot,
284 right: StateSnapshot) -> MergeResult:
285 # Three-way merge — surface conflicts per dimension
286 raise NotImplementedError
287
288 def drift(self, committed: StateSnapshot,
289 live: LiveState) -> DriftReport:
290 # Detect uncommitted changes in the working state
291 raise NotImplementedError
292
293 def apply(self, delta: StateDelta,
294 live_state: LiveState) -> LiveState:
295 # Reconstruct historical state from a delta
296 raise NotImplementedError
297
298 def schema(self) -> DomainSchema:
299 # Declare dimensions — drives diff algorithm selection
300 raise NotImplementedError
301 """
302
303 # ---------------------------------------------------------------------------
304 # SVG icon library — Lucide/Feather style, stroke="currentColor", no fixed size
305 # ---------------------------------------------------------------------------
306
307 def _icon(paths: str) -> str:
308 """Wrap SVG paths in a standard icon shell."""
309 return (
310 '<svg class="icon" viewBox="0 0 24 24" fill="none" '
311 'stroke="currentColor" stroke-width="1.75" '
312 'stroke-linecap="round" stroke-linejoin="round">'
313 + paths
314 + "</svg>"
315 )
316
317
318 _ICONS: dict[str, str] = {
319 # Domains
320 "music": _icon('<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>'),
321 "genomics": _icon('<path d="M2 15c6.667-6 13.333 0 20-6"/><path d="M2 9c6.667 6 13.333 0 20 6"/><line x1="5.5" y1="11" x2="5.5" y2="13"/><line x1="18.5" y1="11" x2="18.5" y2="13"/>'),
322 "cube": _icon('<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>'),
323 "trending": _icon('<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/>'),
324 "atom": _icon('<circle cx="12" cy="12" r="1"/><path d="M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5z"/><path d="M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5z"/>'),
325 "plus": _icon('<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/>'),
326 "activity": _icon('<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>'),
327 "pen-tool": _icon('<path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/>'),
328 # Distribution
329 "terminal": _icon('<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>'),
330 "package": _icon('<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"/><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>'),
331 "globe": _icon('<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>'),
332 # Engine capabilities
333 "code": _icon('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'),
334 "layers": _icon('<polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/>'),
335 "git-merge": _icon('<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/>'),
336 "zap": _icon('<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>'),
337 # MuseHub features
338 "search": _icon('<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>'),
339 "lock": _icon('<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>'),
340 # CRDT primitives
341 "union": _icon('<path d="M5 5v8a7 7 0 0 0 14 0V5"/><line x1="3" y1="19" x2="21" y2="19"/>'),
342 "edit": _icon('<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>'),
343 "arrow-up": _icon('<line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/>'),
344 "git-branch": _icon('<line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>'),
345 # OT scenario outcome badges
346 "check-circle":_icon('<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>'),
347 "x-circle": _icon('<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>'),
348 }
349
350
351 # ---------------------------------------------------------------------------
352 # Planned / aspirational domains
353 # ---------------------------------------------------------------------------
354
355 _PLANNED_DOMAINS = [
356 {
357 "name": "Genomics",
358 "icon": _ICONS["genomics"],
359 "status": "planned",
360 "tagline": "Version sequences, variants, and annotations",
361 "dimensions": ["sequence", "variants", "annotations", "metadata"],
362 "color": "#3fb950",
363 },
364 {
365 "name": "3D / Spatial",
366 "icon": _ICONS["cube"],
367 "status": "planned",
368 "tagline": "Merge spatial fields, meshes, and simulation frames",
369 "dimensions": ["geometry", "materials", "physics", "temporal"],
370 "color": "#58a6ff",
371 },
372 {
373 "name": "Financial",
374 "icon": _ICONS["trending"],
375 "status": "planned",
376 "tagline": "Track model versions, alpha signals, and risk state",
377 "dimensions": ["signals", "positions", "risk", "parameters"],
378 "color": "#f9a825",
379 },
380 {
381 "name": "Scientific Simulation",
382 "icon": _ICONS["atom"],
383 "status": "planned",
384 "tagline": "Snapshot simulation state across timesteps and parameter spaces",
385 "dimensions": ["state", "parameters", "observables", "checkpoints"],
386 "color": "#ab47bc",
387 },
388 {
389 "name": "Your Domain",
390 "icon": _ICONS["plus"],
391 "status": "yours",
392 "tagline": "Six methods. Any multidimensional state. Full VCS for free.",
393 "dimensions": ["your_dim_1", "your_dim_2", "..."],
394 "color": "#4f8ef7",
395 },
396 ]
397
398 # ---------------------------------------------------------------------------
399 # Distribution model description
400 # ---------------------------------------------------------------------------
401
402 _DISTRIBUTION_LEVELS = [
403 {
404 "tier": "Local",
405 "icon": _ICONS["terminal"],
406 "title": "Local plugin (right now)",
407 "color": "#3fb950",
408 "steps": [
409 "muse domains --new &lt;name&gt;",
410 "Implement 6 methods in muse/plugins/&lt;name&gt;/plugin.py",
411 "Register in muse/plugins/registry.py",
412 "muse init --domain &lt;name&gt;",
413 ],
414 "desc": "Works today. Scaffold → implement → register. "
415 "Your plugin lives alongside the core.",
416 },
417 {
418 "tier": "Shareable",
419 "icon": _ICONS["package"],
420 "title": "pip-installable package (right now)",
421 "color": "#58a6ff",
422 "steps": [
423 "Package your plugin as a Python module",
424 "pip install git+https://github.com/you/muse-plugin-genomics",
425 "Register the entry-point in pyproject.toml",
426 "muse init --domain genomics",
427 ],
428 "desc": "Share your plugin as a standard Python package. "
429 "Anyone with pip can install and use it.",
430 },
431 {
432 "tier": "MuseHub",
433 "icon": _ICONS["globe"],
434 "title": "Centralized registry (coming — MuseHub)",
435 "color": "#bc8cff",
436 "steps": [
437 "musehub publish muse-plugin-genomics",
438 "musehub search genomics",
439 "muse init --domain @musehub/genomics",
440 "Browse plugins at musehub.io",
441 ],
442 "desc": "MuseHub is a planned centralized registry — npm for Muse plugins. "
443 "Versioned, searchable, one-command install.",
444 },
445 ]
446
447
448 # ---------------------------------------------------------------------------
449 # HTML template
450 # ---------------------------------------------------------------------------
451
452 def _render_capability_card(cap: dict) -> str:
453 color = cap["color"]
454 body = (
455 cap["html_output"]
456 if "html_output" in cap
457 else f'<pre class="cap-showcase-output">{cap["output"]}</pre>'
458 )
459 return f"""
460 <div class="cap-showcase-card" style="--cap-color:{color}">
461 <div class="cap-showcase-header">
462 <span class="cap-showcase-badge" style="color:{color};background:{color}15;border-color:{color}40">
463 {cap['icon']} {cap['type']}
464 </span>
465 <span class="cap-showcase-sub">{cap['sub']}</span>
466 </div>
467 <div class="cap-showcase-body">
468 {body}
469 </div>
470 </div>"""
471
472
473 def _render_domain_card(d: dict) -> str:
474 domain = d.get("domain", "unknown")
475 active = d.get("active") == "true"
476 schema = d.get("schema", {})
477 desc = schema.get("description", "")
478 dims = schema.get("dimensions", [])
479 caps = d.get("capabilities", [])
480
481 cap_html = " ".join(
482 f'<span class="cap-pill cap-{c.lower().replace(" ","-")}">{c}</span>'
483 for c in caps
484 )
485 dim_html = " · ".join(
486 f'<span class="dim-tag">{dim["name"]}</span>' for dim in dims
487 )
488
489 status_cls = "active-badge" if active else "reg-badge"
490 status_text = "● active" if active else "○ registered"
491 dot = '<span class="active-dot"></span>' if active else ""
492
493 short_desc = desc[:150] + ("…" if len(desc) > 150 else "")
494
495 return f"""
496 <div class="domain-card{' active-domain' if active else ''}">
497 <div class="domain-card-hdr">
498 <span class="{status_cls}">{status_text}</span>
499 <span class="domain-name-lg">{domain}</span>
500 {dot}
501 </div>
502 <div class="domain-card-body">
503 <p class="domain-desc">{short_desc}</p>
504 <div class="cap-row">{cap_html}</div>
505 <div class="dim-row"><strong>Dimensions:</strong> {dim_html}</div>
506 </div>
507 </div>"""
508
509
510 def _render_planned_card(p: dict) -> str:
511 dims = " · ".join(f'<span class="dim-tag">{d}</span>' for d in p["dimensions"])
512 cls = "planned-card yours" if p["status"] == "yours" else "planned-card"
513 return f"""
514 <div class="{cls}" style="--card-accent:{p['color']}">
515 <div class="planned-icon">{p['icon']}</div>
516 <div class="planned-name">{p['name']}</div>
517 <div class="planned-tag">{p['tagline']}</div>
518 <div class="planned-dims">{dims}</div>
519 {'<a class="cta-btn" href="#build">Build it →</a>' if p["status"] == "yours" else '<span class="coming-soon">coming soon</span>'}
520 </div>"""
521
522
523 def _render_dist_card(d: dict) -> str:
524 steps = "".join(
525 f'<li><code>{s}</code></li>' for s in d["steps"]
526 )
527 return f"""
528 <div class="dist-card" style="--dist-color:{d['color']}">
529 <div class="dist-header">
530 <span class="dist-icon">{d['icon']}</span>
531 <div>
532 <div class="dist-tier">{d['tier']}</div>
533 <div class="dist-title">{d['title']}</div>
534 </div>
535 </div>
536 <p class="dist-desc">{d['desc']}</p>
537 <ol class="dist-steps">{steps}</ol>
538 </div>"""
539
540
541 def render(output_path: pathlib.Path) -> None:
542 """Generate the domain registry HTML page."""
543 print(" Loading live domain data...")
544 domains = _load_domains()
545 print(f" Found {len(domains)} registered domain(s)")
546
547 print(" Computing live CRDT demos...")
548 crdt_demos = _compute_crdt_demos()
549
550 active_domains_html = "\n".join(_render_domain_card(d) for d in domains)
551 planned_html = "\n".join(_render_planned_card(p) for p in _PLANNED_DOMAINS)
552 dist_html = "\n".join(_render_dist_card(d) for d in _DISTRIBUTION_LEVELS)
553 crdt_cards_html = "\n".join(_render_capability_card(c) for c in crdt_demos)
554
555 html = _HTML_TEMPLATE.replace("{{ACTIVE_DOMAINS}}", active_domains_html)
556 html = html.replace("{{PLANNED_DOMAINS}}", planned_html)
557 html = html.replace("{{DIST_CARDS}}", dist_html)
558 html = html.replace("{{SCAFFOLD_SNIPPET}}", _SCAFFOLD_SNIPPET)
559 html = html.replace("{{TYPED_DELTA_EXAMPLE}}", _TYPED_DELTA_EXAMPLE)
560 html = html.replace("{{CRDT_CARDS}}", crdt_cards_html)
561 html = html.replace("{{DIFF_ALGEBRA}}", _DIFF_ALGEBRA_HTML)
562
563 # Inject SVG icons into template placeholders
564 _ICON_SLOTS: dict[str, str] = {
565 "MUSIC": _ICONS["music"],
566 "GENOMICS": _ICONS["genomics"],
567 "CUBE": _ICONS["cube"],
568 "TRENDING": _ICONS["trending"],
569 "ATOM": _ICONS["atom"],
570 "PLUS": _ICONS["plus"],
571 "ACTIVITY": _ICONS["activity"],
572 "PEN_TOOL": _ICONS["pen-tool"],
573 "CODE": _ICONS["code"],
574 "LAYERS": _ICONS["layers"],
575 "GIT_MERGE": _ICONS["git-merge"],
576 "ZAP": _ICONS["zap"],
577 "GLOBE": _ICONS["globe"],
578 "SEARCH": _ICONS["search"],
579 "PACKAGE": _ICONS["package"],
580 "LOCK": _ICONS["lock"],
581 "CHECK_CIRCLE": _ICONS["check-circle"],
582 "X_CIRCLE": _ICONS["x-circle"],
583 }
584 for slot, svg in _ICON_SLOTS.items():
585 html = html.replace(f"{{{{ICON_{slot}}}}}", svg)
586
587 output_path.write_text(html, encoding="utf-8")
588 size_kb = output_path.stat().st_size // 1024
589 print(f" HTML written ({size_kb}KB) → {output_path}")
590
591 # Also write as index.html so the domain registry IS the landing page.
592 index_path = output_path.parent / "index.html"
593 index_path.write_text(html, encoding="utf-8")
594 print(f" Landing page mirrored → {index_path}")
595
596
597 # ---------------------------------------------------------------------------
598 # Diff Algebra section — five algorithm visualizations + StructuredDelta flow
599 # ---------------------------------------------------------------------------
600
601 _DIFF_ALGEBRA_HTML = """
602 <section id="diff-algebra" style="background:var(--bg)">
603 <div class="inner">
604 <div class="section-eyebrow">Diff Algebra</div>
605 <h2>Five Algebras. One Typed Result.</h2>
606 <p class="section-lead">
607 The engine selects the algorithm per dimension from your plugin&rsquo;s
608 <code>schema()</code>. You declare the shape &mdash; the engine handles identity,
609 diffing, and merge selection automatically.
610 </p>
611
612 <div class="da-grid">
613
614 <!-- 1. SEQUENCE — spans full width -->
615 <div class="da-card da-seq-card">
616 <div class="da-card-hdr">
617 <span class="da-chip da-chip-seq">Sequence</span>
618 <span class="da-algo-name">Myers / LCS on SHA-256 IDs</span>
619 </div>
620 <div class="da-domains-row">notes &middot; nucleotides &middot; animation frames &middot; git objects</div>
621 <div class="da-visual" style="flex-direction:column;align-items:flex-start;gap:8px">
622 <div class="seq-vis">
623 <div class="seq-row-lbl">before</div>
624 <div class="seq-row">
625 <div class="seq-blk seq-match"><div class="seq-hash">a1b2</div><div class="seq-name">C4</div></div>
626 <div class="seq-blk seq-del"><div class="seq-hash">c3d4</div><div class="seq-name">E4</div></div>
627 <div class="seq-blk seq-match"><div class="seq-hash">e5f6</div><div class="seq-name">G4</div></div>
628 <div class="seq-blk seq-moved-from"><div class="seq-hash">g7h8</div><div class="seq-name">B&flat;4</div></div>
629 </div>
630 <div class="seq-ops-row">
631 <div class="seq-op-cell"><span class="da-op da-op-match">= match</span></div>
632 <div class="seq-op-cell"><span class="da-op da-op-delete">&times; DeleteOp</span></div>
633 <div class="seq-op-cell"><span class="da-op da-op-match">= match</span></div>
634 <div class="seq-op-cell"><span class="da-op da-op-move">&darr; MoveOp</span></div>
635 </div>
636 <div class="seq-row">
637 <div class="seq-blk seq-match"><div class="seq-hash">a1b2</div><div class="seq-name">C4</div></div>
638 <div class="seq-blk seq-ins"><div class="seq-hash">k1l2</div><div class="seq-name">F4</div></div>
639 <div class="seq-blk seq-match"><div class="seq-hash">e5f6</div><div class="seq-name">G4</div></div>
640 <div class="seq-blk seq-ins"><div class="seq-hash">n5o6</div><div class="seq-name">A4</div></div>
641 <div class="seq-blk seq-moved-to"><div class="seq-hash">g7h8</div><div class="seq-name">B&flat;4</div></div>
642 </div>
643 <div class="seq-ops-row">
644 <div class="seq-op-cell"></div>
645 <div class="seq-op-cell"><span class="da-op da-op-insert">+ InsertOp</span></div>
646 <div class="seq-op-cell"></div>
647 <div class="seq-op-cell"><span class="da-op da-op-insert">+ InsertOp</span></div>
648 <div class="seq-op-cell"><span class="da-op da-op-move">&uarr; arrived</span></div>
649 </div>
650 <div class="seq-row-lbl">after</div>
651 </div>
652 </div>
653 <div class="da-note">Identity is hash-based: two elements are equal iff their SHA-256 hashes match &mdash; content is never inspected by the core. Delete&thinsp;+&thinsp;insert pairs sharing the same hash are collapsed into MoveOps in a post-pass.</div>
654 </div>
655
656 <!-- 2. TREE -->
657 <div class="da-card">
658 <div class="da-card-hdr">
659 <span class="da-chip da-chip-tree">Tree</span>
660 <span class="da-algo-name">Zhang-Shasha / GumTree</span>
661 </div>
662 <div class="da-domains-row">scene graphs &middot; ASTs &middot; track hierarchies</div>
663 <div class="da-visual" style="padding:10px 8px">
664 <svg class="tree-vis" viewBox="0 0 290 128" xmlns="http://www.w3.org/2000/svg">
665 <!-- BEFORE -->
666 <text x="52" y="9" text-anchor="middle" font-size="7" fill="#484f58" font-family="monospace" font-weight="700">BEFORE</text>
667 <line x1="52" y1="27" x2="24" y2="57" stroke="#30363d" stroke-width="1.5"/>
668 <line x1="52" y1="27" x2="80" y2="57" stroke="#30363d" stroke-width="1.5"/>
669 <line x1="24" y1="73" x2="11" y2="100" stroke="#30363d" stroke-width="1.5"/>
670 <line x1="24" y1="73" x2="37" y2="100" stroke="#30363d" stroke-width="1.5"/>
671 <line x1="80" y1="73" x2="80" y2="100" stroke="#bc8cff" stroke-width="1.5" stroke-dasharray="3,2"/>
672 <rect x="31" y="14" width="42" height="16" rx="4" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
673 <text x="52" y="25" text-anchor="middle" font-size="8" fill="#58a6ff" font-family="monospace">session</text>
674 <rect x="7" y="59" width="34" height="16" rx="3" fill="#161b22" stroke="#484f58"/>
675 <text x="24" y="70" text-anchor="middle" font-size="8" fill="#8b949e" font-family="monospace">intro</text>
676 <rect x="63" y="59" width="34" height="16" rx="3" fill="#161b22" stroke="#484f58"/>
677 <text x="80" y="70" text-anchor="middle" font-size="8" fill="#8b949e" font-family="monospace">verse</text>
678 <rect x="4" y="98" width="15" height="13" rx="3" fill="#161b22" stroke="#484f58"/>
679 <text x="11" y="108" text-anchor="middle" font-size="7.5" fill="#8b949e" font-family="monospace">C4</text>
680 <rect x="28" y="98" width="15" height="13" rx="3" fill="#161b22" stroke="#484f58"/>
681 <text x="35" y="108" text-anchor="middle" font-size="7.5" fill="#8b949e" font-family="monospace">E4</text>
682 <rect x="72" y="98" width="15" height="13" rx="3" fill="rgba(188,140,255,0.1)" stroke="#bc8cff" stroke-width="1.5"/>
683 <text x="79" y="108" text-anchor="middle" font-size="7.5" fill="#bc8cff" font-family="monospace">G4</text>
684 <!-- divider -->
685 <line x1="143" y1="8" x2="143" y2="118" stroke="#30363d" stroke-width="1" stroke-dasharray="3,3"/>
686 <text x="143" y="126" text-anchor="middle" font-size="6.5" fill="#484f58" font-family="monospace">MoveOp + InsertOp</text>
687 <!-- AFTER -->
688 <text x="216" y="9" text-anchor="middle" font-size="7" fill="#484f58" font-family="monospace" font-weight="700">AFTER</text>
689 <line x1="216" y1="27" x2="183" y2="57" stroke="#30363d" stroke-width="1.5"/>
690 <line x1="216" y1="27" x2="249" y2="57" stroke="#30363d" stroke-width="1.5"/>
691 <line x1="183" y1="73" x2="162" y2="100" stroke="#30363d" stroke-width="1.5"/>
692 <line x1="183" y1="73" x2="178" y2="100" stroke="#30363d" stroke-width="1.5"/>
693 <line x1="183" y1="73" x2="195" y2="100" stroke="#bc8cff" stroke-width="1.5" stroke-dasharray="3,2"/>
694 <line x1="249" y1="73" x2="249" y2="100" stroke="#3fb950" stroke-width="1.5"/>
695 <rect x="195" y="14" width="42" height="16" rx="4" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
696 <text x="216" y="25" text-anchor="middle" font-size="8" fill="#58a6ff" font-family="monospace">session</text>
697 <rect x="166" y="59" width="34" height="16" rx="3" fill="#161b22" stroke="#484f58"/>
698 <text x="183" y="70" text-anchor="middle" font-size="8" fill="#8b949e" font-family="monospace">intro</text>
699 <rect x="232" y="59" width="34" height="16" rx="3" fill="#161b22" stroke="#484f58"/>
700 <text x="249" y="70" text-anchor="middle" font-size="8" fill="#8b949e" font-family="monospace">verse</text>
701 <rect x="155" y="98" width="15" height="13" rx="3" fill="#161b22" stroke="#484f58"/>
702 <text x="162" y="108" text-anchor="middle" font-size="7.5" fill="#8b949e" font-family="monospace">C4</text>
703 <rect x="171" y="98" width="15" height="13" rx="3" fill="#161b22" stroke="#484f58"/>
704 <text x="178" y="108" text-anchor="middle" font-size="7.5" fill="#8b949e" font-family="monospace">E4</text>
705 <rect x="188" y="98" width="15" height="13" rx="3" fill="rgba(188,140,255,0.12)" stroke="#bc8cff" stroke-width="1.5"/>
706 <text x="195" y="108" text-anchor="middle" font-size="7.5" fill="#bc8cff" font-family="monospace">G4</text>
707 <rect x="241" y="98" width="15" height="13" rx="3" fill="rgba(63,185,80,0.12)" stroke="#3fb950" stroke-width="1.5"/>
708 <text x="249" y="108" text-anchor="middle" font-size="7.5" fill="#3fb950" font-family="monospace">A4</text>
709 <rect x="156" y="116" width="8" height="8" rx="1" fill="rgba(188,140,255,0.1)" stroke="#bc8cff"/>
710 <text x="166" y="122" font-size="6" fill="#bc8cff" font-family="monospace">moved</text>
711 <rect x="204" y="116" width="8" height="8" rx="1" fill="rgba(63,185,80,0.1)" stroke="#3fb950"/>
712 <text x="214" y="122" font-size="6" fill="#3fb950" font-family="monospace">inserted</text>
713 </svg>
714 </div>
715 <div class="da-note">Edit distance over node hierarchy &mdash; moves preserve subtree identity across parent changes.</div>
716 </div>
717
718 <!-- 3. TENSOR -->
719 <div class="da-card">
720 <div class="da-card-hdr">
721 <span class="da-chip da-chip-tensor">Tensor</span>
722 <span class="da-algo-name">Sparse / block numerical diff</span>
723 </div>
724 <div class="da-domains-row">sim state &middot; voxel grids &middot; weight matrices</div>
725 <div class="da-visual" style="flex-direction:column;gap:12px">
726 <div class="tensor-wrap">
727 <div class="tensor-panel">
728 <div class="tensor-label">t&thinsp;=&thinsp;0</div>
729 <div class="tensor-grid">
730 <div class="tc tc-2"></div><div class="tc tc-0"></div><div class="tc tc-1"></div><div class="tc tc-0"></div>
731 <div class="tc tc-0"></div><div class="tc tc-3"></div><div class="tc tc-0"></div><div class="tc tc-1"></div>
732 <div class="tc tc-1"></div><div class="tc tc-0"></div><div class="tc tc-2"></div><div class="tc tc-0"></div>
733 <div class="tc tc-0"></div><div class="tc tc-1"></div><div class="tc tc-0"></div><div class="tc tc-3"></div>
734 </div>
735 </div>
736 <div class="tensor-arrow">&rarr;</div>
737 <div class="tensor-panel">
738 <div class="tensor-label">t&thinsp;=&thinsp;1&thinsp;(&Delta;)</div>
739 <div class="tensor-grid">
740 <div class="tc tc-2"></div><div class="tc tc-0"></div><div class="tc tc-1"></div><div class="tc tc-0"></div>
741 <div class="tc tc-0"></div><div class="tc tc-hot3"></div><div class="tc tc-0"></div><div class="tc tc-warm1"></div>
742 <div class="tc tc-1"></div><div class="tc tc-0"></div><div class="tc tc-hot2"></div><div class="tc tc-0"></div>
743 <div class="tc tc-0"></div><div class="tc tc-1"></div><div class="tc tc-0"></div><div class="tc tc-3"></div>
744 </div>
745 </div>
746 </div>
747 <div class="tensor-legend">
748 <span class="tl-item"><span class="tl-swatch tl-unchanged"></span>|&Delta;| = 0</span>
749 <span class="tl-item"><span class="tl-swatch tl-warm"></span>|&Delta;| &le; &epsilon; (within threshold)</span>
750 <span class="tl-item"><span class="tl-swatch tl-hot"></span>|&Delta;| &gt; &epsilon; &rarr; PatchOp</span>
751 </div>
752 </div>
753 <div class="da-note">Configurable &epsilon; threshold. Sparse mode records only changed blocks &mdash; efficient for large tensors.</div>
754 </div>
755
756 <!-- 4. SET -->
757 <div class="da-card">
758 <div class="da-card-hdr">
759 <span class="da-chip da-chip-set">Set</span>
760 <span class="da-algo-name">Set algebra &middot; add / remove</span>
761 </div>
762 <div class="da-domains-row">annotations &middot; tags &middot; gene ontology terms</div>
763 <div class="da-visual">
764 <div class="set-vis">
765 <div class="set-col">
766 <div class="set-header">before</div>
767 <div class="set-members">
768 <span class="set-member set-kept">GO:0001234</span>
769 <span class="set-member set-kept">GO:0005634</span>
770 <span class="set-member set-removed">GO:0006915</span>
771 <span class="set-member set-kept">GO:0016020</span>
772 </div>
773 </div>
774 <div class="set-ops-col">
775 <div class="set-op-line set-op-keep">&mdash;</div>
776 <div class="set-op-line set-op-keep">&mdash;</div>
777 <div class="set-op-line set-op-rm">&times; del</div>
778 <div class="set-op-line set-op-keep">&mdash;</div>
779 <div class="set-op-line set-op-add">+ ins</div>
780 </div>
781 <div class="set-col">
782 <div class="set-header">after</div>
783 <div class="set-members">
784 <span class="set-member set-kept">GO:0001234</span>
785 <span class="set-member set-kept">GO:0005634</span>
786 <span class="set-member set-kept">GO:0016020</span>
787 <span class="set-member set-added">GO:0042592</span>
788 </div>
789 </div>
790 </div>
791 </div>
792 <div class="da-note">Unordered &mdash; no position tracking. Pure membership delta: {removed} and {added}.</div>
793 </div>
794
795 <!-- 5. MAP -->
796 <div class="da-card">
797 <div class="da-card-hdr">
798 <span class="da-chip da-chip-map">Map</span>
799 <span class="da-algo-name">Recursive key-by-key delegation</span>
800 </div>
801 <div class="da-domains-row">metadata &middot; configs &middot; nested structures</div>
802 <div class="da-visual" style="align-items:flex-start">
803 <div class="map-vis">
804 <div class="map-entry map-entry-changed">
805 <span class="map-key">tempo</span>
806 <span class="map-val-before">120</span><span class="map-delta">&nbsp;&rarr;&nbsp;</span><span class="map-val-after">140</span>
807 <span class="map-sub-algo da-chip-tensor">scalar &rarr; PatchOp</span>
808 </div>
809 <div class="map-entry map-entry-changed">
810 <span class="map-key">notes</span>
811 <span class="map-val-before">[&hellip;]</span><span class="map-delta">&nbsp;&rarr;&nbsp;</span><span class="map-val-after">[&hellip;&prime;]</span>
812 <span class="map-sub-algo da-chip-seq">sequence &rarr; LCS</span>
813 </div>
814 <div class="map-entry map-entry-changed">
815 <span class="map-key">tags</span>
816 <span class="map-val-before">{&hellip;}</span><span class="map-delta">&nbsp;&rarr;&nbsp;</span><span class="map-val-after">{&hellip;&prime;}</span>
817 <span class="map-sub-algo da-chip-set">set &rarr; algebra</span>
818 </div>
819 <div class="map-entry map-entry-unchanged">
820 <span class="map-key">author</span>
821 <span class="map-val-before">"Bach"</span><span class="map-delta">&nbsp;=&nbsp;</span><span class="map-val-after">"Bach"</span>
822 <span style="margin-left:auto;font-size:9px;color:var(--dim);font-family:var(--mono)">unchanged</span>
823 </div>
824 </div>
825 </div>
826 <div class="da-note">Each key is diffed by whichever algorithm matches its declared type &mdash; recursively, to arbitrary depth.</div>
827 </div>
828
829 </div><!-- .da-grid -->
830
831 <!-- StructuredDelta taxonomy -->
832 <div class="da-delta-flow">
833 <div class="da-delta-top">
834 <span class="da-delta-label">diff() &rarr; StructuredDelta</span>
835 <span class="da-delta-sub">all five algorithms produce the same typed operation list</span>
836 </div>
837 <div class="da-delta-ops">
838 <span class="da-dop da-dop-ins">InsertOp</span>
839 <span class="da-dop da-dop-del">DeleteOp</span>
840 <span class="da-dop da-dop-mov">MoveOp</span>
841 <span class="da-dop da-dop-rep">ReplaceOp</span>
842 <span class="da-dop da-dop-pat">PatchOp</span>
843 </div>
844 <div class="da-delta-merge">
845 <div class="da-merge-branch da-merge-ot">
846 <div class="da-merge-mode-label">merge_mode: &ldquo;three_way&rdquo;</div>
847 <div class="da-merge-desc">Operational Transformation &mdash; independent ops commute automatically; conflicting ops surface for human resolution</div>
848 </div>
849 <div class="da-merge-divider">or</div>
850 <div class="da-merge-branch da-merge-crdt">
851 <div class="da-merge-mode-label">merge_mode: &ldquo;crdt&rdquo;</div>
852 <div class="da-merge-desc">CRDT join() &mdash; convergent, no coordination required; any two replicas always reach the same state</div>
853 </div>
854 </div>
855 </div>
856
857 </div>
858 </section>
859 """
860
861 # ---------------------------------------------------------------------------
862 # Large HTML template
863 # ---------------------------------------------------------------------------
864
865 _HTML_TEMPLATE = """\
866 <!DOCTYPE html>
867 <html lang="en">
868 <head>
869 <meta charset="utf-8">
870 <meta name="viewport" content="width=device-width, initial-scale=1">
871 <title>Muse — Version Anything</title>
872 <style>
873 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
874 :root {
875 --bg: #0d1117;
876 --bg2: #161b22;
877 --bg3: #21262d;
878 --border: #30363d;
879 --text: #e6edf3;
880 --mute: #8b949e;
881 --dim: #484f58;
882 --accent: #4f8ef7;
883 --accent2: #58a6ff;
884 --green: #3fb950;
885 --red: #f85149;
886 --yellow: #d29922;
887 --purple: #bc8cff;
888 --mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
889 --ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
890 --r: 8px;
891 }
892 html { scroll-behavior: smooth; }
893 body {
894 background: var(--bg);
895 color: var(--text);
896 font-family: var(--ui);
897 font-size: 15px;
898 line-height: 1.7;
899 }
900 a { color: var(--accent2); text-decoration: none; }
901 a:hover { text-decoration: underline; }
902 code {
903 font-family: var(--mono);
904 font-size: 0.88em;
905 background: var(--bg3);
906 border: 1px solid var(--border);
907 border-radius: 4px;
908 padding: 1px 6px;
909 }
910
911 /* ---- Hero ---- */
912 .hero {
913 background: linear-gradient(160deg, #0d1117 0%, #161b22 50%, #0d1117 100%);
914 border-bottom: 1px solid var(--border);
915 padding: 80px 40px 100px;
916 text-align: center;
917 position: relative;
918 overflow: hidden;
919 }
920 .hero::before {
921 content: '';
922 position: absolute;
923 inset: 0;
924 background:
925 radial-gradient(ellipse 60% 40% at 20% 50%, rgba(79,142,247,0.07) 0%, transparent 70%),
926 radial-gradient(ellipse 50% 40% at 80% 50%, rgba(188,140,255,0.06) 0%, transparent 70%);
927 pointer-events: none;
928 }
929 .hero-wordmark {
930 font-family: var(--ui);
931 font-size: clamp(72px, 11vw, 130px);
932 font-weight: 800;
933 letter-spacing: -5px;
934 line-height: 1;
935 margin-bottom: 12px;
936 background: linear-gradient(90deg, #6ea8fe 0%, #a78bfa 50%, #c084fc 100%);
937 -webkit-background-clip: text;
938 -webkit-text-fill-color: transparent;
939 background-clip: text;
940 }
941 .hero-version-any {
942 font-size: clamp(18px, 2.8vw, 26px);
943 font-weight: 700;
944 color: #ffffff;
945 letter-spacing: 6px;
946 text-transform: uppercase;
947 margin-bottom: 32px;
948 }
949 .hero-sub {
950 font-size: 18px;
951 color: var(--mute);
952 max-width: 600px;
953 margin: 0 auto 40px;
954 line-height: 1.6;
955 }
956 .hero-sub strong { color: var(--text); }
957 .hero-cta-row {
958 display: flex;
959 gap: 12px;
960 justify-content: center;
961 flex-wrap: wrap;
962 }
963 .btn-primary {
964 background: var(--accent);
965 color: #fff;
966 font-weight: 600;
967 padding: 12px 28px;
968 border-radius: var(--r);
969 font-size: 15px;
970 border: none;
971 cursor: pointer;
972 text-decoration: none;
973 transition: opacity 0.15s, transform 0.1s;
974 display: inline-block;
975 }
976 .btn-primary:hover { opacity: 0.88; transform: translateY(-1px); text-decoration: none; }
977 .btn-outline {
978 background: transparent;
979 color: var(--text);
980 font-weight: 500;
981 padding: 12px 28px;
982 border-radius: var(--r);
983 font-size: 15px;
984 border: 1px solid var(--border);
985 cursor: pointer;
986 text-decoration: none;
987 display: inline-block;
988 transition: border-color 0.15s, color 0.15s;
989 }
990 .btn-outline:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
991
992 /* ---- Domain ticker ---- */
993 .domain-ticker {
994 margin: 32px auto 0;
995 max-width: 700px;
996 overflow: hidden;
997 position: relative;
998 height: 34px;
999 }
1000 .domain-ticker::before,
1001 .domain-ticker::after {
1002 content: '';
1003 position: absolute;
1004 top: 0; bottom: 0;
1005 width: 60px;
1006 z-index: 2;
1007 }
1008 .domain-ticker::before { left: 0; background: linear-gradient(90deg, var(--bg), transparent); }
1009 .domain-ticker::after { right: 0; background: linear-gradient(-90deg, var(--bg), transparent); }
1010 .ticker-track {
1011 display: flex;
1012 gap: 10px;
1013 animation: ticker-scroll 18s linear infinite;
1014 width: max-content;
1015 }
1016 @keyframes ticker-scroll {
1017 0% { transform: translateX(0); }
1018 100% { transform: translateX(-50%); }
1019 }
1020 .ticker-item {
1021 font-family: var(--mono);
1022 font-size: 13px;
1023 padding: 4px 14px;
1024 border-radius: 20px;
1025 border: 1px solid var(--border);
1026 white-space: nowrap;
1027 color: var(--mute);
1028 }
1029 .ticker-item.active { border-color: rgba(79,142,247,0.5); color: var(--accent2); background: rgba(79,142,247,0.08); }
1030
1031 /* ---- Sections ---- */
1032 section { padding: 72px 40px; border-top: 1px solid var(--border); }
1033 .inner { max-width: 1100px; margin: 0 auto; }
1034 .section-eyebrow {
1035 font-family: var(--mono);
1036 font-size: 11px;
1037 color: var(--accent2);
1038 letter-spacing: 2px;
1039 text-transform: uppercase;
1040 margin-bottom: 10px;
1041 }
1042 section h2 {
1043 font-size: 32px;
1044 font-weight: 700;
1045 letter-spacing: -0.5px;
1046 margin-bottom: 12px;
1047 }
1048 .section-lead {
1049 font-size: 16px;
1050 color: var(--mute);
1051 max-width: 620px;
1052 margin-bottom: 48px;
1053 line-height: 1.7;
1054 }
1055 .section-lead strong { color: var(--text); }
1056
1057 /* ---- Base icon ---- */
1058 .icon {
1059 display: inline-block;
1060 vertical-align: -0.15em;
1061 flex-shrink: 0;
1062 }
1063 .ticker-item .icon { width: 13px; height: 13px; vertical-align: -0.1em; }
1064 .cap-showcase-badge .icon { width: 13px; height: 13px; vertical-align: -0.1em; }
1065
1066 /* ---- Protocol two-col layout ---- */
1067 .proto-layout {
1068 display: grid;
1069 grid-template-columns: 148px 1fr;
1070 gap: 0;
1071 border: 1px solid var(--border);
1072 border-radius: var(--r);
1073 overflow: hidden;
1074 margin-bottom: 40px;
1075 align-items: stretch;
1076 }
1077 @media (max-width: 640px) {
1078 .proto-layout { grid-template-columns: 1fr; }
1079 .stat-strip { border-right: none; border-bottom: 1px solid var(--border); }
1080 }
1081
1082 /* ---- Stat strip (left column) ---- */
1083 .stat-strip {
1084 display: flex;
1085 flex-direction: column;
1086 border-right: 1px solid var(--border);
1087 }
1088 .stat-cell {
1089 flex: 1;
1090 padding: 18px 20px;
1091 border-bottom: 1px solid var(--border);
1092 text-align: center;
1093 display: flex;
1094 flex-direction: column;
1095 align-items: center;
1096 justify-content: center;
1097 }
1098 .stat-cell:last-child { border-bottom: none; }
1099 .stat-num {
1100 font-family: var(--mono);
1101 font-size: 26px;
1102 font-weight: 700;
1103 color: var(--accent2);
1104 display: block;
1105 line-height: 1.1;
1106 }
1107 .stat-lbl { font-size: 11px; color: var(--mute); margin-top: 4px; line-height: 1.3; }
1108
1109 /* ---- Protocol table (right column) ---- */
1110 .proto-table {
1111 overflow: hidden;
1112 }
1113 .proto-row {
1114 display: grid;
1115 grid-template-columns: 90px 240px 1fr;
1116 border-bottom: 1px solid var(--border);
1117 }
1118 .proto-row:last-child { border-bottom: none; }
1119 .proto-row.hdr { background: var(--bg3); }
1120 .proto-row > div { padding: 11px 16px; }
1121 .proto-method { font-family: var(--mono); font-size: 13px; color: var(--accent2); font-weight: 600; }
1122 .proto-sig { font-family: var(--mono); font-size: 12px; color: var(--mute); }
1123 .proto-desc { font-size: 13px; color: var(--mute); }
1124 .proto-row.hdr .proto-method,
1125 .proto-row.hdr .proto-sig,
1126 .proto-row.hdr .proto-desc { font-family: var(--ui); font-size: 11px; font-weight: 600; color: var(--dim); text-transform: uppercase; letter-spacing: 0.6px; }
1127
1128 /* ---- Engine capability showcase ---- */
1129 .cap-showcase-grid {
1130 display: grid;
1131 grid-template-columns: repeat(auto-fill, minmax(480px, 1fr));
1132 gap: 24px;
1133 }
1134 @media (max-width: 600px) { .cap-showcase-grid { grid-template-columns: 1fr; } }
1135 .cap-showcase-card {
1136 border: 1px solid var(--border);
1137 border-top: 3px solid var(--cap-color, var(--accent));
1138 border-radius: var(--r);
1139 background: var(--bg);
1140 overflow: hidden;
1141 transition: transform 0.15s;
1142 }
1143 .cap-showcase-card:hover { transform: translateY(-2px); }
1144 .cap-showcase-header {
1145 padding: 14px 18px;
1146 border-bottom: 1px solid var(--border);
1147 background: var(--bg2);
1148 display: flex;
1149 align-items: center;
1150 gap: 12px;
1151 flex-wrap: wrap;
1152 }
1153 .cap-showcase-badge {
1154 font-size: 12px;
1155 font-family: var(--mono);
1156 padding: 3px 10px;
1157 border-radius: 4px;
1158 border: 1px solid;
1159 white-space: nowrap;
1160 }
1161 .cap-showcase-sub {
1162 font-size: 12px;
1163 color: var(--mute);
1164 font-style: italic;
1165 }
1166 .cap-showcase-body { padding: 16px 18px; }
1167 .cap-showcase-desc {
1168 font-size: 13px;
1169 color: var(--mute);
1170 margin-bottom: 14px;
1171 line-height: 1.6;
1172 }
1173 .cap-showcase-desc strong { color: var(--text); }
1174 .cap-showcase-output {
1175 background: #0a0e14;
1176 border: 1px solid var(--border);
1177 border-radius: 5px;
1178 padding: 12px 14px;
1179 font-family: var(--mono);
1180 font-size: 11.5px;
1181 color: #abb2bf;
1182 white-space: pre;
1183 overflow-x: auto;
1184 line-height: 1.65;
1185 }
1186 /* ---- Diff Algebra section ---- */
1187 .da-grid {
1188 display: grid;
1189 grid-template-columns: repeat(2, 1fr);
1190 gap: 20px;
1191 margin-bottom: 36px;
1192 }
1193 .da-seq-card { grid-column: span 2; }
1194 @media (max-width: 720px) {
1195 .da-grid { grid-template-columns: 1fr; }
1196 .da-seq-card { grid-column: span 1; }
1197 }
1198 .da-card {
1199 background: var(--bg2);
1200 border: 1px solid var(--border);
1201 border-radius: var(--r);
1202 padding: 18px;
1203 display: flex;
1204 flex-direction: column;
1205 gap: 10px;
1206 }
1207 .da-card-hdr { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
1208 .da-chip {
1209 font-family: var(--mono);
1210 font-size: 10.5px;
1211 font-weight: 700;
1212 padding: 3px 10px;
1213 border-radius: 4px;
1214 border: 1px solid;
1215 }
1216 .da-chip-seq { color:#58a6ff; background:rgba(88,166,255,0.1); border-color:rgba(88,166,255,0.3); }
1217 .da-chip-tree { color:#f9a825; background:rgba(249,168,37,0.1); border-color:rgba(249,168,37,0.3); }
1218 .da-chip-tensor { color:#ab47bc; background:rgba(171,71,188,0.1); border-color:rgba(171,71,188,0.3); }
1219 .da-chip-set { color:#3fb950; background:rgba(63,185,80,0.1); border-color:rgba(63,185,80,0.3); }
1220 .da-chip-map { color:#ef5350; background:rgba(239,83,80,0.1); border-color:rgba(239,83,80,0.3); }
1221 .da-algo-name { font-size:12px; color:var(--mute); font-family:var(--mono); }
1222 .da-domains-row { font-size:11px; color:var(--dim); }
1223 .da-visual {
1224 background: #0a0e14;
1225 border: 1px solid var(--border);
1226 border-radius: 5px;
1227 padding: 14px;
1228 flex: 1;
1229 min-height: 130px;
1230 display: flex;
1231 align-items: center;
1232 justify-content: center;
1233 }
1234 .da-note { font-size:11.5px; color:var(--mute); line-height:1.5; }
1235
1236 /* Sequence LCS */
1237 .seq-vis { display:flex; flex-direction:column; gap:6px; width:100%; }
1238 .seq-row-lbl { font-family:var(--mono); font-size:9px; color:var(--dim); text-transform:uppercase; letter-spacing:.6px; }
1239 .seq-row { display:flex; gap:6px; flex-wrap:wrap; }
1240 .seq-blk {
1241 display:flex; flex-direction:column; align-items:center;
1242 padding:5px 9px; border-radius:5px; border:1.5px solid;
1243 min-width:46px; gap:2px; transition:transform .15s;
1244 }
1245 .seq-blk:hover { transform:translateY(-2px); }
1246 .seq-hash { font-family:var(--mono); font-size:7.5px; opacity:.55; }
1247 .seq-name { font-family:var(--mono); font-size:14px; font-weight:700; }
1248 .seq-match { background:rgba(88,166,255,.07); border-color:rgba(88,166,255,.25); color:#58a6ff; }
1249 .seq-del { background:rgba(239,83,80,.1); border-color:rgba(239,83,80,.35); color:#ef5350; text-decoration:line-through; }
1250 .seq-ins { background:rgba(63,185,80,.1); border-color:rgba(63,185,80,.35); color:#3fb950; }
1251 .seq-moved-from{ background:rgba(188,140,255,.06); border-color:rgba(188,140,255,.25);color:#bc8cff; opacity:.55; }
1252 .seq-moved-to { background:rgba(188,140,255,.12); border-color:rgba(188,140,255,.5); color:#bc8cff; }
1253 .seq-ops-row { display:flex; gap:6px; padding:2px 0; flex-wrap:wrap; }
1254 .seq-op-cell { min-width:46px; display:flex; justify-content:center; }
1255 .da-op { font-size:9px; font-weight:700; letter-spacing:.3px; padding:1px 5px; border-radius:3px; white-space:nowrap; }
1256 .da-op-match { color:var(--dim); }
1257 .da-op-delete { color:#ef5350; background:rgba(239,83,80,.1); }
1258 .da-op-insert { color:#3fb950; background:rgba(63,185,80,.1); }
1259 .da-op-move { color:#bc8cff; background:rgba(188,140,255,.1); }
1260
1261 /* Tree SVG */
1262 .tree-vis { width:100%; height:130px; overflow:visible; }
1263
1264 /* Tensor */
1265 .tensor-wrap { display:flex; align-items:center; gap:14px; }
1266 .tensor-panel { display:flex; flex-direction:column; align-items:center; gap:5px; }
1267 .tensor-label { font-family:var(--mono); font-size:9px; color:var(--dim); }
1268 .tensor-grid { display:grid; grid-template-columns:repeat(4,26px); gap:3px; }
1269 .tc { width:26px; height:26px; border-radius:3px; }
1270 .tc-0 { background:#141c28; }
1271 .tc-1 { background:#1a2538; }
1272 .tc-2 { background:#1d2f50; }
1273 .tc-3 { background:#1a3060; }
1274 .tc-warm1 { background:rgba(249,168,37,.28); border:1px solid rgba(249,168,37,.4); }
1275 .tc-hot2 { background:rgba(239,83,80,.45); border:1px solid rgba(239,83,80,.65); }
1276 .tc-hot3 { background:rgba(239,83,80,.65); border:1px solid rgba(239,83,80,.9); }
1277 .tensor-arrow { font-size:22px; color:var(--dim); }
1278 .tensor-legend { display:flex; flex-direction:column; gap:5px; margin-top:4px; }
1279 .tl-item { display:flex; align-items:center; gap:5px; font-size:9px; color:var(--mute); }
1280 .tl-swatch { width:12px; height:12px; border-radius:2px; flex-shrink:0; }
1281 .tl-unchanged { background:#141c28; }
1282 .tl-warm { background:rgba(249,168,37,.28); border:1px solid rgba(249,168,37,.4); }
1283 .tl-hot { background:rgba(239,83,80,.65); border:1px solid rgba(239,83,80,.9); }
1284
1285 /* Set algebra */
1286 .set-vis { display:grid; grid-template-columns:1fr auto 1fr; gap:8px; align-items:start; width:100%; }
1287 .set-col .set-header { font-family:var(--mono); font-size:9px; color:var(--dim); text-transform:uppercase; letter-spacing:.6px; margin-bottom:6px; }
1288 .set-members { display:flex; flex-direction:column; gap:4px; }
1289 .set-member { font-family:var(--mono); font-size:10px; padding:3px 8px; border-radius:3px; border:1px solid; }
1290 .set-kept { color:var(--mute); border-color:var(--border); background:var(--bg3); }
1291 .set-removed { color:#ef5350; border-color:rgba(239,83,80,.35); background:rgba(239,83,80,.07); text-decoration:line-through; }
1292 .set-added { color:#3fb950; border-color:rgba(63,185,80,.35); background:rgba(63,185,80,.07); }
1293 .set-ops-col { display:flex; flex-direction:column; gap:4px; padding-top:21px; }
1294 .set-op-line { font-size:10px; font-weight:700; white-space:nowrap; height:26px; display:flex; align-items:center; }
1295 .set-op-keep { color:var(--dim); }
1296 .set-op-rm { color:#ef5350; }
1297 .set-op-add { color:#3fb950; }
1298
1299 /* Map recursive */
1300 .map-vis { display:flex; flex-direction:column; gap:5px; width:100%; font-family:var(--mono); }
1301 .map-entry { display:flex; align-items:center; gap:6px; padding:5px 8px; border-radius:4px; font-size:11px; flex-wrap:wrap; }
1302 .map-entry-changed { background:rgba(88,166,255,.04); border:1px solid rgba(88,166,255,.15); }
1303 .map-entry-unchanged { background:var(--bg3); border:1px solid var(--border); opacity:.65; }
1304 .map-key { color:#61afef; font-weight:700; min-width:52px; }
1305 .map-delta { color:var(--dim); }
1306 .map-val-before { color:var(--dim); font-size:10px; }
1307 .map-val-after { color:var(--text); font-size:10px; }
1308 .map-sub-algo { margin-left:auto; font-size:9px; font-weight:700; padding:1px 6px; border-radius:3px; border:1px solid; }
1309
1310 /* StructuredDelta flow */
1311 .da-delta-flow {
1312 background: var(--bg2);
1313 border: 1px solid var(--border);
1314 border-radius: var(--r);
1315 padding: 24px;
1316 }
1317 .da-delta-top { margin-bottom:14px; display:flex; align-items:baseline; gap:12px; flex-wrap:wrap; }
1318 .da-delta-label { font-family:var(--mono); font-size:15px; font-weight:700; color:var(--text); }
1319 .da-delta-sub { font-size:12px; color:var(--mute); }
1320 .da-delta-ops { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:18px; }
1321 .da-dop { font-family:var(--mono); font-size:12px; font-weight:700; padding:5px 14px; border-radius:5px; border:1px solid; }
1322 .da-dop-ins { color:#3fb950; background:rgba(63,185,80,.1); border-color:rgba(63,185,80,.3); }
1323 .da-dop-del { color:#ef5350; background:rgba(239,83,80,.1); border-color:rgba(239,83,80,.3); }
1324 .da-dop-mov { color:#bc8cff; background:rgba(188,140,255,.1);border-color:rgba(188,140,255,.3); }
1325 .da-dop-rep { color:#f9a825; background:rgba(249,168,37,.1); border-color:rgba(249,168,37,.3); }
1326 .da-dop-pat { color:#58a6ff; background:rgba(88,166,255,.1); border-color:rgba(88,166,255,.3); }
1327 .da-delta-merge { display:grid; grid-template-columns:1fr auto 1fr; gap:14px; align-items:center; }
1328 @media (max-width: 640px) { .da-delta-merge { grid-template-columns:1fr; } .da-merge-divider { text-align:center; } }
1329 .da-merge-branch { padding:14px 16px; border-radius:6px; border:1px solid; }
1330 .da-merge-ot { border-color:rgba(239,83,80,.3); background:rgba(239,83,80,.04); }
1331 .da-merge-crdt { border-color:rgba(188,140,255,.3); background:rgba(188,140,255,.04); }
1332 .da-merge-mode-label { font-family:var(--mono); font-size:11px; font-weight:700; margin-bottom:5px; }
1333 .da-merge-ot .da-merge-mode-label { color:#ef5350; }
1334 .da-merge-crdt .da-merge-mode-label { color:#bc8cff; }
1335 .da-merge-desc { font-size:11.5px; color:var(--mute); line-height:1.5; }
1336 .da-merge-divider { color:var(--dim); font-size:13px; font-weight:700; }
1337
1338 /* ---- CRDT visualizations ---- */
1339 .crdt-vis { display:flex; flex-direction:column; gap:10px; width:100%; }
1340 /* concurrent replicas row */
1341 .crdt-concurrent { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
1342 .crdt-rep {
1343 background:var(--bg3); border:1px solid var(--border);
1344 border-radius:5px; padding:9px 10px;
1345 display:flex; flex-direction:column; gap:4px;
1346 }
1347 .crdt-rep-hdr { font-family:var(--mono); font-size:9px; font-weight:700; color:var(--dim); text-transform:uppercase; letter-spacing:.6px; }
1348 .crdt-op { font-family:var(--mono); font-size:10px; padding:2px 6px; border-radius:3px; }
1349 .crdt-op-add { color:#3fb950; background:rgba(63,185,80,.1); }
1350 .crdt-op-rm { color:#ef5350; background:rgba(239,83,80,.1); }
1351 .crdt-rep-state { font-family:var(--mono); font-size:11px; color:var(--mute); margin-top:2px; }
1352 /* join result row */
1353 .crdt-join { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:8px 10px; background:var(--bg3); border-radius:5px; border:1px solid var(--border); }
1354 .crdt-join-label { font-family:var(--mono); font-size:10px; color:var(--dim); white-space:nowrap; }
1355 .crdt-join-val { font-family:var(--mono); font-size:13px; font-weight:700; }
1356 .crdt-join-rule { font-size:10px; color:var(--mute); font-style:italic; margin-left:auto; }
1357 /* LWW timeline */
1358 .crdt-writes { display:flex; flex-direction:column; gap:5px; }
1359 .crdt-write { display:flex; align-items:center; gap:8px; padding:7px 10px; border-radius:5px; border:1px solid var(--border); background:var(--bg3); font-family:var(--mono); font-size:11px; }
1360 .crdt-write-winner { border-color:rgba(88,166,255,.4); background:rgba(88,166,255,.05); }
1361 .crdt-time { font-size:9px; color:var(--dim); min-width:32px; }
1362 .crdt-agent { font-size:9px; font-weight:700; padding:1px 6px; border-radius:3px; min-width:18px; text-align:center; }
1363 .crdt-agent-a { background:rgba(249,168,37,.15); color:#f9a825; }
1364 .crdt-agent-b { background:rgba(88,166,255,.15); color:#58a6ff; }
1365 .crdt-wval { color:var(--text); flex:1; }
1366 .crdt-latest { font-size:9px; font-weight:700; color:#58a6ff; background:rgba(88,166,255,.12); padding:1px 6px; border-radius:10px; white-space:nowrap; }
1367 /* GCounter bars */
1368 .crdt-gcounter { display:flex; flex-direction:column; gap:7px; }
1369 .crdt-gc-row { display:flex; align-items:center; gap:8px; }
1370 .crdt-bar { flex:1; height:22px; background:var(--bg3); border-radius:4px; overflow:hidden; }
1371 .crdt-bar-fill { height:100%; display:flex; align-items:center; justify-content:flex-end; padding-right:7px; font-family:var(--mono); font-size:11px; font-weight:700; border-radius:4px; transition:width .4s; }
1372 .crdt-bar-a { background:rgba(249,168,37,.35); color:#f9a825; }
1373 .crdt-bar-b { background:rgba(88,166,255,.35); color:#58a6ff; }
1374 /* VectorClock grid */
1375 .crdt-vclocks { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
1376 .crdt-vc { display:flex; flex-direction:column; gap:4px; }
1377 .crdt-vc-hdr { font-family:var(--mono); font-size:9px; color:var(--dim); text-transform:uppercase; letter-spacing:.5px; }
1378 .crdt-vc-cells { display:flex; gap:4px; }
1379 .crdt-vc-cell { font-family:var(--mono); font-size:10.5px; font-weight:700; padding:4px 8px; border-radius:4px; border:1px solid; }
1380 .crdt-vc-self { color:#f9a825; background:rgba(249,168,37,.1); border-color:rgba(249,168,37,.35); }
1381 .crdt-vc-zero { color:var(--dim); background:var(--bg3); border-color:var(--border); }
1382 .crdt-vc-max { color:#3fb950; background:rgba(63,185,80,.1); border-color:rgba(63,185,80,.35); }
1383 .crdt-vc-sep { font-size:16px; color:var(--dim); padding:0 2px; align-self:center; margin-top:14px; }
1384 .crdt-concurrent-badge { font-family:var(--mono); font-size:10px; color:#f9a825; background:rgba(249,168,37,.08); border:1px solid rgba(249,168,37,.25); border-radius:4px; padding:3px 10px; align-self:flex-start; }
1385
1386 /* ---- OT Merge scenario cards ---- */
1387 .ot-scenarios { display: flex; flex-direction: column; gap: 10px; }
1388 .ot-scenario {
1389 background: var(--bg);
1390 border: 1px solid var(--border);
1391 border-left: 3px solid transparent;
1392 border-radius: 6px;
1393 padding: 12px 14px;
1394 display: flex;
1395 flex-direction: column;
1396 gap: 9px;
1397 }
1398 .ot-clean { border-left-color: #3fb950; }
1399 .ot-conflict { border-left-color: #ef5350; }
1400 .ot-scenario-hdr { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
1401 .ot-scenario-label {
1402 font-family: var(--mono);
1403 font-size: 9.5px;
1404 font-weight: 700;
1405 text-transform: uppercase;
1406 letter-spacing: 1px;
1407 color: var(--dim);
1408 }
1409 .ot-scenario-title { font-size: 11.5px; color: var(--mute); }
1410 .ot-ops { display: flex; flex-direction: column; gap: 5px; }
1411 .ot-op {
1412 display: flex;
1413 align-items: center;
1414 gap: 7px;
1415 font-family: var(--mono);
1416 font-size: 11.5px;
1417 flex-wrap: wrap;
1418 }
1419 .ot-op-side {
1420 font-size: 9px;
1421 font-weight: 700;
1422 color: var(--dim);
1423 background: var(--bg3);
1424 padding: 1px 6px;
1425 border-radius: 3px;
1426 min-width: 34px;
1427 text-align: center;
1428 }
1429 .ot-op-type { font-weight: 700; padding: 1px 7px; border-radius: 3px; font-size: 10.5px; }
1430 .ot-insert { background: rgba(63,185,80,0.13); color: #3fb950; }
1431 .ot-replace { background: rgba(249,168,37,0.13); color: #f9a825; }
1432 .ot-op-addr { color: #98c379; }
1433 .ot-op-meta { color: var(--dim); font-size: 10.5px; }
1434 .ot-result {
1435 display: flex;
1436 align-items: center;
1437 justify-content: space-between;
1438 flex-wrap: wrap;
1439 gap: 8px;
1440 padding-top: 9px;
1441 border-top: 1px solid var(--border);
1442 }
1443 .ot-reason { font-family: var(--mono); font-size: 11px; color: var(--mute); }
1444 .ot-badge {
1445 display: inline-flex;
1446 align-items: center;
1447 gap: 5px;
1448 font-size: 11px;
1449 font-weight: 700;
1450 padding: 3px 10px;
1451 border-radius: 12px;
1452 white-space: nowrap;
1453 }
1454 .ot-badge .icon { width: 11px; height: 11px; vertical-align: -0.05em; }
1455 .ot-badge-clean { background: rgba(63,185,80,0.1); color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
1456 .ot-badge-conflict { background: rgba(239,83,80,0.1); color: #ef5350; border: 1px solid rgba(239,83,80,0.3); }
1457
1458 .cap-showcase-domain-grid {
1459 display: flex;
1460 flex-direction: column;
1461 gap: 10px;
1462 }
1463 .crdt-mini-grid {
1464 display: grid;
1465 grid-template-columns: 1fr 1fr;
1466 gap: 10px;
1467 }
1468 @media (max-width: 700px) { .crdt-mini-grid { grid-template-columns: 1fr; } }
1469
1470 /* ---- Three steps ---- */
1471 .steps-grid {
1472 display: grid;
1473 grid-template-columns: repeat(3, 1fr);
1474 gap: 24px;
1475 }
1476 @media (max-width: 800px) { .steps-grid { grid-template-columns: 1fr; } }
1477 .step-card {
1478 border: 1px solid var(--border);
1479 border-radius: var(--r);
1480 background: var(--bg2);
1481 padding: 24px;
1482 position: relative;
1483 }
1484 .step-num {
1485 font-family: var(--mono);
1486 font-size: 11px;
1487 color: var(--accent);
1488 font-weight: 700;
1489 text-transform: uppercase;
1490 letter-spacing: 1px;
1491 margin-bottom: 10px;
1492 }
1493 .step-title { font-size: 17px; font-weight: 700; margin-bottom: 8px; }
1494 .step-desc { font-size: 13px; color: var(--mute); line-height: 1.6; margin-bottom: 16px; }
1495 .step-cmd {
1496 font-family: var(--mono);
1497 font-size: 12px;
1498 background: var(--bg3);
1499 border: 1px solid var(--border);
1500 border-radius: 5px;
1501 padding: 10px 14px;
1502 color: var(--accent2);
1503 }
1504
1505 /* ---- Code block ---- */
1506 .code-wrap {
1507 border: 1px solid var(--border);
1508 border-radius: var(--r);
1509 overflow: hidden;
1510 }
1511 .code-bar {
1512 background: var(--bg3);
1513 border-bottom: 1px solid var(--border);
1514 padding: 8px 16px;
1515 display: flex;
1516 align-items: center;
1517 gap: 8px;
1518 }
1519 .code-bar-dot {
1520 width: 10px; height: 10px; border-radius: 50%;
1521 }
1522 .code-bar-title {
1523 font-family: var(--mono);
1524 font-size: 12px;
1525 color: var(--mute);
1526 margin-left: 6px;
1527 }
1528 .code-body {
1529 background: #0a0e14;
1530 padding: 20px 24px;
1531 font-family: var(--mono);
1532 font-size: 12.5px;
1533 line-height: 1.7;
1534 color: #abb2bf;
1535 white-space: pre;
1536 overflow-x: auto;
1537 }
1538 /* Simple syntax highlights */
1539 .kw { color: #c678dd; }
1540 .kw2 { color: #e06c75; }
1541 .fn { color: #61afef; }
1542 .str { color: #98c379; }
1543 .cmt { color: #5c6370; font-style: italic; }
1544 .cls { color: #e5c07b; }
1545 .typ { color: #56b6c2; }
1546
1547 /* ---- Active domains grid ---- */
1548 .domain-grid {
1549 display: grid;
1550 grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
1551 gap: 20px;
1552 }
1553 .domain-card {
1554 border: 1px solid var(--border);
1555 border-radius: var(--r);
1556 background: var(--bg2);
1557 overflow: hidden;
1558 transition: border-color 0.2s, transform 0.15s;
1559 }
1560 .domain-card:hover { border-color: var(--accent); transform: translateY(-2px); }
1561 .domain-card.active-domain { border-color: rgba(63,185,80,0.4); }
1562 .domain-card-hdr {
1563 background: var(--bg3);
1564 padding: 12px 16px;
1565 border-bottom: 1px solid var(--border);
1566 display: flex;
1567 align-items: center;
1568 gap: 10px;
1569 }
1570 .active-badge { font-size: 11px; padding: 2px 8px; border-radius: 4px; background: rgba(63,185,80,0.12); border: 1px solid rgba(63,185,80,0.3); color: var(--green); font-family: var(--mono); }
1571 .reg-badge { font-size: 11px; padding: 2px 8px; border-radius: 4px; background: var(--bg); border: 1px solid var(--border); color: var(--mute); font-family: var(--mono); }
1572 .active-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); margin-left: auto; }
1573 .domain-name-lg { font-family: var(--mono); font-size: 16px; font-weight: 700; color: var(--text); }
1574 .domain-card-body { padding: 16px; }
1575 .domain-desc { font-size: 13px; color: var(--mute); margin-bottom: 12px; }
1576 .cap-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
1577 .cap-pill { font-size: 10px; padding: 2px 8px; border-radius: 12px; border: 1px solid var(--border); color: var(--mute); background: var(--bg3); }
1578 .cap-pill.cap-crdt { border-color: rgba(188,140,255,0.4); color: var(--purple); background: rgba(188,140,255,0.08); }
1579 .cap-pill.cap-ot-merge { border-color: rgba(88,166,255,0.4); color: var(--accent2); background: rgba(88,166,255,0.08); }
1580 .cap-pill.cap-domain-schema { border-color: rgba(63,185,80,0.4); color: var(--green); background: rgba(63,185,80,0.08); }
1581 .cap-pill.cap-typed-deltas { border-color: rgba(249,168,37,0.4); color: #f9a825; background: rgba(249,168,37,0.08); }
1582 .dim-row { font-size: 11px; color: var(--dim); }
1583 .dim-tag { color: var(--mute); }
1584
1585 /* ---- Planned domains ---- */
1586 .planned-grid {
1587 display: grid;
1588 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1589 gap: 16px;
1590 }
1591 .planned-card {
1592 border: 1px solid var(--border);
1593 border-radius: var(--r);
1594 background: var(--bg2);
1595 padding: 20px 16px;
1596 display: flex;
1597 flex-direction: column;
1598 gap: 8px;
1599 transition: border-color 0.2s, transform 0.15s;
1600 }
1601 .planned-card:hover { border-color: var(--card-accent,var(--accent)); transform: translateY(-2px); }
1602 .planned-card.yours { border: 2px dashed var(--accent); background: rgba(79,142,247,0.04); }
1603 .planned-icon { line-height: 0; }
1604 .planned-icon .icon { width: 28px; height: 28px; }
1605 .planned-name { font-size: 15px; font-weight: 700; color: var(--text); }
1606 .planned-tag { font-size: 12px; color: var(--mute); line-height: 1.5; }
1607 .planned-dims { font-size: 10px; color: var(--dim); }
1608 .coming-soon { font-size: 10px; color: var(--dim); border: 1px solid var(--border); border-radius: 12px; padding: 2px 8px; display: inline-block; margin-top: 4px; }
1609 .cta-btn {
1610 display: inline-block;
1611 margin-top: 6px;
1612 font-size: 12px;
1613 font-weight: 600;
1614 color: var(--accent2);
1615 border: 1px solid rgba(88,166,255,0.4);
1616 border-radius: 4px;
1617 padding: 4px 12px;
1618 text-decoration: none;
1619 transition: background 0.15s;
1620 }
1621 .cta-btn:hover { background: rgba(88,166,255,0.1); text-decoration: none; }
1622
1623 /* ---- Distribution tiers ---- */
1624 .dist-grid {
1625 display: grid;
1626 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1627 gap: 24px;
1628 }
1629 .dist-card {
1630 border: 1px solid var(--border);
1631 border-top: 3px solid var(--dist-color, var(--accent));
1632 border-radius: var(--r);
1633 background: var(--bg2);
1634 padding: 24px;
1635 transition: transform 0.15s;
1636 }
1637 .dist-card:hover { transform: translateY(-2px); }
1638 .dist-header { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 14px; }
1639 .dist-icon { line-height: 0; flex-shrink: 0; }
1640 .dist-icon .icon { width: 26px; height: 26px; }
1641 .dist-tier { font-family: var(--mono); font-size: 11px; color: var(--dist-color,var(--accent)); letter-spacing: 1px; text-transform: uppercase; font-weight: 700; }
1642 .dist-title { font-size: 14px; font-weight: 600; color: var(--text); margin-top: 2px; }
1643 .dist-desc { font-size: 13px; color: var(--mute); margin-bottom: 16px; line-height: 1.6; }
1644 .dist-steps { list-style: none; counter-reset: step; display: flex; flex-direction: column; gap: 6px; }
1645 .dist-steps li { counter-increment: step; display: flex; align-items: flex-start; gap: 8px; font-size: 12px; color: var(--mute); }
1646 .dist-steps li::before { content: counter(step); min-width: 18px; height: 18px; background: var(--dist-color,var(--accent)); color: #000; border-radius: 50%; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px; }
1647 .dist-steps code { background: var(--bg3); border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; font-size: 11px; }
1648
1649 /* ---- MuseHub teaser ---- */
1650 .musehub-section {
1651 background: linear-gradient(135deg, #0d1117 0%, #1a0d2e 50%, #0d1117 100%);
1652 padding: 80px 40px;
1653 text-align: center;
1654 border-top: 1px solid var(--border);
1655 }
1656 .musehub-logo {
1657 margin-bottom: 20px;
1658 line-height: 0;
1659 }
1660 .musehub-logo .icon { width: 48px; height: 48px; stroke: #bc8cff; }
1661 .musehub-section h2 {
1662 font-size: 36px;
1663 font-weight: 800;
1664 letter-spacing: -1px;
1665 margin-bottom: 12px;
1666 }
1667 .musehub-section h2 span {
1668 background: linear-gradient(135deg, #bc8cff, #4f8ef7);
1669 -webkit-background-clip: text;
1670 -webkit-text-fill-color: transparent;
1671 background-clip: text;
1672 }
1673 .musehub-desc {
1674 font-size: 16px;
1675 color: var(--mute);
1676 max-width: 560px;
1677 margin: 0 auto 36px;
1678 }
1679 .musehub-desc strong { color: var(--text); }
1680 .musehub-features {
1681 display: flex;
1682 gap: 24px;
1683 justify-content: center;
1684 flex-wrap: wrap;
1685 margin-bottom: 40px;
1686 }
1687 .mh-feature {
1688 background: var(--bg2);
1689 border: 1px solid rgba(188,140,255,0.2);
1690 border-radius: var(--r);
1691 padding: 16px 20px;
1692 text-align: left;
1693 min-width: 180px;
1694 }
1695 .mh-feature-icon { margin-bottom: 10px; line-height: 0; }
1696 .mh-feature-icon .icon { width: 22px; height: 22px; stroke: #bc8cff; }
1697 .mh-feature-title { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
1698 .mh-feature-desc { font-size: 12px; color: var(--mute); }
1699 .musehub-status {
1700 display: inline-flex;
1701 align-items: center;
1702 gap: 8px;
1703 background: rgba(188,140,255,0.1);
1704 border: 1px solid rgba(188,140,255,0.3);
1705 border-radius: 20px;
1706 padding: 8px 20px;
1707 font-size: 13px;
1708 color: var(--purple);
1709 }
1710 .mh-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--purple); animation: pulse 2s ease-in-out infinite; }
1711 @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
1712
1713 /* ---- Footer ---- */
1714 footer {
1715 background: var(--bg2);
1716 border-top: 1px solid var(--border);
1717 padding: 24px 40px;
1718 display: flex;
1719 justify-content: space-between;
1720 align-items: center;
1721 flex-wrap: wrap;
1722 gap: 12px;
1723 font-size: 13px;
1724 color: var(--mute);
1725 }
1726 footer a { color: var(--accent2); }
1727
1728 /* ---- Nav ---- */
1729 nav {
1730 background: var(--bg2);
1731 border-bottom: 1px solid var(--border);
1732 padding: 0 40px;
1733 display: flex;
1734 align-items: center;
1735 gap: 0;
1736 height: 52px;
1737 }
1738 .nav-logo {
1739 font-family: var(--mono);
1740 font-size: 16px;
1741 font-weight: 700;
1742 color: var(--accent2);
1743 margin-right: 32px;
1744 text-decoration: none;
1745 }
1746 .nav-logo:hover { text-decoration: none; }
1747 .nav-link {
1748 font-size: 13px;
1749 color: var(--mute);
1750 padding: 0 14px;
1751 height: 100%;
1752 display: flex;
1753 align-items: center;
1754 border-bottom: 2px solid transparent;
1755 text-decoration: none;
1756 transition: color 0.15s, border-color 0.15s;
1757 }
1758 .nav-link:hover { color: var(--text); text-decoration: none; }
1759 .nav-link.current { color: var(--text); border-bottom-color: var(--accent); }
1760 .nav-spacer { flex: 1; }
1761 .nav-badge {
1762 font-size: 11px;
1763 background: rgba(79,142,247,0.12);
1764 border: 1px solid rgba(79,142,247,0.3);
1765 color: var(--accent2);
1766 border-radius: 4px;
1767 padding: 2px 8px;
1768 font-family: var(--mono);
1769 }
1770 </style>
1771 </head>
1772 <body>
1773
1774 <nav>
1775 <a class="nav-logo" href="#">muse</a>
1776 <a class="nav-link" href="tour_de_force.html">Demo</a>
1777 <a class="nav-link current" href="index.html">Domain Registry</a>
1778 <a class="nav-link" href="../docs/guide/plugin-authoring-guide.md">Plugin Guide</a>
1779 <div class="nav-spacer"></div>
1780 <span class="nav-badge">v0.1.1</span>
1781 </nav>
1782
1783 <!-- =================== HERO =================== -->
1784 <div class="hero">
1785 <h1 class="hero-wordmark">muse</h1>
1786 <div class="hero-version-any">Version Anything</div>
1787 <p class="hero-sub">
1788 One protocol. Any domain. <strong>Six methods</strong> between you and a
1789 complete version control system — branching, merging, conflict resolution,
1790 time-travel, and typed diffs — for free.
1791 </p>
1792 <div class="hero-cta-row">
1793 <a class="btn-primary" href="#build">Build a Domain Plugin</a>
1794 <a class="btn-outline" href="tour_de_force.html">Watch the Demo →</a>
1795 </div>
1796 <div class="domain-ticker">
1797 <div class="ticker-track">
1798 <span class="ticker-item active">{{ICON_MUSIC}} music</span>
1799 <span class="ticker-item">{{ICON_GENOMICS}} genomics</span>
1800 <span class="ticker-item">{{ICON_CUBE}} 3d-spatial</span>
1801 <span class="ticker-item">{{ICON_TRENDING}} financial</span>
1802 <span class="ticker-item">{{ICON_ATOM}} simulation</span>
1803 <span class="ticker-item">{{ICON_ACTIVITY}} proteomics</span>
1804 <span class="ticker-item">{{ICON_PEN_TOOL}} cad</span>
1805 <span class="ticker-item">{{ICON_ZAP}} game-state</span>
1806 <span class="ticker-item">{{ICON_PLUS}} your-domain</span>
1807 <!-- duplicate for seamless loop -->
1808 <span class="ticker-item active">{{ICON_MUSIC}} music</span>
1809 <span class="ticker-item">{{ICON_GENOMICS}} genomics</span>
1810 <span class="ticker-item">{{ICON_CUBE}} 3d-spatial</span>
1811 <span class="ticker-item">{{ICON_TRENDING}} financial</span>
1812 <span class="ticker-item">{{ICON_ATOM}} simulation</span>
1813 <span class="ticker-item">{{ICON_ACTIVITY}} proteomics</span>
1814 <span class="ticker-item">{{ICON_PEN_TOOL}} cad</span>
1815 <span class="ticker-item">{{ICON_ZAP}} game-state</span>
1816 <span class="ticker-item">{{ICON_PLUS}} your-domain</span>
1817 </div>
1818 </div>
1819 </div>
1820
1821 <!-- =================== PROTOCOL =================== -->
1822 <section id="protocol">
1823 <div class="inner">
1824 <div class="section-eyebrow">The Contract</div>
1825 <h2>The MuseDomainPlugin Protocol</h2>
1826 <p class="section-lead">
1827 Every domain — music, genomics, 3D spatial, financial models — implements
1828 the same <strong>six-method protocol</strong>. The core engine handles
1829 everything else: content-addressed storage, DAG, branches, log, merge base,
1830 cherry-pick, revert, stash, tags.
1831 </p>
1832
1833 <div class="proto-layout">
1834 <div class="stat-strip">
1835 <div class="stat-cell"><span class="stat-num">6</span><span class="stat-lbl">methods to implement</span></div>
1836 <div class="stat-cell"><span class="stat-num">14</span><span class="stat-lbl">CLI commands, free</span></div>
1837 <div class="stat-cell"><span class="stat-num">∞</span><span class="stat-lbl">domains possible</span></div>
1838 <div class="stat-cell"><span class="stat-num">0</span><span class="stat-lbl">core changes needed</span></div>
1839 </div>
1840 <div class="proto-table">
1841 <div class="proto-row hdr">
1842 <div class="proto-method">Method</div>
1843 <div class="proto-sig">Signature</div>
1844 <div class="proto-desc">Purpose</div>
1845 </div>
1846 <div class="proto-row">
1847 <div class="proto-method">snapshot</div>
1848 <div class="proto-sig">snapshot(live) → StateSnapshot</div>
1849 <div class="proto-desc">Capture current state as a content-addressable blob</div>
1850 </div>
1851 <div class="proto-row">
1852 <div class="proto-method">diff</div>
1853 <div class="proto-sig">diff(base, target) → StateDelta</div>
1854 <div class="proto-desc">Compute minimal change between two snapshots (added · removed · modified)</div>
1855 </div>
1856 <div class="proto-row">
1857 <div class="proto-method">merge</div>
1858 <div class="proto-sig">merge(base, left, right) → MergeResult</div>
1859 <div class="proto-desc">Three-way reconcile divergent state lines; surface conflicts per dimension</div>
1860 </div>
1861 <div class="proto-row">
1862 <div class="proto-method">drift</div>
1863 <div class="proto-sig">drift(committed, live) → DriftReport</div>
1864 <div class="proto-desc">Detect uncommitted changes between HEAD and working state</div>
1865 </div>
1866 <div class="proto-row">
1867 <div class="proto-method">apply</div>
1868 <div class="proto-sig">apply(delta, live) → LiveState</div>
1869 <div class="proto-desc">Apply a delta during checkout to reconstruct historical state</div>
1870 </div>
1871 <div class="proto-row">
1872 <div class="proto-method">schema</div>
1873 <div class="proto-sig">schema() → DomainSchema</div>
1874 <div class="proto-desc">Declare data structure — drives diff algorithm selection per dimension</div>
1875 </div>
1876 </div>
1877 </div>
1878 </div>
1879 </section>
1880
1881 {{DIFF_ALGEBRA}}
1882
1883 <!-- =================== ENGINE CAPABILITIES =================== -->
1884 <section id="capabilities" style="background:var(--bg2)">
1885 <div class="inner">
1886 <div class="section-eyebrow">Engine Capabilities</div>
1887 <h2>What Every Plugin Gets for Free</h2>
1888 <p class="section-lead">
1889 The core engine provides four advanced capabilities that any domain plugin
1890 can opt into. Implement the protocol — the engine does the rest.
1891 </p>
1892
1893 <div class="cap-showcase-grid">
1894
1895 <div class="cap-showcase-card" style="--cap-color:#f9a825">
1896 <div class="cap-showcase-header">
1897 <span class="cap-showcase-badge" style="color:#f9a825;background:#f9a82515;border-color:#f9a82540">
1898 {{ICON_CODE}} Typed Delta Algebra
1899 </span>
1900 <span class="cap-showcase-sub">StructuredDelta — every change is a typed operation</span>
1901 </div>
1902 <div class="cap-showcase-body">
1903 <p class="cap-showcase-desc">
1904 Unlike Git's blob diffs, Muse deltas are <strong>typed objects</strong>:
1905 <code>InsertOp</code>, <code>ReplaceOp</code>, <code>DeleteOp</code> — each
1906 carrying the address, before/after hashes, and affected dimensions.
1907 Machine-readable with <code>muse show --json</code>.
1908 </p>
1909 <pre class="cap-showcase-output" data-lang="json">{{TYPED_DELTA_EXAMPLE}}</pre>
1910 </div>
1911 </div>
1912
1913 <div class="cap-showcase-card" style="--cap-color:#58a6ff">
1914 <div class="cap-showcase-header">
1915 <span class="cap-showcase-badge" style="color:#58a6ff;background:#58a6ff15;border-color:#58a6ff40">
1916 {{ICON_LAYERS}} Domain Schema
1917 </span>
1918 <span class="cap-showcase-sub">Per-domain dimensions drive diff algorithm selection</span>
1919 </div>
1920 <div class="cap-showcase-body">
1921 <p class="cap-showcase-desc">
1922 Each plugin's <code>schema()</code> method declares its dimensions and merge mode.
1923 The engine uses this to select the right diff algorithm per dimension and to
1924 surface only the dimensions that actually conflict.
1925 </p>
1926 <div class="cap-showcase-domain-grid" id="schema-domain-grid">
1927 {{ACTIVE_DOMAINS}}
1928 </div>
1929 </div>
1930 </div>
1931
1932 <div class="cap-showcase-card" style="--cap-color:#ef5350">
1933 <div class="cap-showcase-header">
1934 <span class="cap-showcase-badge" style="color:#ef5350;background:#ef535015;border-color:#ef535040">
1935 {{ICON_GIT_MERGE}} OT Merge
1936 </span>
1937 <span class="cap-showcase-sub">Operational transformation — independent ops commute automatically</span>
1938 </div>
1939 <div class="cap-showcase-body">
1940 <p class="cap-showcase-desc">
1941 Plugins implementing <strong>StructuredMergePlugin</strong> get operational
1942 transformation. Operations at different addresses commute automatically —
1943 only operations on the same address with incompatible intent surface a conflict.
1944 </p>
1945 <div class="ot-scenarios">
1946
1947 <div class="ot-scenario ot-clean">
1948 <div class="ot-scenario-hdr">
1949 <span class="ot-scenario-label">Scenario A</span>
1950 <span class="ot-scenario-title">Independent ops at different addresses</span>
1951 </div>
1952 <div class="ot-ops">
1953 <div class="ot-op">
1954 <span class="ot-op-side">left</span>
1955 <span class="ot-op-type ot-insert">InsertOp</span>
1956 <span class="ot-op-addr">"ot-notes-a.mid"</span>
1957 <span class="ot-op-meta">tick=0 · C4 E4 G4</span>
1958 </div>
1959 <div class="ot-op">
1960 <span class="ot-op-side">right</span>
1961 <span class="ot-op-type ot-insert">InsertOp</span>
1962 <span class="ot-op-addr">"ot-notes-b.mid"</span>
1963 <span class="ot-op-meta">tick=480 · D4 F4 A4</span>
1964 </div>
1965 </div>
1966 <div class="ot-result">
1967 <span class="ot-reason">transform → no overlap → ops commute</span>
1968 <span class="ot-badge ot-badge-clean">{{ICON_CHECK_CIRCLE}} Clean merge · both files applied</span>
1969 </div>
1970 </div>
1971
1972 <div class="ot-scenario ot-conflict">
1973 <div class="ot-scenario-hdr">
1974 <span class="ot-scenario-label">Scenario B</span>
1975 <span class="ot-scenario-title">Same address, conflicting musical intent</span>
1976 </div>
1977 <div class="ot-ops">
1978 <div class="ot-op">
1979 <span class="ot-op-side">left</span>
1980 <span class="ot-op-type ot-replace">ReplaceOp</span>
1981 <span class="ot-op-addr">"shared-melody.mid"</span>
1982 <span class="ot-op-meta">C4 E4 G4 · major triad</span>
1983 </div>
1984 <div class="ot-op">
1985 <span class="ot-op-side">right</span>
1986 <span class="ot-op-type ot-replace">ReplaceOp</span>
1987 <span class="ot-op-addr">"shared-melody.mid"</span>
1988 <span class="ot-op-meta">C4 Eb4 G4 · minor triad</span>
1989 </div>
1990 </div>
1991 <div class="ot-result">
1992 <span class="ot-reason">transform → same address · non-commuting content</span>
1993 <span class="ot-badge ot-badge-conflict">{{ICON_X_CIRCLE}} Conflict · human resolves</span>
1994 </div>
1995 </div>
1996
1997 </div>
1998 </div>
1999 </div>
2000
2001 <div class="cap-showcase-card" style="--cap-color:#bc8cff">
2002 <div class="cap-showcase-header">
2003 <span class="cap-showcase-badge" style="color:#bc8cff;background:#bc8cff15;border-color:#bc8cff40">
2004 {{ICON_ZAP}} CRDT Primitives
2005 </span>
2006 <span class="cap-showcase-sub">Convergent merge — any two replicas always reach the same state</span>
2007 </div>
2008 <div class="cap-showcase-body">
2009 <p class="cap-showcase-desc">
2010 Plugins implementing <strong>CRDTPlugin</strong> get four battle-tested
2011 convergent data structures. No coordination required between replicas.
2012 </p>
2013 <div class="crdt-mini-grid">
2014 {{CRDT_CARDS}}
2015 </div>
2016 </div>
2017 </div>
2018
2019 </div>
2020 </div>
2021 </section>
2022
2023 <!-- =================== BUILD =================== -->
2024 <section id="build" style="background:var(--bg)">
2025 <div class="inner">
2026 <div class="section-eyebrow">Build</div>
2027 <h2>Build in Three Steps</h2>
2028 <p class="section-lead">
2029 One command scaffolds the entire plugin skeleton. You fill in six methods.
2030 The full VCS follows.
2031 </p>
2032
2033 <div class="steps-grid">
2034 <div class="step-card">
2035 <div class="step-num">Step 1 · Scaffold</div>
2036 <div class="step-title">Generate the skeleton</div>
2037 <div class="step-desc">
2038 One command creates the plugin directory, class, and all six method stubs
2039 with full type annotations.
2040 </div>
2041 <div class="step-cmd">muse domains --new genomics</div>
2042 </div>
2043 <div class="step-card">
2044 <div class="step-num">Step 2 · Implement</div>
2045 <div class="step-title">Fill in the six methods</div>
2046 <div class="step-desc">
2047 Replace each <code>raise NotImplementedError</code> with your domain's
2048 snapshot, diff, merge, drift, apply, and schema logic.
2049 </div>
2050 <div class="step-cmd">vim muse/plugins/genomics/plugin.py</div>
2051 </div>
2052 <div class="step-card">
2053 <div class="step-num">Step 3 · Use</div>
2054 <div class="step-title">Full VCS, instantly</div>
2055 <div class="step-desc">
2056 Register in <code>registry.py</code>, then every Muse command works
2057 for your domain out of the box.
2058 </div>
2059 <div class="step-cmd">muse init --domain genomics</div>
2060 </div>
2061 </div>
2062 </div>
2063 </section>
2064
2065 <!-- =================== CODE =================== -->
2066 <section id="code">
2067 <div class="inner">
2068 <div class="section-eyebrow">The Scaffold</div>
2069 <h2>What <code>muse domains --new genomics</code> produces</h2>
2070 <p class="section-lead">
2071 A fully typed, immediately runnable plugin skeleton. Every method has the
2072 correct signature. You replace the stubs — the protocol does the rest.
2073 </p>
2074 <div class="code-wrap">
2075 <div class="code-bar">
2076 <div class="code-bar-dot" style="background:#ff5f57"></div>
2077 <div class="code-bar-dot" style="background:#febc2e"></div>
2078 <div class="code-bar-dot" style="background:#28c840"></div>
2079 <span class="code-bar-title">muse/plugins/genomics/plugin.py</span>
2080 </div>
2081 <div class="code-body">{{SCAFFOLD_SNIPPET}}</div>
2082 </div>
2083 <p style="margin-top:16px;font-size:13px;color:var(--mute)">
2084 Full walkthrough →
2085 <a href="../docs/guide/plugin-authoring-guide.md">docs/guide/plugin-authoring-guide.md</a>
2086 · CRDT extension →
2087 <a href="../docs/guide/crdt-reference.md">docs/guide/crdt-reference.md</a>
2088 </p>
2089 </div>
2090 </section>
2091
2092 <!-- =================== ACTIVE DOMAINS =================== -->
2093 <section id="registry" style="background:var(--bg2)">
2094 <div class="inner">
2095 <div class="section-eyebrow">Registry</div>
2096 <h2>Registered Domains</h2>
2097 <p class="section-lead">
2098 Domains currently registered in this Muse instance. The active domain
2099 is the one used when you run <code>muse commit</code>, <code>muse diff</code>,
2100 and all other commands.
2101 </p>
2102 <div class="domain-grid">
2103 {{ACTIVE_DOMAINS}}
2104 </div>
2105 </div>
2106 </section>
2107
2108 <!-- =================== PLANNED ECOSYSTEM =================== -->
2109 <section id="ecosystem">
2110 <div class="inner">
2111 <div class="section-eyebrow">Ecosystem</div>
2112 <h2>The Plugin Ecosystem</h2>
2113 <p class="section-lead">
2114 Music is the reference implementation. These are the domains planned
2115 next — and the slot waiting for yours.
2116 </p>
2117 <div class="planned-grid">
2118 {{PLANNED_DOMAINS}}
2119 </div>
2120 </div>
2121 </section>
2122
2123 <!-- =================== DISTRIBUTION =================== -->
2124 <section id="distribute" style="background:var(--bg2)">
2125 <div class="inner">
2126 <div class="section-eyebrow">Distribution</div>
2127 <h2>How to Share Your Plugin</h2>
2128 <p class="section-lead">
2129 Three tiers of distribution — from local prototype to globally searchable
2130 registry. Start local, publish when ready.
2131 </p>
2132 <div class="dist-grid">
2133 {{DIST_CARDS}}
2134 </div>
2135 </div>
2136 </section>
2137
2138 <!-- =================== MUSEHUB TEASER =================== -->
2139 <div class="musehub-section">
2140 <div class="musehub-logo">{{ICON_GLOBE}}</div>
2141 <h2><span>MuseHub</span> is coming</h2>
2142 <p class="musehub-desc">
2143 A <strong>centralized, searchable registry</strong> for Muse domain plugins —
2144 think npm or crates.io, but for any multidimensional versioned state.
2145 One command to publish. One command to install.
2146 </p>
2147 <div class="musehub-features">
2148 <div class="mh-feature">
2149 <div class="mh-feature-icon">{{ICON_SEARCH}}</div>
2150 <div class="mh-feature-title">Searchable</div>
2151 <div class="mh-feature-desc">Find plugins by domain, capability, or keyword</div>
2152 </div>
2153 <div class="mh-feature">
2154 <div class="mh-feature-icon">{{ICON_PACKAGE}}</div>
2155 <div class="mh-feature-title">Versioned</div>
2156 <div class="mh-feature-desc">Semantic versioning, pinned installs, changelogs</div>
2157 </div>
2158 <div class="mh-feature">
2159 <div class="mh-feature-icon">{{ICON_LOCK}}</div>
2160 <div class="mh-feature-title">Private registries</div>
2161 <div class="mh-feature-desc">Self-host for enterprise or research teams</div>
2162 </div>
2163 <div class="mh-feature">
2164 <div class="mh-feature-icon">{{ICON_ZAP}}</div>
2165 <div class="mh-feature-title">One command</div>
2166 <div class="mh-feature-desc"><code>muse init --domain @musehub/genomics</code></div>
2167 </div>
2168 </div>
2169 <div class="musehub-status">
2170 <div class="mh-dot"></div>
2171 MuseHub — planned · building in public at <a href="https://github.com/cgcardona/musehub" target="_blank" rel="noopener noreferrer" style="color:inherit;text-decoration:underline;text-underline-offset:3px;">github.com/cgcardona/musehub</a>
2172 </div>
2173 </div>
2174
2175 <footer>
2176 <span>Muse v0.1.1 · domain-agnostic version control for multidimensional state</span>
2177 <span>
2178 <a href="tour_de_force.html">Demo</a> ·
2179 <a href="https://github.com/cgcardona/muse">GitHub</a> ·
2180 <a href="../docs/guide/plugin-authoring-guide.md">Plugin Guide</a>
2181 </span>
2182 </footer>
2183
2184 <script>
2185 (function () {
2186 function esc(s) {
2187 return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2188 }
2189
2190 function tokenizeJSON(raw) {
2191 let html = '';
2192 let i = 0;
2193 while (i < raw.length) {
2194 // Comment line starting with #
2195 if (raw[i] === '#') {
2196 const end = raw.indexOf('\n', i);
2197 const line = end === -1 ? raw.slice(i) : raw.slice(i, end);
2198 html += '<span style="color:#5c6370;font-style:italic">' + esc(line) + '</span>';
2199 i += line.length;
2200 continue;
2201 }
2202 // String literal
2203 if (raw[i] === '"') {
2204 let j = i + 1;
2205 while (j < raw.length && raw[j] !== '"') {
2206 if (raw[j] === '\\') j++;
2207 j++;
2208 }
2209 j++;
2210 const str = raw.slice(i, j);
2211 // Peek past whitespace — key if followed by ':'
2212 let k = j;
2213 while (k < raw.length && (raw[k] === ' ' || raw[k] === '\t')) k++;
2214 const color = raw[k] === ':' ? '#61afef' : '#98c379';
2215 html += '<span style="color:' + color + '">' + esc(str) + '</span>';
2216 i = j;
2217 continue;
2218 }
2219 // Number (including negative)
2220 if (/[0-9]/.test(raw[i]) || (raw[i] === '-' && /[0-9]/.test(raw[i + 1] || ''))) {
2221 let j = i;
2222 if (raw[j] === '-') j++;
2223 while (j < raw.length && /[0-9.eE+-]/.test(raw[j])) j++;
2224 html += '<span style="color:#d19a66">' + esc(raw.slice(i, j)) + '</span>';
2225 i = j;
2226 continue;
2227 }
2228 // Keywords: true / false / null
2229 const kws = [['true', '#c678dd'], ['false', '#c678dd'], ['null', '#c678dd']];
2230 let matched = false;
2231 for (const [kw, col] of kws) {
2232 if (raw.slice(i, i + kw.length) === kw) {
2233 html += '<span style="color:' + col + '">' + kw + '</span>';
2234 i += kw.length;
2235 matched = true;
2236 break;
2237 }
2238 }
2239 if (matched) continue;
2240 // Default character (punctuation / whitespace)
2241 html += esc(raw[i]);
2242 i++;
2243 }
2244 return html;
2245 }
2246
2247 document.querySelectorAll('pre[data-lang="json"]').forEach(function (pre) {
2248 pre.innerHTML = tokenizeJSON(pre.textContent);
2249 });
2250 })();
2251 </script>
2252
2253 </body>
2254 </html>
2255 """
2256
2257
2258 # ---------------------------------------------------------------------------
2259 # Entry point
2260 # ---------------------------------------------------------------------------
2261
2262 if __name__ == "__main__":
2263 import argparse
2264
2265 parser = argparse.ArgumentParser(
2266 description="Generate the Muse domain registry HTML page"
2267 )
2268 parser.add_argument(
2269 "--out",
2270 default=str(_ROOT / "artifacts" / "domain_registry.html"),
2271 help="Output HTML path",
2272 )
2273 args = parser.parse_args()
2274
2275 out_path = pathlib.Path(args.out)
2276 out_path.parent.mkdir(parents=True, exist_ok=True)
2277
2278 print("Generating domain_registry.html...")
2279 render(out_path)
2280 print(f"Open: file://{out_path.resolve()}")