cgcardona / muse public
render_domain_registry.py python
2281 lines 98.0 KB
39b87a7a fix: prevent ORSet Replica B box from clipping in CRDT grid 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 min-width:0; overflow:hidden;
1347 }
1348 .crdt-rep-hdr { font-family:var(--mono); font-size:9px; font-weight:700; color:var(--dim); text-transform:uppercase; letter-spacing:.6px; }
1349 .crdt-op { font-family:var(--mono); font-size:10px; padding:2px 6px; border-radius:3px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
1350 .crdt-op-add { color:#3fb950; background:rgba(63,185,80,.1); }
1351 .crdt-op-rm { color:#ef5350; background:rgba(239,83,80,.1); }
1352 .crdt-rep-state { font-family:var(--mono); font-size:11px; color:var(--mute); margin-top:2px; }
1353 /* join result row */
1354 .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); }
1355 .crdt-join-label { font-family:var(--mono); font-size:10px; color:var(--dim); white-space:nowrap; }
1356 .crdt-join-val { font-family:var(--mono); font-size:13px; font-weight:700; }
1357 .crdt-join-rule { font-size:10px; color:var(--mute); font-style:italic; margin-left:auto; }
1358 /* LWW timeline */
1359 .crdt-writes { display:flex; flex-direction:column; gap:5px; }
1360 .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; }
1361 .crdt-write-winner { border-color:rgba(88,166,255,.4); background:rgba(88,166,255,.05); }
1362 .crdt-time { font-size:9px; color:var(--dim); min-width:32px; }
1363 .crdt-agent { font-size:9px; font-weight:700; padding:1px 6px; border-radius:3px; min-width:18px; text-align:center; }
1364 .crdt-agent-a { background:rgba(249,168,37,.15); color:#f9a825; }
1365 .crdt-agent-b { background:rgba(88,166,255,.15); color:#58a6ff; }
1366 .crdt-wval { color:var(--text); flex:1; }
1367 .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; }
1368 /* GCounter bars */
1369 .crdt-gcounter { display:flex; flex-direction:column; gap:7px; }
1370 .crdt-gc-row { display:flex; align-items:center; gap:8px; }
1371 .crdt-bar { flex:1; height:22px; background:var(--bg3); border-radius:4px; overflow:hidden; }
1372 .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; }
1373 .crdt-bar-a { background:rgba(249,168,37,.35); color:#f9a825; }
1374 .crdt-bar-b { background:rgba(88,166,255,.35); color:#58a6ff; }
1375 /* VectorClock grid */
1376 .crdt-vclocks { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
1377 .crdt-vc { display:flex; flex-direction:column; gap:4px; }
1378 .crdt-vc-hdr { font-family:var(--mono); font-size:9px; color:var(--dim); text-transform:uppercase; letter-spacing:.5px; }
1379 .crdt-vc-cells { display:flex; gap:4px; }
1380 .crdt-vc-cell { font-family:var(--mono); font-size:10.5px; font-weight:700; padding:4px 8px; border-radius:4px; border:1px solid; }
1381 .crdt-vc-self { color:#f9a825; background:rgba(249,168,37,.1); border-color:rgba(249,168,37,.35); }
1382 .crdt-vc-zero { color:var(--dim); background:var(--bg3); border-color:var(--border); }
1383 .crdt-vc-max { color:#3fb950; background:rgba(63,185,80,.1); border-color:rgba(63,185,80,.35); }
1384 .crdt-vc-sep { font-size:16px; color:var(--dim); padding:0 2px; align-self:center; margin-top:14px; }
1385 .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; }
1386
1387 /* ---- OT Merge scenario cards ---- */
1388 .ot-scenarios { display: flex; flex-direction: column; gap: 10px; }
1389 .ot-scenario {
1390 background: var(--bg);
1391 border: 1px solid var(--border);
1392 border-left: 3px solid transparent;
1393 border-radius: 6px;
1394 padding: 12px 14px;
1395 display: flex;
1396 flex-direction: column;
1397 gap: 9px;
1398 }
1399 .ot-clean { border-left-color: #3fb950; }
1400 .ot-conflict { border-left-color: #ef5350; }
1401 .ot-scenario-hdr { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
1402 .ot-scenario-label {
1403 font-family: var(--mono);
1404 font-size: 9.5px;
1405 font-weight: 700;
1406 text-transform: uppercase;
1407 letter-spacing: 1px;
1408 color: var(--dim);
1409 }
1410 .ot-scenario-title { font-size: 11.5px; color: var(--mute); }
1411 .ot-ops { display: flex; flex-direction: column; gap: 5px; }
1412 .ot-op {
1413 display: flex;
1414 align-items: center;
1415 gap: 7px;
1416 font-family: var(--mono);
1417 font-size: 11.5px;
1418 flex-wrap: wrap;
1419 }
1420 .ot-op-side {
1421 font-size: 9px;
1422 font-weight: 700;
1423 color: var(--dim);
1424 background: var(--bg3);
1425 padding: 1px 6px;
1426 border-radius: 3px;
1427 min-width: 34px;
1428 text-align: center;
1429 }
1430 .ot-op-type { font-weight: 700; padding: 1px 7px; border-radius: 3px; font-size: 10.5px; }
1431 .ot-insert { background: rgba(63,185,80,0.13); color: #3fb950; }
1432 .ot-replace { background: rgba(249,168,37,0.13); color: #f9a825; }
1433 .ot-op-addr { color: #98c379; }
1434 .ot-op-meta { color: var(--dim); font-size: 10.5px; }
1435 .ot-result {
1436 display: flex;
1437 align-items: center;
1438 justify-content: space-between;
1439 flex-wrap: wrap;
1440 gap: 8px;
1441 padding-top: 9px;
1442 border-top: 1px solid var(--border);
1443 }
1444 .ot-reason { font-family: var(--mono); font-size: 11px; color: var(--mute); }
1445 .ot-badge {
1446 display: inline-flex;
1447 align-items: center;
1448 gap: 5px;
1449 font-size: 11px;
1450 font-weight: 700;
1451 padding: 3px 10px;
1452 border-radius: 12px;
1453 white-space: nowrap;
1454 }
1455 .ot-badge .icon { width: 11px; height: 11px; vertical-align: -0.05em; }
1456 .ot-badge-clean { background: rgba(63,185,80,0.1); color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
1457 .ot-badge-conflict { background: rgba(239,83,80,0.1); color: #ef5350; border: 1px solid rgba(239,83,80,0.3); }
1458
1459 .cap-showcase-domain-grid {
1460 display: flex;
1461 flex-direction: column;
1462 gap: 10px;
1463 }
1464 .crdt-mini-grid {
1465 display: grid;
1466 grid-template-columns: 1fr 1fr;
1467 gap: 10px;
1468 }
1469 @media (max-width: 700px) { .crdt-mini-grid { grid-template-columns: 1fr; } }
1470
1471 /* ---- Three steps ---- */
1472 .steps-grid {
1473 display: grid;
1474 grid-template-columns: repeat(3, 1fr);
1475 gap: 24px;
1476 }
1477 @media (max-width: 800px) { .steps-grid { grid-template-columns: 1fr; } }
1478 .step-card {
1479 border: 1px solid var(--border);
1480 border-radius: var(--r);
1481 background: var(--bg2);
1482 padding: 24px;
1483 position: relative;
1484 }
1485 .step-num {
1486 font-family: var(--mono);
1487 font-size: 11px;
1488 color: var(--accent);
1489 font-weight: 700;
1490 text-transform: uppercase;
1491 letter-spacing: 1px;
1492 margin-bottom: 10px;
1493 }
1494 .step-title { font-size: 17px; font-weight: 700; margin-bottom: 8px; }
1495 .step-desc { font-size: 13px; color: var(--mute); line-height: 1.6; margin-bottom: 16px; }
1496 .step-cmd {
1497 font-family: var(--mono);
1498 font-size: 12px;
1499 background: var(--bg3);
1500 border: 1px solid var(--border);
1501 border-radius: 5px;
1502 padding: 10px 14px;
1503 color: var(--accent2);
1504 }
1505
1506 /* ---- Code block ---- */
1507 .code-wrap {
1508 border: 1px solid var(--border);
1509 border-radius: var(--r);
1510 overflow: hidden;
1511 }
1512 .code-bar {
1513 background: var(--bg3);
1514 border-bottom: 1px solid var(--border);
1515 padding: 8px 16px;
1516 display: flex;
1517 align-items: center;
1518 gap: 8px;
1519 }
1520 .code-bar-dot {
1521 width: 10px; height: 10px; border-radius: 50%;
1522 }
1523 .code-bar-title {
1524 font-family: var(--mono);
1525 font-size: 12px;
1526 color: var(--mute);
1527 margin-left: 6px;
1528 }
1529 .code-body {
1530 background: #0a0e14;
1531 padding: 20px 24px;
1532 font-family: var(--mono);
1533 font-size: 12.5px;
1534 line-height: 1.7;
1535 color: #abb2bf;
1536 white-space: pre;
1537 overflow-x: auto;
1538 }
1539 /* Simple syntax highlights */
1540 .kw { color: #c678dd; }
1541 .kw2 { color: #e06c75; }
1542 .fn { color: #61afef; }
1543 .str { color: #98c379; }
1544 .cmt { color: #5c6370; font-style: italic; }
1545 .cls { color: #e5c07b; }
1546 .typ { color: #56b6c2; }
1547
1548 /* ---- Active domains grid ---- */
1549 .domain-grid {
1550 display: grid;
1551 grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
1552 gap: 20px;
1553 }
1554 .domain-card {
1555 border: 1px solid var(--border);
1556 border-radius: var(--r);
1557 background: var(--bg2);
1558 overflow: hidden;
1559 transition: border-color 0.2s, transform 0.15s;
1560 }
1561 .domain-card:hover { border-color: var(--accent); transform: translateY(-2px); }
1562 .domain-card.active-domain { border-color: rgba(63,185,80,0.4); }
1563 .domain-card-hdr {
1564 background: var(--bg3);
1565 padding: 12px 16px;
1566 border-bottom: 1px solid var(--border);
1567 display: flex;
1568 align-items: center;
1569 gap: 10px;
1570 }
1571 .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); }
1572 .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); }
1573 .active-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); margin-left: auto; }
1574 .domain-name-lg { font-family: var(--mono); font-size: 16px; font-weight: 700; color: var(--text); }
1575 .domain-card-body { padding: 16px; }
1576 .domain-desc { font-size: 13px; color: var(--mute); margin-bottom: 12px; }
1577 .cap-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
1578 .cap-pill { font-size: 10px; padding: 2px 8px; border-radius: 12px; border: 1px solid var(--border); color: var(--mute); background: var(--bg3); }
1579 .cap-pill.cap-crdt { border-color: rgba(188,140,255,0.4); color: var(--purple); background: rgba(188,140,255,0.08); }
1580 .cap-pill.cap-ot-merge { border-color: rgba(88,166,255,0.4); color: var(--accent2); background: rgba(88,166,255,0.08); }
1581 .cap-pill.cap-domain-schema { border-color: rgba(63,185,80,0.4); color: var(--green); background: rgba(63,185,80,0.08); }
1582 .cap-pill.cap-typed-deltas { border-color: rgba(249,168,37,0.4); color: #f9a825; background: rgba(249,168,37,0.08); }
1583 .dim-row { font-size: 11px; color: var(--dim); }
1584 .dim-tag { color: var(--mute); }
1585
1586 /* ---- Planned domains ---- */
1587 .planned-grid {
1588 display: grid;
1589 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1590 gap: 16px;
1591 }
1592 .planned-card {
1593 border: 1px solid var(--border);
1594 border-radius: var(--r);
1595 background: var(--bg2);
1596 padding: 20px 16px;
1597 display: flex;
1598 flex-direction: column;
1599 gap: 8px;
1600 transition: border-color 0.2s, transform 0.15s;
1601 }
1602 .planned-card:hover { border-color: var(--card-accent,var(--accent)); transform: translateY(-2px); }
1603 .planned-card.yours { border: 2px dashed var(--accent); background: rgba(79,142,247,0.04); }
1604 .planned-icon { line-height: 0; }
1605 .planned-icon .icon { width: 28px; height: 28px; }
1606 .planned-name { font-size: 15px; font-weight: 700; color: var(--text); }
1607 .planned-tag { font-size: 12px; color: var(--mute); line-height: 1.5; }
1608 .planned-dims { font-size: 10px; color: var(--dim); }
1609 .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; }
1610 .cta-btn {
1611 display: inline-block;
1612 margin-top: 6px;
1613 font-size: 12px;
1614 font-weight: 600;
1615 color: var(--accent2);
1616 border: 1px solid rgba(88,166,255,0.4);
1617 border-radius: 4px;
1618 padding: 4px 12px;
1619 text-decoration: none;
1620 transition: background 0.15s;
1621 }
1622 .cta-btn:hover { background: rgba(88,166,255,0.1); text-decoration: none; }
1623
1624 /* ---- Distribution tiers ---- */
1625 .dist-grid {
1626 display: grid;
1627 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1628 gap: 24px;
1629 }
1630 .dist-card {
1631 border: 1px solid var(--border);
1632 border-top: 3px solid var(--dist-color, var(--accent));
1633 border-radius: var(--r);
1634 background: var(--bg2);
1635 padding: 24px;
1636 transition: transform 0.15s;
1637 }
1638 .dist-card:hover { transform: translateY(-2px); }
1639 .dist-header { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 14px; }
1640 .dist-icon { line-height: 0; flex-shrink: 0; }
1641 .dist-icon .icon { width: 26px; height: 26px; }
1642 .dist-tier { font-family: var(--mono); font-size: 11px; color: var(--dist-color,var(--accent)); letter-spacing: 1px; text-transform: uppercase; font-weight: 700; }
1643 .dist-title { font-size: 14px; font-weight: 600; color: var(--text); margin-top: 2px; }
1644 .dist-desc { font-size: 13px; color: var(--mute); margin-bottom: 16px; line-height: 1.6; }
1645 .dist-steps { list-style: none; counter-reset: step; display: flex; flex-direction: column; gap: 6px; }
1646 .dist-steps li { counter-increment: step; display: flex; align-items: flex-start; gap: 8px; font-size: 12px; color: var(--mute); }
1647 .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; }
1648 .dist-steps code { background: var(--bg3); border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; font-size: 11px; }
1649
1650 /* ---- MuseHub teaser ---- */
1651 .musehub-section {
1652 background: linear-gradient(135deg, #0d1117 0%, #1a0d2e 50%, #0d1117 100%);
1653 padding: 80px 40px;
1654 text-align: center;
1655 border-top: 1px solid var(--border);
1656 }
1657 .musehub-logo {
1658 margin-bottom: 20px;
1659 line-height: 0;
1660 }
1661 .musehub-logo .icon { width: 48px; height: 48px; stroke: #bc8cff; }
1662 .musehub-section h2 {
1663 font-size: 36px;
1664 font-weight: 800;
1665 letter-spacing: -1px;
1666 margin-bottom: 12px;
1667 }
1668 .musehub-section h2 span {
1669 background: linear-gradient(135deg, #bc8cff, #4f8ef7);
1670 -webkit-background-clip: text;
1671 -webkit-text-fill-color: transparent;
1672 background-clip: text;
1673 }
1674 .musehub-desc {
1675 font-size: 16px;
1676 color: var(--mute);
1677 max-width: 560px;
1678 margin: 0 auto 36px;
1679 }
1680 .musehub-desc strong { color: var(--text); }
1681 .musehub-features {
1682 display: flex;
1683 gap: 24px;
1684 justify-content: center;
1685 flex-wrap: wrap;
1686 margin-bottom: 40px;
1687 }
1688 .mh-feature {
1689 background: var(--bg2);
1690 border: 1px solid rgba(188,140,255,0.2);
1691 border-radius: var(--r);
1692 padding: 16px 20px;
1693 text-align: left;
1694 min-width: 180px;
1695 }
1696 .mh-feature-icon { margin-bottom: 10px; line-height: 0; }
1697 .mh-feature-icon .icon { width: 22px; height: 22px; stroke: #bc8cff; }
1698 .mh-feature-title { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
1699 .mh-feature-desc { font-size: 12px; color: var(--mute); }
1700 .musehub-status {
1701 display: inline-flex;
1702 align-items: center;
1703 gap: 8px;
1704 background: rgba(188,140,255,0.1);
1705 border: 1px solid rgba(188,140,255,0.3);
1706 border-radius: 20px;
1707 padding: 8px 20px;
1708 font-size: 13px;
1709 color: var(--purple);
1710 }
1711 .mh-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--purple); animation: pulse 2s ease-in-out infinite; }
1712 @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
1713
1714 /* ---- Footer ---- */
1715 footer {
1716 background: var(--bg2);
1717 border-top: 1px solid var(--border);
1718 padding: 24px 40px;
1719 display: flex;
1720 justify-content: space-between;
1721 align-items: center;
1722 flex-wrap: wrap;
1723 gap: 12px;
1724 font-size: 13px;
1725 color: var(--mute);
1726 }
1727 footer a { color: var(--accent2); }
1728
1729 /* ---- Nav ---- */
1730 nav {
1731 background: var(--bg2);
1732 border-bottom: 1px solid var(--border);
1733 padding: 0 40px;
1734 display: flex;
1735 align-items: center;
1736 gap: 0;
1737 height: 52px;
1738 }
1739 .nav-logo {
1740 font-family: var(--mono);
1741 font-size: 16px;
1742 font-weight: 700;
1743 color: var(--accent2);
1744 margin-right: 32px;
1745 text-decoration: none;
1746 }
1747 .nav-logo:hover { text-decoration: none; }
1748 .nav-link {
1749 font-size: 13px;
1750 color: var(--mute);
1751 padding: 0 14px;
1752 height: 100%;
1753 display: flex;
1754 align-items: center;
1755 border-bottom: 2px solid transparent;
1756 text-decoration: none;
1757 transition: color 0.15s, border-color 0.15s;
1758 }
1759 .nav-link:hover { color: var(--text); text-decoration: none; }
1760 .nav-link.current { color: var(--text); border-bottom-color: var(--accent); }
1761 .nav-spacer { flex: 1; }
1762 .nav-badge {
1763 font-size: 11px;
1764 background: rgba(79,142,247,0.12);
1765 border: 1px solid rgba(79,142,247,0.3);
1766 color: var(--accent2);
1767 border-radius: 4px;
1768 padding: 2px 8px;
1769 font-family: var(--mono);
1770 }
1771 </style>
1772 </head>
1773 <body>
1774
1775 <nav>
1776 <a class="nav-logo" href="#">muse</a>
1777 <a class="nav-link" href="tour_de_force.html">Demo</a>
1778 <a class="nav-link current" href="index.html">Domain Registry</a>
1779 <a class="nav-link" href="../docs/guide/plugin-authoring-guide.md">Plugin Guide</a>
1780 <div class="nav-spacer"></div>
1781 <span class="nav-badge">v0.1.1</span>
1782 </nav>
1783
1784 <!-- =================== HERO =================== -->
1785 <div class="hero">
1786 <h1 class="hero-wordmark">muse</h1>
1787 <div class="hero-version-any">Version Anything</div>
1788 <p class="hero-sub">
1789 One protocol. Any domain. <strong>Six methods</strong> between you and a
1790 complete version control system — branching, merging, conflict resolution,
1791 time-travel, and typed diffs — for free.
1792 </p>
1793 <div class="hero-cta-row">
1794 <a class="btn-primary" href="#build">Build a Domain Plugin</a>
1795 <a class="btn-outline" href="tour_de_force.html">Watch the Demo →</a>
1796 </div>
1797 <div class="domain-ticker">
1798 <div class="ticker-track">
1799 <span class="ticker-item active">{{ICON_MUSIC}} music</span>
1800 <span class="ticker-item">{{ICON_GENOMICS}} genomics</span>
1801 <span class="ticker-item">{{ICON_CUBE}} 3d-spatial</span>
1802 <span class="ticker-item">{{ICON_TRENDING}} financial</span>
1803 <span class="ticker-item">{{ICON_ATOM}} simulation</span>
1804 <span class="ticker-item">{{ICON_ACTIVITY}} proteomics</span>
1805 <span class="ticker-item">{{ICON_PEN_TOOL}} cad</span>
1806 <span class="ticker-item">{{ICON_ZAP}} game-state</span>
1807 <span class="ticker-item">{{ICON_PLUS}} your-domain</span>
1808 <!-- duplicate for seamless loop -->
1809 <span class="ticker-item active">{{ICON_MUSIC}} music</span>
1810 <span class="ticker-item">{{ICON_GENOMICS}} genomics</span>
1811 <span class="ticker-item">{{ICON_CUBE}} 3d-spatial</span>
1812 <span class="ticker-item">{{ICON_TRENDING}} financial</span>
1813 <span class="ticker-item">{{ICON_ATOM}} simulation</span>
1814 <span class="ticker-item">{{ICON_ACTIVITY}} proteomics</span>
1815 <span class="ticker-item">{{ICON_PEN_TOOL}} cad</span>
1816 <span class="ticker-item">{{ICON_ZAP}} game-state</span>
1817 <span class="ticker-item">{{ICON_PLUS}} your-domain</span>
1818 </div>
1819 </div>
1820 </div>
1821
1822 <!-- =================== PROTOCOL =================== -->
1823 <section id="protocol">
1824 <div class="inner">
1825 <div class="section-eyebrow">The Contract</div>
1826 <h2>The MuseDomainPlugin Protocol</h2>
1827 <p class="section-lead">
1828 Every domain — music, genomics, 3D spatial, financial models — implements
1829 the same <strong>six-method protocol</strong>. The core engine handles
1830 everything else: content-addressed storage, DAG, branches, log, merge base,
1831 cherry-pick, revert, stash, tags.
1832 </p>
1833
1834 <div class="proto-layout">
1835 <div class="stat-strip">
1836 <div class="stat-cell"><span class="stat-num">6</span><span class="stat-lbl">methods to implement</span></div>
1837 <div class="stat-cell"><span class="stat-num">14</span><span class="stat-lbl">CLI commands, free</span></div>
1838 <div class="stat-cell"><span class="stat-num">∞</span><span class="stat-lbl">domains possible</span></div>
1839 <div class="stat-cell"><span class="stat-num">0</span><span class="stat-lbl">core changes needed</span></div>
1840 </div>
1841 <div class="proto-table">
1842 <div class="proto-row hdr">
1843 <div class="proto-method">Method</div>
1844 <div class="proto-sig">Signature</div>
1845 <div class="proto-desc">Purpose</div>
1846 </div>
1847 <div class="proto-row">
1848 <div class="proto-method">snapshot</div>
1849 <div class="proto-sig">snapshot(live) → StateSnapshot</div>
1850 <div class="proto-desc">Capture current state as a content-addressable blob</div>
1851 </div>
1852 <div class="proto-row">
1853 <div class="proto-method">diff</div>
1854 <div class="proto-sig">diff(base, target) → StateDelta</div>
1855 <div class="proto-desc">Compute minimal change between two snapshots (added · removed · modified)</div>
1856 </div>
1857 <div class="proto-row">
1858 <div class="proto-method">merge</div>
1859 <div class="proto-sig">merge(base, left, right) → MergeResult</div>
1860 <div class="proto-desc">Three-way reconcile divergent state lines; surface conflicts per dimension</div>
1861 </div>
1862 <div class="proto-row">
1863 <div class="proto-method">drift</div>
1864 <div class="proto-sig">drift(committed, live) → DriftReport</div>
1865 <div class="proto-desc">Detect uncommitted changes between HEAD and working state</div>
1866 </div>
1867 <div class="proto-row">
1868 <div class="proto-method">apply</div>
1869 <div class="proto-sig">apply(delta, live) → LiveState</div>
1870 <div class="proto-desc">Apply a delta during checkout to reconstruct historical state</div>
1871 </div>
1872 <div class="proto-row">
1873 <div class="proto-method">schema</div>
1874 <div class="proto-sig">schema() → DomainSchema</div>
1875 <div class="proto-desc">Declare data structure — drives diff algorithm selection per dimension</div>
1876 </div>
1877 </div>
1878 </div>
1879 </div>
1880 </section>
1881
1882 {{DIFF_ALGEBRA}}
1883
1884 <!-- =================== ENGINE CAPABILITIES =================== -->
1885 <section id="capabilities" style="background:var(--bg2)">
1886 <div class="inner">
1887 <div class="section-eyebrow">Engine Capabilities</div>
1888 <h2>What Every Plugin Gets for Free</h2>
1889 <p class="section-lead">
1890 The core engine provides four advanced capabilities that any domain plugin
1891 can opt into. Implement the protocol — the engine does the rest.
1892 </p>
1893
1894 <div class="cap-showcase-grid">
1895
1896 <div class="cap-showcase-card" style="--cap-color:#f9a825">
1897 <div class="cap-showcase-header">
1898 <span class="cap-showcase-badge" style="color:#f9a825;background:#f9a82515;border-color:#f9a82540">
1899 {{ICON_CODE}} Typed Delta Algebra
1900 </span>
1901 <span class="cap-showcase-sub">StructuredDelta — every change is a typed operation</span>
1902 </div>
1903 <div class="cap-showcase-body">
1904 <p class="cap-showcase-desc">
1905 Unlike Git's blob diffs, Muse deltas are <strong>typed objects</strong>:
1906 <code>InsertOp</code>, <code>ReplaceOp</code>, <code>DeleteOp</code> — each
1907 carrying the address, before/after hashes, and affected dimensions.
1908 Machine-readable with <code>muse show --json</code>.
1909 </p>
1910 <pre class="cap-showcase-output" data-lang="json">{{TYPED_DELTA_EXAMPLE}}</pre>
1911 </div>
1912 </div>
1913
1914 <div class="cap-showcase-card" style="--cap-color:#58a6ff">
1915 <div class="cap-showcase-header">
1916 <span class="cap-showcase-badge" style="color:#58a6ff;background:#58a6ff15;border-color:#58a6ff40">
1917 {{ICON_LAYERS}} Domain Schema
1918 </span>
1919 <span class="cap-showcase-sub">Per-domain dimensions drive diff algorithm selection</span>
1920 </div>
1921 <div class="cap-showcase-body">
1922 <p class="cap-showcase-desc">
1923 Each plugin's <code>schema()</code> method declares its dimensions and merge mode.
1924 The engine uses this to select the right diff algorithm per dimension and to
1925 surface only the dimensions that actually conflict.
1926 </p>
1927 <div class="cap-showcase-domain-grid" id="schema-domain-grid">
1928 {{ACTIVE_DOMAINS}}
1929 </div>
1930 </div>
1931 </div>
1932
1933 <div class="cap-showcase-card" style="--cap-color:#ef5350">
1934 <div class="cap-showcase-header">
1935 <span class="cap-showcase-badge" style="color:#ef5350;background:#ef535015;border-color:#ef535040">
1936 {{ICON_GIT_MERGE}} OT Merge
1937 </span>
1938 <span class="cap-showcase-sub">Operational transformation — independent ops commute automatically</span>
1939 </div>
1940 <div class="cap-showcase-body">
1941 <p class="cap-showcase-desc">
1942 Plugins implementing <strong>StructuredMergePlugin</strong> get operational
1943 transformation. Operations at different addresses commute automatically —
1944 only operations on the same address with incompatible intent surface a conflict.
1945 </p>
1946 <div class="ot-scenarios">
1947
1948 <div class="ot-scenario ot-clean">
1949 <div class="ot-scenario-hdr">
1950 <span class="ot-scenario-label">Scenario A</span>
1951 <span class="ot-scenario-title">Independent ops at different addresses</span>
1952 </div>
1953 <div class="ot-ops">
1954 <div class="ot-op">
1955 <span class="ot-op-side">left</span>
1956 <span class="ot-op-type ot-insert">InsertOp</span>
1957 <span class="ot-op-addr">"ot-notes-a.mid"</span>
1958 <span class="ot-op-meta">tick=0 · C4 E4 G4</span>
1959 </div>
1960 <div class="ot-op">
1961 <span class="ot-op-side">right</span>
1962 <span class="ot-op-type ot-insert">InsertOp</span>
1963 <span class="ot-op-addr">"ot-notes-b.mid"</span>
1964 <span class="ot-op-meta">tick=480 · D4 F4 A4</span>
1965 </div>
1966 </div>
1967 <div class="ot-result">
1968 <span class="ot-reason">transform → no overlap → ops commute</span>
1969 <span class="ot-badge ot-badge-clean">{{ICON_CHECK_CIRCLE}} Clean merge · both files applied</span>
1970 </div>
1971 </div>
1972
1973 <div class="ot-scenario ot-conflict">
1974 <div class="ot-scenario-hdr">
1975 <span class="ot-scenario-label">Scenario B</span>
1976 <span class="ot-scenario-title">Same address, conflicting musical intent</span>
1977 </div>
1978 <div class="ot-ops">
1979 <div class="ot-op">
1980 <span class="ot-op-side">left</span>
1981 <span class="ot-op-type ot-replace">ReplaceOp</span>
1982 <span class="ot-op-addr">"shared-melody.mid"</span>
1983 <span class="ot-op-meta">C4 E4 G4 · major triad</span>
1984 </div>
1985 <div class="ot-op">
1986 <span class="ot-op-side">right</span>
1987 <span class="ot-op-type ot-replace">ReplaceOp</span>
1988 <span class="ot-op-addr">"shared-melody.mid"</span>
1989 <span class="ot-op-meta">C4 Eb4 G4 · minor triad</span>
1990 </div>
1991 </div>
1992 <div class="ot-result">
1993 <span class="ot-reason">transform → same address · non-commuting content</span>
1994 <span class="ot-badge ot-badge-conflict">{{ICON_X_CIRCLE}} Conflict · human resolves</span>
1995 </div>
1996 </div>
1997
1998 </div>
1999 </div>
2000 </div>
2001
2002 <div class="cap-showcase-card" style="--cap-color:#bc8cff">
2003 <div class="cap-showcase-header">
2004 <span class="cap-showcase-badge" style="color:#bc8cff;background:#bc8cff15;border-color:#bc8cff40">
2005 {{ICON_ZAP}} CRDT Primitives
2006 </span>
2007 <span class="cap-showcase-sub">Convergent merge — any two replicas always reach the same state</span>
2008 </div>
2009 <div class="cap-showcase-body">
2010 <p class="cap-showcase-desc">
2011 Plugins implementing <strong>CRDTPlugin</strong> get four battle-tested
2012 convergent data structures. No coordination required between replicas.
2013 </p>
2014 <div class="crdt-mini-grid">
2015 {{CRDT_CARDS}}
2016 </div>
2017 </div>
2018 </div>
2019
2020 </div>
2021 </div>
2022 </section>
2023
2024 <!-- =================== BUILD =================== -->
2025 <section id="build" style="background:var(--bg)">
2026 <div class="inner">
2027 <div class="section-eyebrow">Build</div>
2028 <h2>Build in Three Steps</h2>
2029 <p class="section-lead">
2030 One command scaffolds the entire plugin skeleton. You fill in six methods.
2031 The full VCS follows.
2032 </p>
2033
2034 <div class="steps-grid">
2035 <div class="step-card">
2036 <div class="step-num">Step 1 · Scaffold</div>
2037 <div class="step-title">Generate the skeleton</div>
2038 <div class="step-desc">
2039 One command creates the plugin directory, class, and all six method stubs
2040 with full type annotations.
2041 </div>
2042 <div class="step-cmd">muse domains --new genomics</div>
2043 </div>
2044 <div class="step-card">
2045 <div class="step-num">Step 2 · Implement</div>
2046 <div class="step-title">Fill in the six methods</div>
2047 <div class="step-desc">
2048 Replace each <code>raise NotImplementedError</code> with your domain's
2049 snapshot, diff, merge, drift, apply, and schema logic.
2050 </div>
2051 <div class="step-cmd">vim muse/plugins/genomics/plugin.py</div>
2052 </div>
2053 <div class="step-card">
2054 <div class="step-num">Step 3 · Use</div>
2055 <div class="step-title">Full VCS, instantly</div>
2056 <div class="step-desc">
2057 Register in <code>registry.py</code>, then every Muse command works
2058 for your domain out of the box.
2059 </div>
2060 <div class="step-cmd">muse init --domain genomics</div>
2061 </div>
2062 </div>
2063 </div>
2064 </section>
2065
2066 <!-- =================== CODE =================== -->
2067 <section id="code">
2068 <div class="inner">
2069 <div class="section-eyebrow">The Scaffold</div>
2070 <h2>What <code>muse domains --new genomics</code> produces</h2>
2071 <p class="section-lead">
2072 A fully typed, immediately runnable plugin skeleton. Every method has the
2073 correct signature. You replace the stubs — the protocol does the rest.
2074 </p>
2075 <div class="code-wrap">
2076 <div class="code-bar">
2077 <div class="code-bar-dot" style="background:#ff5f57"></div>
2078 <div class="code-bar-dot" style="background:#febc2e"></div>
2079 <div class="code-bar-dot" style="background:#28c840"></div>
2080 <span class="code-bar-title">muse/plugins/genomics/plugin.py</span>
2081 </div>
2082 <div class="code-body">{{SCAFFOLD_SNIPPET}}</div>
2083 </div>
2084 <p style="margin-top:16px;font-size:13px;color:var(--mute)">
2085 Full walkthrough →
2086 <a href="../docs/guide/plugin-authoring-guide.md">docs/guide/plugin-authoring-guide.md</a>
2087 · CRDT extension →
2088 <a href="../docs/guide/crdt-reference.md">docs/guide/crdt-reference.md</a>
2089 </p>
2090 </div>
2091 </section>
2092
2093 <!-- =================== ACTIVE DOMAINS =================== -->
2094 <section id="registry" style="background:var(--bg2)">
2095 <div class="inner">
2096 <div class="section-eyebrow">Registry</div>
2097 <h2>Registered Domains</h2>
2098 <p class="section-lead">
2099 Domains currently registered in this Muse instance. The active domain
2100 is the one used when you run <code>muse commit</code>, <code>muse diff</code>,
2101 and all other commands.
2102 </p>
2103 <div class="domain-grid">
2104 {{ACTIVE_DOMAINS}}
2105 </div>
2106 </div>
2107 </section>
2108
2109 <!-- =================== PLANNED ECOSYSTEM =================== -->
2110 <section id="ecosystem">
2111 <div class="inner">
2112 <div class="section-eyebrow">Ecosystem</div>
2113 <h2>The Plugin Ecosystem</h2>
2114 <p class="section-lead">
2115 Music is the reference implementation. These are the domains planned
2116 next — and the slot waiting for yours.
2117 </p>
2118 <div class="planned-grid">
2119 {{PLANNED_DOMAINS}}
2120 </div>
2121 </div>
2122 </section>
2123
2124 <!-- =================== DISTRIBUTION =================== -->
2125 <section id="distribute" style="background:var(--bg2)">
2126 <div class="inner">
2127 <div class="section-eyebrow">Distribution</div>
2128 <h2>How to Share Your Plugin</h2>
2129 <p class="section-lead">
2130 Three tiers of distribution — from local prototype to globally searchable
2131 registry. Start local, publish when ready.
2132 </p>
2133 <div class="dist-grid">
2134 {{DIST_CARDS}}
2135 </div>
2136 </div>
2137 </section>
2138
2139 <!-- =================== MUSEHUB TEASER =================== -->
2140 <div class="musehub-section">
2141 <div class="musehub-logo">{{ICON_GLOBE}}</div>
2142 <h2><span>MuseHub</span> is coming</h2>
2143 <p class="musehub-desc">
2144 A <strong>centralized, searchable registry</strong> for Muse domain plugins —
2145 think npm or crates.io, but for any multidimensional versioned state.
2146 One command to publish. One command to install.
2147 </p>
2148 <div class="musehub-features">
2149 <div class="mh-feature">
2150 <div class="mh-feature-icon">{{ICON_SEARCH}}</div>
2151 <div class="mh-feature-title">Searchable</div>
2152 <div class="mh-feature-desc">Find plugins by domain, capability, or keyword</div>
2153 </div>
2154 <div class="mh-feature">
2155 <div class="mh-feature-icon">{{ICON_PACKAGE}}</div>
2156 <div class="mh-feature-title">Versioned</div>
2157 <div class="mh-feature-desc">Semantic versioning, pinned installs, changelogs</div>
2158 </div>
2159 <div class="mh-feature">
2160 <div class="mh-feature-icon">{{ICON_LOCK}}</div>
2161 <div class="mh-feature-title">Private registries</div>
2162 <div class="mh-feature-desc">Self-host for enterprise or research teams</div>
2163 </div>
2164 <div class="mh-feature">
2165 <div class="mh-feature-icon">{{ICON_ZAP}}</div>
2166 <div class="mh-feature-title">One command</div>
2167 <div class="mh-feature-desc"><code>muse init --domain @musehub/genomics</code></div>
2168 </div>
2169 </div>
2170 <div class="musehub-status">
2171 <div class="mh-dot"></div>
2172 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>
2173 </div>
2174 </div>
2175
2176 <footer>
2177 <span>Muse v0.1.1 · domain-agnostic version control for multidimensional state</span>
2178 <span>
2179 <a href="tour_de_force.html">Demo</a> ·
2180 <a href="https://github.com/cgcardona/muse">GitHub</a> ·
2181 <a href="../docs/guide/plugin-authoring-guide.md">Plugin Guide</a>
2182 </span>
2183 </footer>
2184
2185 <script>
2186 (function () {
2187 function esc(s) {
2188 return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2189 }
2190
2191 function tokenizeJSON(raw) {
2192 let html = '';
2193 let i = 0;
2194 while (i < raw.length) {
2195 // Comment line starting with #
2196 if (raw[i] === '#') {
2197 const end = raw.indexOf('\n', i);
2198 const line = end === -1 ? raw.slice(i) : raw.slice(i, end);
2199 html += '<span style="color:#5c6370;font-style:italic">' + esc(line) + '</span>';
2200 i += line.length;
2201 continue;
2202 }
2203 // String literal
2204 if (raw[i] === '"') {
2205 let j = i + 1;
2206 while (j < raw.length && raw[j] !== '"') {
2207 if (raw[j] === '\\') j++;
2208 j++;
2209 }
2210 j++;
2211 const str = raw.slice(i, j);
2212 // Peek past whitespace — key if followed by ':'
2213 let k = j;
2214 while (k < raw.length && (raw[k] === ' ' || raw[k] === '\t')) k++;
2215 const color = raw[k] === ':' ? '#61afef' : '#98c379';
2216 html += '<span style="color:' + color + '">' + esc(str) + '</span>';
2217 i = j;
2218 continue;
2219 }
2220 // Number (including negative)
2221 if (/[0-9]/.test(raw[i]) || (raw[i] === '-' && /[0-9]/.test(raw[i + 1] || ''))) {
2222 let j = i;
2223 if (raw[j] === '-') j++;
2224 while (j < raw.length && /[0-9.eE+-]/.test(raw[j])) j++;
2225 html += '<span style="color:#d19a66">' + esc(raw.slice(i, j)) + '</span>';
2226 i = j;
2227 continue;
2228 }
2229 // Keywords: true / false / null
2230 const kws = [['true', '#c678dd'], ['false', '#c678dd'], ['null', '#c678dd']];
2231 let matched = false;
2232 for (const [kw, col] of kws) {
2233 if (raw.slice(i, i + kw.length) === kw) {
2234 html += '<span style="color:' + col + '">' + kw + '</span>';
2235 i += kw.length;
2236 matched = true;
2237 break;
2238 }
2239 }
2240 if (matched) continue;
2241 // Default character (punctuation / whitespace)
2242 html += esc(raw[i]);
2243 i++;
2244 }
2245 return html;
2246 }
2247
2248 document.querySelectorAll('pre[data-lang="json"]').forEach(function (pre) {
2249 pre.innerHTML = tokenizeJSON(pre.textContent);
2250 });
2251 })();
2252 </script>
2253
2254 </body>
2255 </html>
2256 """
2257
2258
2259 # ---------------------------------------------------------------------------
2260 # Entry point
2261 # ---------------------------------------------------------------------------
2262
2263 if __name__ == "__main__":
2264 import argparse
2265
2266 parser = argparse.ArgumentParser(
2267 description="Generate the Muse domain registry HTML page"
2268 )
2269 parser.add_argument(
2270 "--out",
2271 default=str(_ROOT / "artifacts" / "domain_registry.html"),
2272 help="Output HTML path",
2273 )
2274 args = parser.parse_args()
2275
2276 out_path = pathlib.Path(args.out)
2277 out_path.parent.mkdir(parents=True, exist_ok=True)
2278
2279 print("Generating domain_registry.html...")
2280 render(out_path)
2281 print(f"Open: file://{out_path.resolve()}")