cgcardona / muse public
render_index.py python
363 lines 11.0 KB
b7f3f7bd feat: add GitHub Pages deployment — landing page + CI build workflow (#30) Gabriel Cardona <cgcardona@gmail.com> 2d ago
1 #!/usr/bin/env python3
2 """Muse — landing page generator.
3
4 Produces artifacts/index.html: a single entry point that presents both
5 the Tour de Force demo and the Domain Registry to visitors.
6
7 Stand-alone usage
8 -----------------
9 python tools/render_index.py
10 python tools/render_index.py --out artifacts/index.html
11 """
12 from __future__ import annotations
13
14 import pathlib
15
16 _ROOT = pathlib.Path(__file__).resolve().parent.parent
17 _DEFAULT = _ROOT / "artifacts" / "index.html"
18
19 _HTML = """\
20 <!DOCTYPE html>
21 <html lang="en">
22 <head>
23 <meta charset="utf-8">
24 <meta name="viewport" content="width=device-width, initial-scale=1">
25 <title>Muse — Version Anything</title>
26 <style>
27 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
28 :root {
29 --bg: #0d1117;
30 --bg2: #161b22;
31 --bg3: #21262d;
32 --border: #30363d;
33 --text: #e6edf3;
34 --mute: #8b949e;
35 --dim: #484f58;
36 --accent: #4f8ef7;
37 --a2: #58a6ff;
38 --green: #3fb950;
39 --purple: #bc8cff;
40 --mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
41 --ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
42 --r: 10px;
43 }
44 html { scroll-behavior: smooth; }
45 body {
46 background: var(--bg);
47 color: var(--text);
48 font-family: var(--ui);
49 min-height: 100vh;
50 display: flex;
51 flex-direction: column;
52 }
53 a { color: var(--a2); text-decoration: none; }
54 a:hover { text-decoration: underline; }
55
56 /* ---- Nav ---- */
57 nav {
58 display: flex;
59 align-items: center;
60 padding: 0 40px;
61 height: 52px;
62 border-bottom: 1px solid var(--border);
63 background: var(--bg2);
64 gap: 24px;
65 }
66 .nav-logo {
67 font-family: var(--mono);
68 font-size: 17px;
69 font-weight: 700;
70 color: var(--a2);
71 letter-spacing: -0.5px;
72 }
73 .nav-link { font-size: 13px; color: var(--mute); transition: color 0.15s; }
74 .nav-link:hover { color: var(--text); text-decoration: none; }
75 .nav-spacer { flex: 1; }
76 .nav-badge {
77 font-family: var(--mono);
78 font-size: 11px;
79 background: rgba(79,142,247,0.1);
80 border: 1px solid rgba(79,142,247,0.3);
81 color: var(--a2);
82 border-radius: 4px;
83 padding: 2px 8px;
84 }
85
86 /* ---- Hero ---- */
87 .hero {
88 flex: 1;
89 display: flex;
90 flex-direction: column;
91 align-items: center;
92 justify-content: center;
93 text-align: center;
94 padding: 80px 40px 60px;
95 position: relative;
96 overflow: hidden;
97 }
98 .hero::before {
99 content: '';
100 position: absolute;
101 inset: 0;
102 background:
103 radial-gradient(ellipse 55% 45% at 25% 55%, rgba(79,142,247,0.08) 0%, transparent 70%),
104 radial-gradient(ellipse 45% 40% at 75% 45%, rgba(188,140,255,0.07) 0%, transparent 70%);
105 pointer-events: none;
106 }
107 .hero-eyebrow {
108 font-family: var(--mono);
109 font-size: 11px;
110 color: var(--a2);
111 letter-spacing: 2.5px;
112 text-transform: uppercase;
113 margin-bottom: 22px;
114 opacity: 0.85;
115 }
116 .hero h1 {
117 font-size: clamp(52px, 8vw, 88px);
118 font-weight: 800;
119 letter-spacing: -3px;
120 line-height: 1;
121 margin-bottom: 20px;
122 font-family: var(--mono);
123 }
124 .hero h1 .grad {
125 background: linear-gradient(135deg, #4f8ef7 0%, #bc8cff 100%);
126 -webkit-background-clip: text;
127 -webkit-text-fill-color: transparent;
128 background-clip: text;
129 }
130 .hero-sub {
131 font-size: clamp(16px, 2.5vw, 20px);
132 color: var(--mute);
133 max-width: 560px;
134 line-height: 1.65;
135 margin-bottom: 48px;
136 }
137 .hero-sub strong { color: var(--text); }
138
139 /* ---- Cards ---- */
140 .cards {
141 display: grid;
142 grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
143 gap: 24px;
144 max-width: 800px;
145 width: 100%;
146 position: relative;
147 }
148 .card {
149 border: 1px solid var(--border);
150 border-radius: var(--r);
151 background: var(--bg2);
152 padding: 32px 28px;
153 text-align: left;
154 display: flex;
155 flex-direction: column;
156 gap: 12px;
157 transition: border-color 0.2s, transform 0.15s, box-shadow 0.2s;
158 text-decoration: none;
159 color: inherit;
160 }
161 .card:hover {
162 border-color: var(--accent);
163 transform: translateY(-3px);
164 box-shadow: 0 8px 32px rgba(79,142,247,0.12);
165 text-decoration: none;
166 }
167 .card.registry:hover {
168 border-color: var(--purple);
169 box-shadow: 0 8px 32px rgba(188,140,255,0.12);
170 }
171 .card-icon { font-size: 36px; line-height: 1; }
172 .card-eyebrow {
173 font-family: var(--mono);
174 font-size: 10px;
175 font-weight: 700;
176 letter-spacing: 1.5px;
177 text-transform: uppercase;
178 color: var(--accent);
179 }
180 .card.registry .card-eyebrow { color: var(--purple); }
181 .card-title {
182 font-size: 22px;
183 font-weight: 700;
184 letter-spacing: -0.4px;
185 color: var(--text);
186 }
187 .card-desc { font-size: 14px; color: var(--mute); line-height: 1.65; }
188 .card-desc strong { color: var(--text); }
189 .card-cta {
190 margin-top: auto;
191 display: inline-flex;
192 align-items: center;
193 gap: 6px;
194 font-size: 13px;
195 font-weight: 600;
196 color: var(--accent);
197 padding-top: 8px;
198 border-top: 1px solid var(--border);
199 }
200 .card.registry .card-cta { color: var(--purple); }
201 .card-pills {
202 display: flex;
203 flex-wrap: wrap;
204 gap: 6px;
205 }
206 .pill {
207 font-size: 10px;
208 padding: 2px 8px;
209 border-radius: 12px;
210 border: 1px solid var(--border);
211 color: var(--mute);
212 background: var(--bg3);
213 }
214 .pill.blue { border-color: rgba(79,142,247,0.3); color: var(--a2); background: rgba(79,142,247,0.07); }
215 .pill.green { border-color: rgba(63,185,80,0.3); color: var(--green); background: rgba(63,185,80,0.07); }
216 .pill.purple { border-color: rgba(188,140,255,0.3); color: var(--purple);background: rgba(188,140,255,0.07); }
217
218 /* ---- Feature strip ---- */
219 .features {
220 display: flex;
221 flex-wrap: wrap;
222 justify-content: center;
223 gap: 32px;
224 padding: 40px;
225 border-top: 1px solid var(--border);
226 background: var(--bg2);
227 }
228 .feature {
229 display: flex;
230 align-items: center;
231 gap: 10px;
232 font-size: 13px;
233 color: var(--mute);
234 }
235 .feature-dot {
236 width: 6px;
237 height: 6px;
238 border-radius: 50%;
239 background: var(--accent);
240 flex-shrink: 0;
241 }
242
243 /* ---- Footer ---- */
244 footer {
245 background: var(--bg2);
246 border-top: 1px solid var(--border);
247 padding: 20px 40px;
248 display: flex;
249 justify-content: space-between;
250 align-items: center;
251 flex-wrap: wrap;
252 gap: 12px;
253 font-size: 12px;
254 color: var(--dim);
255 }
256 footer a { color: var(--a2); }
257 </style>
258 </head>
259 <body>
260
261 <nav>
262 <span class="nav-logo">muse</span>
263 <a class="nav-link" href="tour_de_force.html">Tour de Force</a>
264 <a class="nav-link" href="domain_registry.html">Domain Registry</a>
265 <div class="nav-spacer"></div>
266 <a class="nav-link" href="https://github.com/cgcardona/muse">GitHub</a>
267 <span class="nav-badge">v0.1.1</span>
268 </nav>
269
270 <div class="hero">
271 <div class="hero-eyebrow">Domain-agnostic version control</div>
272 <h1><span class="grad">muse</span></h1>
273 <p class="hero-sub">
274 Version control for <strong>any multidimensional state</strong>.
275 The same DAG, branching, merging, and conflict resolution that powers
276 music — applied to genomics, 3D spatial fields, scientific simulation,
277 or whatever you build next.
278 </p>
279
280 <div class="cards">
281 <a class="card" href="tour_de_force.html">
282 <div class="card-icon">🎵</div>
283 <div class="card-eyebrow">Interactive Demo</div>
284 <div class="card-title">Tour de Force</div>
285 <div class="card-desc">
286 Watch Muse version a real music project — five acts covering
287 <strong>commits, branches, merges, conflict resolution</strong>,
288 cherry-pick, stash, revert, and tags. Every operation is live,
289 every commit real.
290 </div>
291 <div class="card-pills">
292 <span class="pill blue">Animated DAG</span>
293 <span class="pill blue">5 Acts</span>
294 <span class="pill blue">41 Operations</span>
295 <span class="pill blue">Dimension Matrix</span>
296 </div>
297 <div class="card-cta">Open Tour de Force →</div>
298 </a>
299
300 <a class="card registry" href="domain_registry.html">
301 <div class="card-icon">🌐</div>
302 <div class="card-eyebrow">Plugin Ecosystem</div>
303 <div class="card-title">Domain Registry</div>
304 <div class="card-desc">
305 Build your own domain plugin. The
306 <strong>six-method MuseDomainPlugin protocol</strong> gives you
307 the full VCS for free — typed deltas, OT merge, CRDT primitives,
308 domain schema. One command to scaffold.
309 </div>
310 <div class="card-pills">
311 <span class="pill purple">6-Method Protocol</span>
312 <span class="pill purple">OT Merge</span>
313 <span class="pill purple">CRDT Primitives</span>
314 <span class="pill purple">MuseHub Roadmap</span>
315 </div>
316 <div class="card-cta">Open Domain Registry →</div>
317 </a>
318 </div>
319 </div>
320
321 <div class="features">
322 <div class="feature"><div class="feature-dot"></div>Content-addressed object store</div>
323 <div class="feature"><div class="feature-dot"></div>Commit DAG with full history</div>
324 <div class="feature"><div class="feature-dot" style="background:var(--green)"></div>Typed delta algebra</div>
325 <div class="feature"><div class="feature-dot" style="background:var(--green)"></div>Per-dimension merge & conflict</div>
326 <div class="feature"><div class="feature-dot" style="background:var(--purple)"></div>OT merge (StructuredMergePlugin)</div>
327 <div class="feature"><div class="feature-dot" style="background:var(--purple)"></div>CRDT primitives (CRDTPlugin)</div>
328 <div class="feature"><div class="feature-dot"></div>14 CLI commands · zero dependencies</div>
329 </div>
330
331 <footer>
332 <span>Muse v0.1.1 · Python 3.12 · MIT License</span>
333 <span>
334 <a href="https://github.com/cgcardona/muse">github.com/cgcardona/muse</a>
335 &nbsp;·&nbsp;
336 <a href="https://github.com/cgcardona/muse/blob/main/docs/guide/plugin-authoring-guide.md">Plugin Guide</a>
337 </span>
338 </footer>
339
340 </body>
341 </html>
342 """
343
344
345 def render(output_path: pathlib.Path) -> None:
346 """Write the landing page."""
347 output_path.write_text(_HTML, encoding="utf-8")
348 size_kb = output_path.stat().st_size // 1024
349 print(f" Landing page written ({size_kb}KB) → {output_path}")
350
351
352 if __name__ == "__main__":
353 import argparse
354
355 parser = argparse.ArgumentParser(description="Generate Muse landing index.html")
356 parser.add_argument("--out", default=str(_DEFAULT), help="Output path")
357 args = parser.parse_args()
358
359 out = pathlib.Path(args.out)
360 out.parent.mkdir(parents=True, exist_ok=True)
361 print("Generating index.html...")
362 render(out)
363 print(f"Open: file://{out.resolve()}")