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