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