GauravGosain commited on
Commit
f9fb2c5
·
verified ·
1 Parent(s): 5ad919d

Deploy Small Talk: Reachy Mini AI podcast (gradio.Server + three.js + LiveKit)

Browse files
backend/__pycache__/server.cpython-313.pyc CHANGED
Binary files a/backend/__pycache__/server.cpython-313.pyc and b/backend/__pycache__/server.cpython-313.pyc differ
 
backend/__pycache__/showgen.cpython-313.pyc CHANGED
Binary files a/backend/__pycache__/showgen.cpython-313.pyc and b/backend/__pycache__/showgen.cpython-313.pyc differ
 
backend/server.py CHANGED
@@ -70,6 +70,7 @@ class Room:
70
  topic: str
71
  emoji: str
72
  template: str # 'duo' | 'groupchat' | 'open'
 
73
 
74
 
75
  ROOMS: dict[str, Room] = {
@@ -102,6 +103,7 @@ def _room_dict(r: Room) -> dict:
102
  return {
103
  "id": r.id, "title": r.title, "topic": r.topic, "emoji": r.emoji,
104
  "template": r.template, "cast": _cast_preview(r.template),
 
105
  "live": r.id in ROOM_TASKS and not ROOM_TASKS[r.id].done(),
106
  }
107
 
@@ -170,11 +172,41 @@ async def _shutdown_room(room: Room, *, remove: bool | None = None) -> None:
170
  ROOMS.pop(room.id, None)
171
 
172
 
173
- async def _run_generated_show(room: Room) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  """The live LLM+TTS cascade (the original project idea, end to end):
175
- one structured LLM call writes the cast + speaker→dialogue script, then each
176
- line is voiced by Qwen3-TTS with line N+1's audio generating while line N
177
- playsand streamed into the LiveKit room with subtitle data messages."""
 
178
  pubs = ROOM_PUBS.setdefault(room.id, {})
179
  # a hidden stage manager joins first so the audience gets real progress
180
  # while the script generates (the frontend hides 'stage-' participants)
@@ -184,7 +216,7 @@ async def _run_generated_show(room: Room) -> None:
184
  pubs[stage.identity] = stage
185
  await stage.send_data({"type": "status", "text": "the writers’ room is drafting the script…"})
186
 
187
- script = await showgen.write_script(room.title, room.topic)
188
  speakers = {s["id"]: s for s in script["speakers"]}
189
  names = " · ".join(s["name"] for s in script["speakers"])
190
  await stage.send_data({"type": "status", "text": f"cast locked: {names} — designing their voices…"})
@@ -192,9 +224,14 @@ async def _run_generated_show(room: Room) -> None:
192
  for s in script["speakers"]:
193
  ident = f"gen-{room.id}-{s['id']}"
194
  if ident not in pubs:
 
 
 
 
 
195
  pub = ReachyPublisher(
196
  ident, s["name"], room=room.id, color=s["color"], persona=s["persona"],
197
- props={k: s[k] for k in ("hat", "face", "neck") if s.get(k)},
198
  )
199
  await pub.connect()
200
  pubs[ident] = pub
@@ -277,9 +314,7 @@ async def _ensure_conversation(room: Room) -> None:
277
  return
278
  cast = CAST_TEMPLATES.get(room.template)
279
  if not cast:
280
- # 'open' rooms with a topic get a live generated show (LLM+TTS cascade)
281
- if room.topic and config.MODAL_LLM_URL and config.MODAL_TTS_URL:
282
- ROOM_TASKS[room.id] = asyncio.create_task(_run_generated_show(room))
283
  return
284
  if not any(config.SAMPLES_DIR.glob("*.wav")):
285
  raise HTTPException(400, "no voice clips found (run scripts/make-qwen-samples.py)")
@@ -341,6 +376,7 @@ class TokenRequest(BaseModel):
341
  device: bool = False
342
  color: str | None = None
343
  persona: str | None = None
 
344
  bodyColor: str | None = None
345
  hat: str | None = None
346
  face: str | None = None
@@ -383,18 +419,31 @@ async def create_room(req: CreateRoomRequest):
383
  if req.template not in ("duo", "groupchat", "open"):
384
  raise HTTPException(400, "bad template")
385
  rid = f"{_slug(req.title)}-{uuid.uuid4().hex[:4]}"
 
386
  ROOMS[rid] = Room(rid, req.title.strip() or "Untitled room", req.topic.strip(),
387
- req.emoji or "💬", req.template)
 
 
388
  return {"ok": True, "id": rid, "room": _room_dict(ROOMS[rid])}
389
 
390
 
 
 
 
 
 
 
 
 
 
 
391
  async def post_token(req: TokenRequest):
392
  metadata = None
393
  if req.device:
394
  identity = f"reachy-device-{_slug(req.name or 'reachy')}-{uuid.uuid4().hex[:4]}"
395
  metadata = {"role": "reachy", "device": True,
396
  "color": req.color or "#49e6c8", "persona": req.persona or "a real Reachy Mini, live"}
397
- for k in ("bodyColor", "hat", "face", "neck"):
398
  if getattr(req, k):
399
  metadata[k] = getattr(req, k)
400
  else:
@@ -412,6 +461,25 @@ async def join_room(room_id: str):
412
  return {"ok": True, "room": _room_dict(room)}
413
 
414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  async def add_reachy(room_id: str, req: ReachyRequest):
416
  """Drop a user-configured Reachy into the room. It joins and reacts; live
417
  speech needs the on-device brain + TTS, so its track stays silent for now."""
@@ -605,6 +673,7 @@ def attach(app) -> None:
605
  app.add_api_route("/api/rooms", list_rooms, methods=["GET"])
606
  app.add_api_route("/api/rooms", create_room, methods=["POST"])
607
  app.add_api_route("/api/rooms/{room_id}/join", join_room, methods=["POST"])
 
608
  app.add_api_route("/api/rooms/{room_id}/reachy", add_reachy, methods=["POST"])
609
  app.add_api_route("/api/rooms/{room_id}/reachy/leave", remove_reachy, methods=["POST"])
610
  app.add_api_route("/api/token", post_token, methods=["POST"])
 
70
  topic: str
71
  emoji: str
72
  template: str # 'duo' | 'groupchat' | 'open'
73
+ status: str = "live" # 'waiting' (green room, open shows) | 'live'
74
 
75
 
76
  ROOMS: dict[str, Room] = {
 
103
  return {
104
  "id": r.id, "title": r.title, "topic": r.topic, "emoji": r.emoji,
105
  "template": r.template, "cast": _cast_preview(r.template),
106
+ "status": r.status,
107
  "live": r.id in ROOM_TASKS and not ROOM_TASKS[r.id].done(),
108
  }
109
 
 
172
  ROOMS.pop(room.id, None)
173
 
174
 
175
+ async def _list_device_guests(room_id: str) -> list[dict]:
176
+ """Physical Reachy companions currently in the LiveKit room (green-room cast)."""
177
+ import json as _json
178
+
179
+ guests = []
180
+ try:
181
+ lk = api.LiveKitAPI(config.LIVEKIT_URL, config.LIVEKIT_API_KEY, config.LIVEKIT_API_SECRET)
182
+ res = await lk.room.list_participants(api.ListParticipantsRequest(room=room_id))
183
+ await lk.aclose()
184
+ for p in res.participants:
185
+ if not p.identity.startswith("reachy-device-"):
186
+ continue
187
+ meta = {}
188
+ try:
189
+ meta = _json.loads(p.metadata) if p.metadata else {}
190
+ except Exception:
191
+ pass
192
+ guests.append({
193
+ "id": f"d{len(guests) + 1}",
194
+ "name": (p.name or p.identity)[:24],
195
+ "persona": str(meta.get("persona") or "")[:60],
196
+ "voice": str(meta.get("voice") or "")[:300],
197
+ "device": p.identity,
198
+ })
199
+ except Exception as e:
200
+ print(f"[room:{room_id}] device scan failed: {e}")
201
+ return guests[:3]
202
+
203
+
204
+ async def _run_generated_show(room: Room, required: list[dict] | None = None) -> None:
205
  """The live LLM+TTS cascade (the original project idea, end to end):
206
+ one structured LLM call writes the cast + speaker→dialogue script (starring
207
+ any physical Reachys from the green room), then each line is voiced by
208
+ Qwen3-TTSwith line N+1's audio generating while line N plays — and
209
+ streamed into the LiveKit room with subtitle data messages."""
210
  pubs = ROOM_PUBS.setdefault(room.id, {})
211
  # a hidden stage manager joins first so the audience gets real progress
212
  # while the script generates (the frontend hides 'stage-' participants)
 
216
  pubs[stage.identity] = stage
217
  await stage.send_data({"type": "status", "text": "the writers’ room is drafting the script…"})
218
 
219
+ script = await showgen.write_script(room.title, room.topic, required=required)
220
  speakers = {s["id"]: s for s in script["speakers"]}
221
  names = " · ".join(s["name"] for s in script["speakers"])
222
  await stage.send_data({"type": "status", "text": f"cast locked: {names} — designing their voices…"})
 
224
  for s in script["speakers"]:
225
  ident = f"gen-{room.id}-{s['id']}"
226
  if ident not in pubs:
227
+ props = {k: s[k] for k in ("hat", "face", "neck") if s.get(k)}
228
+ if s.get("device"):
229
+ # this host IS a physical robot: the web UI routes this audio to
230
+ # the device's card, and the device plays only this track
231
+ props["forDevice"] = s["device"]
232
  pub = ReachyPublisher(
233
  ident, s["name"], room=room.id, color=s["color"], persona=s["persona"],
234
+ props=props,
235
  )
236
  await pub.connect()
237
  pubs[ident] = pub
 
314
  return
315
  cast = CAST_TEMPLATES.get(room.template)
316
  if not cast:
317
+ # 'open' rooms start from the green room via POST /rooms/{id}/start
 
 
318
  return
319
  if not any(config.SAMPLES_DIR.glob("*.wav")):
320
  raise HTTPException(400, "no voice clips found (run scripts/make-qwen-samples.py)")
 
376
  device: bool = False
377
  color: str | None = None
378
  persona: str | None = None
379
+ voice: str | None = None
380
  bodyColor: str | None = None
381
  hat: str | None = None
382
  face: str | None = None
 
419
  if req.template not in ("duo", "groupchat", "open"):
420
  raise HTTPException(400, "bad template")
421
  rid = f"{_slug(req.title)}-{uuid.uuid4().hex[:4]}"
422
+ status = "waiting" if req.template == "open" else "live"
423
  ROOMS[rid] = Room(rid, req.title.strip() or "Untitled room", req.topic.strip(),
424
+ req.emoji or "💬", req.template, status=status)
425
+ if status == "waiting":
426
+ asyncio.create_task(_expire_waiting_room(rid))
427
  return {"ok": True, "id": rid, "room": _room_dict(ROOMS[rid])}
428
 
429
 
430
+ async def _expire_waiting_room(rid: str, ttl: float = 900.0) -> None:
431
+ """A green room nobody starts or visits gets cleaned up after a while."""
432
+ await asyncio.sleep(ttl)
433
+ room = ROOMS.get(rid)
434
+ if room and room.status == "waiting" and rid not in ROOM_TASKS:
435
+ if (await _viewer_counts()).get(rid, 0) == 0:
436
+ ROOMS.pop(rid, None)
437
+ print(f"[room:{rid}] waiting room expired")
438
+
439
+
440
  async def post_token(req: TokenRequest):
441
  metadata = None
442
  if req.device:
443
  identity = f"reachy-device-{_slug(req.name or 'reachy')}-{uuid.uuid4().hex[:4]}"
444
  metadata = {"role": "reachy", "device": True,
445
  "color": req.color or "#49e6c8", "persona": req.persona or "a real Reachy Mini, live"}
446
+ for k in ("voice", "bodyColor", "hat", "face", "neck"):
447
  if getattr(req, k):
448
  metadata[k] = getattr(req, k)
449
  else:
 
461
  return {"ok": True, "room": _room_dict(room)}
462
 
463
 
464
+ async def start_show(room_id: str):
465
+ """Leave the green room: cast the physical Reachys currently connected and
466
+ kick off the generated show."""
467
+ room = ROOMS.get(room_id)
468
+ if not room:
469
+ raise HTTPException(404, "no such room")
470
+ if room.template != "open" or not room.topic:
471
+ raise HTTPException(400, "only topic'd open rooms start this way")
472
+ task = ROOM_TASKS.get(room.id)
473
+ if task and not task.done():
474
+ return {"ok": True, "already": True, "room": _room_dict(room)}
475
+ if not (config.MODAL_LLM_URL and config.MODAL_TTS_URL):
476
+ raise HTTPException(503, "show brain not configured")
477
+ guests = await _list_device_guests(room.id)
478
+ room.status = "live"
479
+ ROOM_TASKS[room.id] = asyncio.create_task(_run_generated_show(room, required=guests))
480
+ return {"ok": True, "guests": [g["name"] for g in guests], "room": _room_dict(room)}
481
+
482
+
483
  async def add_reachy(room_id: str, req: ReachyRequest):
484
  """Drop a user-configured Reachy into the room. It joins and reacts; live
485
  speech needs the on-device brain + TTS, so its track stays silent for now."""
 
673
  app.add_api_route("/api/rooms", list_rooms, methods=["GET"])
674
  app.add_api_route("/api/rooms", create_room, methods=["POST"])
675
  app.add_api_route("/api/rooms/{room_id}/join", join_room, methods=["POST"])
676
+ app.add_api_route("/api/rooms/{room_id}/start", start_show, methods=["POST"])
677
  app.add_api_route("/api/rooms/{room_id}/reachy", add_reachy, methods=["POST"])
678
  app.add_api_route("/api/rooms/{room_id}/reachy/leave", remove_reachy, methods=["POST"])
679
  app.add_api_route("/api/token", post_token, methods=["POST"])
backend/showgen.py CHANGED
@@ -38,15 +38,25 @@ SCRIPT_SYS = (
38
 
39
 
40
  def _script_prompt(title: str, topic: str, n_speakers: int, n_lines: int,
41
- history: list[dict] | None) -> str:
42
  cont = ""
43
  if history:
44
  recap = "\n".join(f"{h['speaker']}: {h['text']}" for h in history[-6:])
45
  cont = (f"\nThis is a CONTINUATION. Keep the same speakers (same ids/names). "
46
  f"The conversation so far ended with:\n{recap}\nPick up naturally from there.")
 
 
 
 
 
 
 
 
 
 
47
  return (
48
  f'Show: "{title}". Topic: "{topic}".\n'
49
- f"Create {n_speakers} distinct robot hosts and a {n_lines}-line conversation.\n"
50
  "Return JSON exactly in this shape:\n"
51
  "{\n"
52
  ' "speakers": [{"id": "s1", "name": "...", "persona": "<=8 words",\n'
@@ -72,8 +82,15 @@ def _parse_json(content: str) -> dict:
72
 
73
 
74
  async def write_script(title: str, topic: str, n_speakers: int = 3, n_lines: int = 10,
75
- history: list[dict] | None = None) -> dict:
76
- """One structured extraction: cast + speaker→dialogue mapping."""
 
 
 
 
 
 
 
77
  async with httpx.AsyncClient(timeout=300) as cx:
78
  r = await cx.post(
79
  f"{config.MODAL_LLM_URL}/v1/chat/completions",
@@ -81,7 +98,7 @@ async def write_script(title: str, topic: str, n_speakers: int = 3, n_lines: int
81
  json={
82
  "messages": [
83
  {"role": "system", "content": SCRIPT_SYS},
84
- {"role": "user", "content": _script_prompt(title, topic, n_speakers, n_lines, history)},
85
  ],
86
  "max_tokens": 2400,
87
  "temperature": 0.85,
@@ -95,6 +112,7 @@ async def write_script(title: str, topic: str, n_speakers: int = 3, n_lines: int
95
  lines = data.get("lines") or []
96
  if not speakers or not lines:
97
  raise ValueError("script missing speakers/lines")
 
98
  by_id = {}
99
  for i, s in enumerate(speakers[:6]):
100
  sid = str(s.get("id") or f"s{i+1}")
@@ -108,6 +126,22 @@ async def write_script(title: str, topic: str, n_speakers: int = 3, n_lines: int
108
  "face": s.get("face") if s.get("face") in PROPS["face"] else None,
109
  "neck": s.get("neck") if s.get("neck") in PROPS["neck"] else None,
110
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  clean_lines = []
112
  for ln in lines[:24]:
113
  sid, text = str(ln.get("speaker") or ""), str(ln.get("text") or "").strip()
 
38
 
39
 
40
  def _script_prompt(title: str, topic: str, n_speakers: int, n_lines: int,
41
+ history: list[dict] | None, required: list[dict] | None = None) -> str:
42
  cont = ""
43
  if history:
44
  recap = "\n".join(f"{h['speaker']}: {h['text']}" for h in history[-6:])
45
  cont = (f"\nThis is a CONTINUATION. Keep the same speakers (same ids/names). "
46
  f"The conversation so far ended with:\n{recap}\nPick up naturally from there.")
47
+ req = ""
48
+ if required:
49
+ guests = "\n".join(
50
+ f'- id "{g["id"]}", name "{g["name"]}"'
51
+ + (f' — persona: {g["persona"]}' if g.get("persona") else "")
52
+ + (f' — voice: {g["voice"]}' if g.get("voice") else "")
53
+ for g in required)
54
+ req = ("\nREAL robot guests are physically in the studio. They MUST be speakers, "
55
+ "with these EXACT ids and names (write a fitting voice spec for any without one), "
56
+ f"and they must get plenty of lines:\n{guests}\n")
57
  return (
58
  f'Show: "{title}". Topic: "{topic}".\n'
59
+ f"Create {n_speakers} distinct robot hosts and a {n_lines}-line conversation.{req}\n"
60
  "Return JSON exactly in this shape:\n"
61
  "{\n"
62
  ' "speakers": [{"id": "s1", "name": "...", "persona": "<=8 words",\n'
 
82
 
83
 
84
  async def write_script(title: str, topic: str, n_speakers: int = 3, n_lines: int = 10,
85
+ history: list[dict] | None = None,
86
+ required: list[dict] | None = None) -> dict:
87
+ """One structured extraction: cast + speaker→dialogue mapping.
88
+
89
+ `required` = physical Reachy guests ({id, name, persona, voice, device})
90
+ that MUST appear in the cast; their identity fields are enforced server-side.
91
+ """
92
+ required = required or []
93
+ n_speakers = max(n_speakers, min(len(required) + 1, 4))
94
  async with httpx.AsyncClient(timeout=300) as cx:
95
  r = await cx.post(
96
  f"{config.MODAL_LLM_URL}/v1/chat/completions",
 
98
  json={
99
  "messages": [
100
  {"role": "system", "content": SCRIPT_SYS},
101
+ {"role": "user", "content": _script_prompt(title, topic, n_speakers, n_lines, history, required)},
102
  ],
103
  "max_tokens": 2400,
104
  "temperature": 0.85,
 
112
  lines = data.get("lines") or []
113
  if not speakers or not lines:
114
  raise ValueError("script missing speakers/lines")
115
+ req_by_id = {g["id"]: g for g in required}
116
  by_id = {}
117
  for i, s in enumerate(speakers[:6]):
118
  sid = str(s.get("id") or f"s{i+1}")
 
126
  "face": s.get("face") if s.get("face") in PROPS["face"] else None,
127
  "neck": s.get("neck") if s.get("neck") in PROPS["neck"] else None,
128
  }
129
+ # enforce the real guests: exact name/persona, their device binding, and
130
+ # their own voice spec when the robot brought one
131
+ for gid, g in req_by_id.items():
132
+ if gid not in by_id: # the LLM dropped a guest — seat them anyway
133
+ by_id[gid] = {"id": gid, "name": g["name"], "persona": g.get("persona", ""),
134
+ "voice": g.get("voice") or "A clear, friendly robot voice.",
135
+ "color": PALETTE[len(by_id) % len(PALETTE)],
136
+ "hat": None, "face": None, "neck": None}
137
+ lines.append({"speaker": gid, "text": f"{g['name']} here — happy to be in the studio!"})
138
+ sp = by_id[gid]
139
+ sp["name"] = g["name"]
140
+ if g.get("persona"):
141
+ sp["persona"] = g["persona"][:60]
142
+ if g.get("voice"):
143
+ sp["voice"] = g["voice"][:300]
144
+ sp["device"] = g.get("device")
145
  clean_lines = []
146
  for ln in lines[:24]:
147
  sid, text = str(ln.get("speaker") or ""), str(ln.get("text") or "").strip()
frontend/dist/assets/index-CzU806we.css ADDED
@@ -0,0 +1 @@
 
 
1
+ :root{--font-display:"Bricolage Grotesque", "Clash Display", system-ui, sans-serif;--font-body:"Instrument Sans", "Satoshi", system-ui, -apple-system, sans-serif;--font-mono:"JetBrains Mono", ui-monospace, "SF Mono", monospace;--void:#06060a;--bg:#08080e;--bg-2:#0e0e16;--panel:#f5f1e808;--panel-2:#f5f1e80f;--border:#f5f1e81a;--border-2:#f5f1e82b;--line:#1f1f2c;--line-bright:#2a2a3a;--text:#f5f1e8;--paper:#d8d2c0;--muted:#8a8a96;--faint:#4a4a55;--gold:#c8a24d;--gold-bright:#e8c570;--gold-dim:#8a6f30;--gold-glow:#c8a24d2e;--cyan:#5ce1e6;--teal:var(--cyan);--live:#ff5d5d;--grad:linear-gradient(100deg, var(--gold-bright) 18%, var(--gold) 52%, var(--gold-dim));--r-sm:6px;--r:10px;--r-lg:16px;--shadow:0 32px 80px -24px #000000d1;--ease-out:cubic-bezier(.19, 1, .22, 1);--ease-in-out:cubic-bezier(.77, 0, .175, 1);--ease-spring:cubic-bezier(.34, 1.56, .64, 1);--pad-page:clamp(22px, 5vw, 64px)}*{box-sizing:border-box}html,body{height:100%;margin:0}body{background:var(--bg);color:var(--text);font-family:var(--font-body);font-feature-settings:"ss01", "cv11";-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;font-size:15px;overflow:hidden}::selection{background:var(--gold);color:var(--void)}.hidden{display:none!important}.hidden-audio{opacity:0;pointer-events:none;width:0;height:0;position:absolute}button{color:inherit;font-family:inherit}:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:2px}#app{height:100%}.view{position:absolute;inset:0}.view:not(.hidden){animation:viewIn .34s var(--ease-out)}@keyframes viewIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}.grain{z-index:50;pointer-events:none;opacity:.028;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");position:fixed;inset:0}.grid-mesh{background-image:linear-gradient(90deg,#f5f1e806 1px,#0000 1px),linear-gradient(#f5f1e806 1px,#0000 1px);background-size:64px 64px}.bracket:before,.bracket:after{content:"";border:1px solid var(--gold);opacity:.55;pointer-events:none;width:13px;height:13px;position:absolute}.bracket:before{border-bottom:0;border-right:0;top:-1px;left:-1px}.bracket:after{border-top:0;border-left:0;bottom:-1px;right:-1px}@keyframes sweep{0%{background-position:-200%}to{background-position:200%}}.text-sweep{background:linear-gradient(100deg, var(--text) 20%, var(--gold-bright) 46%, var(--text) 72%);color:#0000;animation:sweep 6s var(--ease-in-out) infinite;background-size:200% 100%;-webkit-background-clip:text;background-clip:text}@keyframes pulse{0%,to{opacity:1}50%{opacity:.25}}@keyframes pulse-dot{0%,to{box-shadow:0 0 0 0 var(--gold-glow), 0 0 11px var(--gold)}50%{box-shadow:0 0 0 7px transparent, 0 0 16px var(--gold-bright)}}@keyframes blink{0%,60%{opacity:1}61%,to{opacity:0}}#topbar{z-index:40;height:58px;padding:0 var(--pad-page);border-bottom:1px solid var(--border);background:color-mix(in srgb, var(--bg) 55%, transparent);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);justify-content:space-between;align-items:center;display:flex;position:fixed;top:0;left:0;right:0}.brand{font-family:var(--font-display);letter-spacing:-.01em;color:var(--text);cursor:pointer;background:0 0;border:none;align-items:center;gap:10px;padding:0;font-size:15px;font-weight:600;display:inline-flex}.brand .dot{background:var(--gold);width:8px;height:8px;box-shadow:0 0 10px 2px var(--gold-glow);border-radius:50%}#nav{align-items:center;gap:10px;display:flex}.btn{border-radius:var(--r-sm);letter-spacing:.01em;cursor:pointer;white-space:nowrap;height:42px;transition:transform .14s var(--ease-out), box-shadow .14s var(--ease-out), background .14s var(--ease-out), border-color .14s var(--ease-out), opacity .14s var(--ease-out);border:1px solid #0000;justify-content:center;align-items:center;padding:0 20px;font-size:13px;font-weight:600;display:inline-flex}.btn:disabled{opacity:.4;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(.97)}.btn-primary{background:var(--grad);color:var(--void);box-shadow:0 8px 28px -12px var(--gold-glow)}.btn-ghost{color:var(--text);border-color:var(--border-2);background:0 0}@media (hover:hover) and (pointer:fine){.btn-primary:not(:disabled):hover{box-shadow:0 12px 32px -10px var(--gold-glow);transform:translateY(-1px)}.btn-ghost:not(:disabled):hover{border-color:var(--gold);background:var(--panel)}}.btn-sm{height:32px;padding:0 14px;font-size:12px}#view-home{background:radial-gradient(680px circle at 78% 42%, var(--gold-glow), transparent 60%), var(--void);flex-direction:column;display:flex}#view-home .grid-mesh{z-index:0;pointer-events:none;opacity:.5;position:absolute;inset:0;-webkit-mask-image:radial-gradient(120% 100% at 50% 0,#000 55%,#0000 100%);mask-image:radial-gradient(120% 100% at 50% 0,#000 55%,#0000 100%)}#hero3d{z-index:0;position:absolute;inset:0}.scanlines{z-index:1;pointer-events:none;opacity:.4;background:repeating-linear-gradient(0deg,#f5f1e805 0 1px,#0000 1px 4px);position:absolute;inset:0;-webkit-mask-image:linear-gradient(#0000,#000 30% 70%,#0000);mask-image:linear-gradient(#0000,#000 30% 70%,#0000)}.boot{right:var(--pad-page);z-index:3;text-align:right;font-family:var(--font-mono);letter-spacing:.05em;color:var(--cyan);pointer-events:none;transition:opacity .7s var(--ease-out);font-size:10.5px;line-height:1.8;position:absolute;top:84px}.boot .ok{color:var(--gold)}.boot .cur{background:var(--cyan);width:7px;animation:1s step-end infinite blink;display:inline-block}@media (width<=900px){.boot{display:none}}.home-top{z-index:2;min-height:0;padding:58px var(--pad-page) 40px;pointer-events:none;flex:1;align-items:center;display:flex;position:relative}.home-content{pointer-events:auto;max-width:580px}.meta-strip{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.3em;flex-wrap:wrap;align-items:center;gap:14px;font-size:10px;font-weight:500;display:flex}.meta-strip .ms-rec{color:var(--gold);align-items:center;gap:9px;display:inline-flex}.meta-strip .ms-sep{color:var(--faint);letter-spacing:0}.meta-strip .ms-ch{color:var(--muted)}.meta-strip .pulse{background:var(--gold);width:7px;height:7px;animation:pulse-dot 2.2s var(--ease-in-out) infinite;border-radius:50%}.home-content h1{font-family:var(--font-display);letter-spacing:-.045em;color:var(--text);text-wrap:balance;margin:24px 0 20px;font-size:clamp(44px,6vw,86px);font-weight:300;line-height:.94}.home-content h1 .l1,.home-content h1 .l2{display:block}.home-content h1 .l2{padding-bottom:.08em}.home-content h1 em{font-style:normal;font-weight:500}.home-content p{color:var(--paper);max-width:34em;margin:0;font-size:clamp(15px,1.35vw,17px);line-height:1.65}.home-actions{flex-wrap:wrap;gap:10px;display:flex}.ha-group{margin-top:22px}.ha-group:first-of-type{margin-top:30px}.ha-label{font-family:var(--font-mono);letter-spacing:.26em;text-transform:uppercase;color:var(--faint);margin-bottom:9px;font-size:9.5px;font-weight:500}.uc-pill{font-family:var(--font-mono);letter-spacing:.04em;color:var(--muted);align-items:center;gap:9px;max-width:46em;margin-top:22px;font-size:10.5px;line-height:1.6;display:inline-flex}.uc-pill .uc-dot{background:var(--gold);opacity:.7;border-radius:50%;flex-shrink:0;width:6px;height:6px}.status{min-height:18px;color:var(--faint);font-family:var(--font-mono);letter-spacing:.02em;margin-top:12px;font-size:11.5px}.ticker{z-index:3;border-top:1px solid var(--border);background:color-mix(in srgb, var(--void) 65%, transparent);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);align-items:center;height:38px;display:flex;position:absolute;bottom:0;left:0;right:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,#0000,#000 5% 95%,#0000);mask-image:linear-gradient(90deg,#0000,#000 5% 95%,#0000)}.ticker-track{white-space:nowrap;will-change:transform;font-family:var(--font-mono);letter-spacing:.26em;text-transform:uppercase;color:var(--muted);font-size:10px;animation:120s linear infinite marquee;display:inline-flex}.ticker-track .b{color:var(--gold)}.ticker-track .sep{color:var(--faint);margin:0 22px}.ticker:hover .ticker-track{animation-play-state:paused}@keyframes marquee{0%{transform:translate(0,0)}to{transform:translate(-50%)}}#view-connect{background:radial-gradient(ellipse at 50% -10%, var(--gold-glow), transparent 55%), var(--void);overflow-y:auto}.connect-wrap{max-width:640px;margin:0 auto;padding:104px 24px 64px}.steps{gap:6px;margin-bottom:28px;display:flex}.steps .step{background:var(--border);height:2px;transition:background .3s var(--ease-out);border-radius:1px;flex:1}.steps .step.on{background:var(--gold)}.connect-card{background:var(--panel);border:1px solid var(--border);border-radius:var(--r-lg);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);padding:clamp(26px,4vw,40px);position:relative}.connect-card h2{font-family:var(--font-display);letter-spacing:-.02em;margin:0 0 6px;font-size:24px;font-weight:500}.connect-card .sub{color:var(--muted);margin:0 0 26px;font-size:14px;line-height:1.55}.field{margin-bottom:22px}.field>label{font-family:var(--font-mono);letter-spacing:.22em;text-transform:uppercase;color:var(--muted);margin-bottom:9px;font-size:10px;font-weight:500;display:block}.input,.textarea{border:1px solid var(--border-2);border-radius:var(--r-sm);width:100%;color:var(--text);font-family:var(--font-body);transition:border-color .14s var(--ease-out), box-shadow .14s var(--ease-out);background:0 0;padding:12px 14px;font-size:15px}.input::placeholder,.textarea::placeholder{color:var(--faint)}.input:focus,.textarea:focus{border-color:var(--gold);box-shadow:0 0 0 3px var(--gold-glow);outline:none}.textarea{resize:vertical;min-height:92px;line-height:1.55}.chips{flex-wrap:wrap;gap:7px;margin-top:10px;display:flex}.chip{border:1px solid var(--border);color:var(--muted);letter-spacing:.01em;cursor:pointer;transition:border-color .13s var(--ease-out), color .13s var(--ease-out), background .13s var(--ease-out), transform .13s var(--ease-out);background:0 0;border-radius:999px;padding:6px 12px;font-size:12px}.chip:active{transform:scale(.96)}@media (hover:hover) and (pointer:fine){.chip:hover{border-color:var(--border-2);color:var(--text)}}.chip.on{border-color:var(--gold);color:var(--text);background:var(--gold-glow);font-weight:600}.swatches{flex-wrap:wrap;gap:9px;display:flex}.swatch{cursor:pointer;width:30px;height:30px;transition:transform .12s var(--ease-out), box-shadow .12s var(--ease-out);border:1px solid #00000040;border-radius:8px}.swatch:active{transform:scale(.92)}@media (hover:hover) and (pointer:fine){.swatch:hover{transform:scale(1.08)}}.swatch.on{box-shadow:0 0 0 2px var(--bg), 0 0 0 4px var(--gold)}.connect-nav{justify-content:space-between;gap:12px;margin-top:30px;display:flex}.review-row{border-bottom:1px solid var(--border);gap:16px;padding:13px 0;display:flex}.review-row:last-child{border-bottom:none}.review-row .k{width:108px;color:var(--faint);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.18em;flex-shrink:0;padding-top:2px;font-size:10px}.review-row .v{color:var(--text);font-size:14px;line-height:1.55}#config-twin{border:1px solid var(--border);border-radius:var(--r);background:radial-gradient(ellipse at 50% 120%, var(--gold-glow), transparent 60%);width:100%;height:250px;margin-bottom:16px;position:relative;overflow:hidden}.style-row{flex-wrap:wrap;align-items:center;gap:12px;margin:0 0 18px;display:flex}.style-reason{font-family:var(--font-mono);color:var(--gold-bright);font-size:11px;line-height:1.5}#view-call{background:var(--void);flex-direction:column;display:flex}.call-head{padding:72px var(--pad-page) 14px;border-bottom:1px solid var(--border);align-items:center;gap:13px;display:flex}.call-head .ch-emoji{border:1px solid var(--border);border-radius:var(--r-sm);background:var(--panel);flex-shrink:0;place-items:center;width:38px;height:38px;font-size:19px;display:grid}.call-head .ch-title{font-family:var(--font-display);letter-spacing:-.01em;font-size:17px;font-weight:500}.call-head .ch-topic{color:var(--muted);margin-top:1px;font-size:12.5px}.call-head .ch-viewers{font-family:var(--font-mono);letter-spacing:.08em;color:var(--muted);align-items:center;gap:6px;margin-left:auto;font-size:10.5px;display:inline-flex}.call-head .live-pill{font-family:var(--font-mono);letter-spacing:.24em;text-transform:uppercase;color:var(--live);align-items:center;gap:7px;margin-left:16px;font-size:10px;font-weight:600;display:inline-flex}.call-head .live-pill .d{background:var(--live);width:6px;height:6px;box-shadow:0 0 8px var(--live);border-radius:50%;animation:1.4s infinite pulse}.grid{padding:18px var(--pad-page) 26px;flex:1;align-content:center;gap:14px;display:grid}.spotlight{padding:18px var(--pad-page) 26px;flex:1;gap:14px;min-height:0;display:flex}.spot-main{flex:1;min-width:0;display:flex}.spot-main .card{aspect-ratio:auto;width:100%;height:100%}.spot-strip{scrollbar-width:thin;scrollbar-color:var(--border-2) transparent;flex-direction:column;flex-shrink:0;gap:10px;width:clamp(150px,16vw,220px);display:flex;overflow-y:auto}.spot-strip .card{aspect-ratio:4/3;flex:none;width:100%}.spot-main .card:before,.spot-main .card:after{content:"";z-index:3;border:1px solid var(--gold);opacity:.6;pointer-events:none;width:16px;height:16px;position:absolute}.spot-main .card:before{border-bottom:0;border-right:0;top:10px;left:10px}.spot-main .card:after{border-top:0;border-left:0;bottom:10px;right:10px}.card{aspect-ratio:4/3;background:var(--panel);border:1px solid var(--border);border-radius:var(--r);transition:border-color .18s var(--ease-out), box-shadow .18s var(--ease-out);position:relative;overflow:hidden}.card.talking{border-color:var(--accent,var(--gold));box-shadow:0 0 0 1px var(--accent,var(--gold)), 0 0 34px -12px var(--accent,var(--gold))}.card .twin{position:absolute;inset:0}.speaking-ring{pointer-events:none;border-radius:var(--r);box-shadow:inset 0 0 52px -26px var(--accent,var(--gold));opacity:0;transition:opacity .2s var(--ease-out);position:absolute;inset:0}.card.talking .speaking-ring{opacity:1}.nameplate{background:color-mix(in srgb, var(--void) 70%, transparent);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--border);border-radius:999px;align-items:center;gap:8px;padding:5px 11px;font-size:12px;display:flex;position:absolute;bottom:10px;left:10px}.nameplate .dot{background:var(--faint);width:6px;height:6px;transition:background .15s var(--ease-out), box-shadow .15s var(--ease-out);border-radius:50%}.card.talking .nameplate .dot{background:var(--accent,var(--gold));box-shadow:0 0 7px var(--accent,var(--gold))}.nameplate .name{letter-spacing:.01em;font-weight:600}.nameplate .persona{color:var(--muted);font-size:11px}.spot-strip .card .nameplate{gap:6px;padding:4px 9px;font-size:11px}.spot-strip .card .nameplate .persona{display:none}.spot-strip .card .nameplate .dot{width:5px;height:5px}.live-rail{max-width:620px;margin-top:18px}.lr-head{justify-content:space-between;align-items:center;gap:14px;margin-bottom:9px;display:flex}.lr-label{font-family:var(--font-mono);letter-spacing:.26em;color:var(--muted);align-items:center;gap:8px;font-size:10px;display:inline-flex}.lr-label .d{background:var(--live);border-radius:50%;width:6px;height:6px;animation:1.6s infinite pulse}.lr-label b{color:var(--gold);font-weight:600}.lr-search{border:0;border-bottom:1px solid var(--border-2);width:150px;color:var(--text);font-family:var(--font-mono);transition:border-color .14s var(--ease-out), width .2s var(--ease-out);background:0 0;padding:4px 2px;font-size:11px}.lr-search::placeholder{color:var(--faint)}.lr-search:focus{border-color:var(--gold);outline:none;width:200px}.lr-row{scroll-snap-type:x proximity;scrollbar-width:thin;scrollbar-color:var(--border-2) transparent;gap:10px;padding:2px 2px 6px;display:flex;overflow-x:auto;-webkit-mask-image:linear-gradient(90deg,#000 92%,#0000);mask-image:linear-gradient(90deg,#000 92%,#0000)}.lr-card{scroll-snap-align:start;text-align:left;background:var(--panel);border:1px solid var(--border);border-radius:var(--r);cursor:pointer;opacity:0;width:250px;animation:lrIn .36s var(--ease-out) forwards;transition:border-color .14s var(--ease-out), background .14s var(--ease-out), transform .14s var(--ease-out);flex:none;align-items:center;gap:11px;padding:10px 13px;display:flex}@keyframes lrIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.lr-card:active{transform:scale(.98)}@media (hover:hover) and (pointer:fine){.lr-card:hover{border-color:var(--gold);background:var(--panel-2)}}.lc-emoji{border:1px solid var(--border);border-radius:var(--r-sm);background:var(--panel);flex-shrink:0;place-items:center;width:34px;height:34px;font-size:17px;display:grid}.lc-body{flex-direction:column;gap:2px;min-width:0;display:flex}.lc-title{white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:600;overflow:hidden}.lc-topic{color:var(--muted);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-meta{font-family:var(--font-mono);color:var(--muted);flex-shrink:0;align-items:center;gap:5px;margin-left:auto;font-size:10.5px;display:inline-flex}.lc-meta .d{background:var(--faint);border-radius:50%;width:6px;height:6px}.lc-meta .d.on{background:var(--live);box-shadow:0 0 7px var(--live)}.lr-empty{font-family:var(--font-mono);color:var(--faint);padding:6px 2px;font-size:11px}.modal-back{z-index:60;background:color-mix(in srgb, var(--void) 72%, transparent);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);place-items:center;display:grid;position:fixed;inset:0}.modal-card{background:var(--bg-2);width:min(460px,92vw)}.subtitles{z-index:10;border-radius:var(--r);background:color-mix(in srgb, var(--void) 80%, transparent);border:1px solid var(--border);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);text-align:left;max-width:min(760px,88vw);animation:viewIn .24s var(--ease-out);align-items:baseline;gap:10px;padding:10px 18px;font-size:14.5px;line-height:1.5;display:flex;position:absolute;bottom:22px;left:50%;transform:translate(-50%)}.subtitles b{flex-shrink:0;font-weight:700}.subtitles i{color:var(--muted);font-style:normal;font-family:var(--font-mono);font-size:12px}.twin-hint{pointer-events:none;font-family:var(--font-mono);letter-spacing:.12em;color:var(--faint);text-transform:uppercase;font-size:9px;position:absolute;bottom:8px;right:10px}.green-room{z-index:8;padding:0 var(--pad-page);pointer-events:none;justify-content:center;display:flex;position:absolute;top:118px;left:0;right:0}.gr-card{pointer-events:auto;border:1px solid var(--border);border-radius:var(--r-lg);background:color-mix(in srgb, var(--void) 72%, transparent);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);width:min(620px,100%);animation:viewIn .4s var(--ease-out);padding:26px 28px}.gr-card h3{font-family:var(--font-display);letter-spacing:-.02em;margin:10px 0 6px;font-size:21px;font-weight:500}.gr-sub{color:var(--muted);margin:0 0 16px;font-size:13.5px;line-height:1.55}.gr-cmd{border:1px solid var(--border-2);border-radius:var(--r-sm);background:color-mix(in srgb, var(--void) 50%, transparent);align-items:center;gap:10px;padding:10px 13px;display:flex}.gr-cmd code{font-family:var(--font-mono);color:var(--gold-bright);white-space:nowrap;flex:1;font-size:12.5px;overflow-x:auto}.gr-foot{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:14px;margin-top:18px;display:flex}.gr-count{font-family:var(--font-mono);letter-spacing:.06em;color:var(--muted);font-size:11px}.gr-count.on{color:var(--gold)}.writing-room{z-index:8;pointer-events:none;place-items:center;padding-top:60px;display:grid;position:absolute;inset:0}.wr-card{text-align:center;border:1px solid var(--border);border-radius:var(--r-lg);background:color-mix(in srgb, var(--void) 60%, transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);width:min(560px,88vw);animation:viewIn .4s var(--ease-out);padding:38px 34px}.wr-label{font-family:var(--font-mono);letter-spacing:.34em;color:var(--gold);margin-bottom:20px;font-size:10px}.wr-typing{min-height:30px;font-family:var(--font-mono);letter-spacing:.04em;color:var(--text);font-size:clamp(14px,1.7vw,18px)}.wr-caret{background:var(--gold);vertical-align:text-bottom;width:8px;height:1em;margin-left:3px;animation:1s step-end infinite blink;display:inline-block}.wr-status{font-family:var(--font-mono);color:var(--muted);letter-spacing:.04em;margin-top:16px;font-size:11.5px}.wr-dots{justify-content:center;gap:7px;margin-top:18px;display:flex}.wr-dots span{background:var(--gold);width:6px;height:6px;animation:wrBounce 1.2s var(--ease-in-out) infinite;border-radius:50%}.wr-dots span:nth-child(2){animation-delay:.15s}.wr-dots span:nth-child(3){animation-delay:.3s}@keyframes wrBounce{0%,to{opacity:.45;transform:translateY(0)}50%{opacity:1;transform:translateY(-6px)}}.adm-toolbar{flex-wrap:wrap;align-items:center;gap:8px;margin-top:4px;display:flex}.adm-search{width:130px;margin-left:auto;padding:7px 11px;font-size:12px}.adm-list{flex-direction:column;gap:8px;margin-top:18px;display:flex}.adm-row{border:1px solid var(--border);border-radius:var(--r-sm);background:var(--panel);transition:border-color .14s var(--ease-out), background .14s var(--ease-out);grid-template-columns:auto 1fr auto;align-items:center;gap:14px;padding:11px 14px;display:grid}.adm-row.sel{border-color:var(--gold);background:var(--gold-glow)}.adm-cb{width:15px;height:15px;accent-color:var(--gold);cursor:pointer}.adm-body{min-width:0}.adm-head{align-items:center;gap:8px;font-size:14px;font-weight:600;display:flex}.adm-live{font-family:var(--font-mono);letter-spacing:.2em;color:var(--live);border:1px solid var(--live);border-radius:3px;padding:1px 5px;font-size:9px;font-weight:600}.adm-tag{font-family:var(--font-mono);letter-spacing:.16em;color:var(--muted);border:1px solid var(--border-2);border-radius:3px;padding:1px 5px;font-size:9px}.adm-topic{color:var(--paper);white-space:nowrap;text-overflow:ellipsis;margin-top:2px;font-size:12px;overflow:hidden}.adm-meta{font-family:var(--font-mono);color:var(--muted);letter-spacing:.04em;margin-top:3px;font-size:10.5px}.adm-acts{gap:7px;display:flex}.adm-danger{color:var(--live);border-color:color-mix(in srgb, var(--live) 40%, transparent)}@media (hover:hover) and (pointer:fine){.adm-danger:not(:disabled):hover{border-color:var(--live);background:color-mix(in srgb, var(--live) 10%, transparent)}}html,body,#app{height:100dvh}@media (width<=900px){:root{--pad-page:20px}}@media (width<=760px){#topbar{height:52px}.brand{font-size:14px}.btn{height:40px;padding:0 16px}.home-top{align-items:flex-start;padding-top:70px;padding-bottom:56px}.home-content{padding-top:6vh}.home-content h1{margin:18px 0 14px;font-size:clamp(36px,11vw,56px)}.home-content p{font-size:14.5px}.home-actions{margin-top:24px}.home-actions .btn{flex:calc(50% - 10px);min-width:130px}.meta-strip{letter-spacing:.24em;gap:10px;font-size:9px}.uc-pill{font-size:10px}.ticker{height:32px}.connect-wrap{padding:84px 16px 48px}.connect-card{padding:22px 18px}.connect-card h2{font-size:21px}.review-row .k{width:88px}#config-twin{height:200px}.connect-nav .btn{flex:1}.call-head{flex-wrap:wrap;row-gap:6px;padding-top:64px;padding-bottom:10px}.call-head .ch-emoji{width:32px;height:32px;font-size:16px}.call-head .ch-title{font-size:15px}.call-head .ch-topic{font-size:11.5px}.grid{padding:12px var(--pad-page) 16px;align-content:start;gap:10px;overflow-y:auto;grid-template-columns:1fr!important}.grid .card{aspect-ratio:16/10}.spotlight{padding:12px var(--pad-page) 14px;flex-direction:column;gap:10px}.spot-main{min-height:0}.spot-strip{flex-direction:row;width:100%;padding-bottom:4px;overflow:auto hidden}.spot-strip .card{width:clamp(120px,36vw,170px)}.nameplate{padding:4px 9px;font-size:11px;bottom:8px;left:8px}.nameplate .persona{display:none}}@media (width<=420px){.home-actions .btn{flex:100%}.meta-strip .ms-ch{display:none}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important}.ticker-track{animation:none}}.topbar-right{align-items:center;gap:12px;display:flex}.theme-picker{align-items:center;gap:8px;display:flex}.theme-picker .tp-label{font-family:var(--font-mono);letter-spacing:.22em;color:var(--muted);font-size:10px}.theme-select{font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;background:var(--panel);color:var(--text);border:1px solid var(--border-2);cursor:pointer;border-radius:999px;outline:none;padding:6px 12px;font-size:11px}.theme-select option{background:var(--bg-2);color:var(--text);text-transform:none;letter-spacing:0}@media (width<=720px){.theme-picker .tp-label{display:none}}.fx-grid,.fx-kanji,.lithos-reveal,:root[data-theme=dossier] .boot,:root[data-theme=concrete] .boot{display:none}.fx-grid{z-index:0;pointer-events:none;background-image:linear-gradient(var(--gold) 1px, transparent 1px), linear-gradient(90deg, var(--gold) 1px, transparent 1px);transform-origin:bottom;opacity:.18;background-size:46px 46px;height:50%;animation:3s linear infinite gridScroll;position:absolute;bottom:-2px;left:-20%;right:-20%;transform:perspective(340px)rotateX(70deg);-webkit-mask-image:linear-gradient(#0000,#000 70%);mask-image:linear-gradient(#0000,#000 70%)}@keyframes gridScroll{to{background-position:0 46px,0 46px}}@media (width<=760px){:root[data-theme=ghost] .fx-kanji{display:none!important}}.fx-kanji{z-index:1;pointer-events:none;writing-mode:vertical-rl;letter-spacing:.34em;color:var(--gold);opacity:.45;text-shadow:0 0 9px var(--gold-glow);font-family:Noto Sans JP,sans-serif;font-size:13px;font-weight:300;line-height:1.8;position:absolute;top:50%;left:clamp(10px,1.4vw,18px);transform:translateY(-50%)}:root[data-theme=ghost]{--void:#04080d;--bg:#04080d;--bg-2:#081119;--panel:#00ffd109;--panel-2:#00ffd112;--border:#00ffd121;--border-2:#00ffd13d;--text:#e8f4f6;--paper:#aecccf;--muted:#69808a;--faint:#33474f;--gold:#00ffd1;--gold-bright:#5fffe4;--gold-dim:#0a8a78;--gold-glow:#00ffd129;--cyan:#ff2e6c;--teal:#00ffd1;--live:#ff2e6c;--grad:linear-gradient(100deg, #5fffe4, #00ffd1 55%, #0a8a78);--font-display:"Inter", system-ui, sans-serif;--font-body:"Inter", system-ui, sans-serif}:root[data-theme=ghost] .fx-kanji{display:block}:root[data-theme=ghost] .home-content h1{letter-spacing:-.03em;font-weight:300}:root[data-theme=ghost] .home-content h1 em{font-weight:400}:root[data-theme=ghost] .text-sweep{color:var(--gold);background:0 0;animation:2.6s steps(2,end) infinite ghostGlitch}@keyframes ghostGlitch{0%,to{text-shadow:-2px 0 #ff2e6c,2px 0 #00ffd1}48%{text-shadow:-2px 0 #ff2e6c,2px 0 #00ffd1}50%{text-shadow:2px 0 #ff2e6c,-2px 0 #4ab8ff;transform:translate(1px)}52%{text-shadow:-1px 0 #ff2e6c,1px 0 #00ffd1}}:root[data-theme=ghost] .scanlines{opacity:.6;background:repeating-linear-gradient(0deg,#00ffd10a 0 1px,#0000 1px 3px)}:root[data-theme=tron]{--void:#000005;--bg:#000005;--bg-2:#00080f;--panel:#00d4ff0a;--panel-2:#00d4ff14;--border:#00d4ff2e;--border-2:#00d4ff6b;--text:#d8f4ff;--paper:#a9e0ff;--muted:#5a8aa0;--faint:#27485a;--gold:#00d4ff;--gold-bright:#7fe6ff;--gold-dim:#0a667a;--gold-glow:#00d4ff33;--cyan:#ff7e29;--teal:#00d4ff;--live:#ff7e29;--hero:#9ff0ff;--grad:linear-gradient(100deg, #7fe6ff, #00d4ff 55%, #0a667a);--font-display:"Orbitron", sans-serif;--font-mono:"Share Tech Mono", monospace;--font-body:"Share Tech Mono", monospace}:root[data-theme=tron] .fx-grid{display:block}:root[data-theme=tron] .grid-mesh{display:none}:root[data-theme=tron] .home-content h1{letter-spacing:.01em;text-transform:uppercase;text-shadow:0 0 26px #00d4ff4d;font-size:clamp(36px,4.6vw,68px);font-weight:700;line-height:1.06}:root[data-theme=tron] .text-sweep{color:#0000;-webkit-text-stroke:1.4px var(--gold);text-shadow:0 0 22px #00d4ff8c;background:0 0;animation:none}:root[data-theme=tron] .btn-primary{box-shadow:0 0 22px -4px var(--gold)}:root[data-theme=tron] #view-home:before{content:"";z-index:0;pointer-events:none;background:radial-gradient(34% 22% at 76% 76%,#00d4ff29,#0000 70%);position:absolute;inset:0}:root[data-theme=tron] #view-home:after{content:"";z-index:4;pointer-events:none;background:radial-gradient(#0000 55%,#00000599 100%);position:absolute;inset:0}:root[data-theme=oracle]{--void:#000;--bg:#000;--bg-2:#0a0a0a;--panel:#5cff5c0a;--panel-2:#5cff5c14;--border:#1c1c1c;--border-2:#2c2c2c;--text:#fafafa;--paper:#cfcfcf;--muted:#6a6a6a;--faint:#404040;--gold:#5cff5c;--gold-bright:#7cff7c;--gold-dim:#2faa2f;--gold-glow:#5cff5c2e;--cyan:#ff2eaa;--teal:#5cff5c;--live:#ff2eaa;--grad:none;--font-display:"JetBrains Mono", monospace;--font-body:"JetBrains Mono", monospace;--font-mono:"JetBrains Mono", monospace;--r-sm:0px;--r:0px;--r-lg:0px}:root[data-theme=oracle] .home-content h1{letter-spacing:-.01em;text-transform:uppercase;text-shadow:0 0 14px #5cff5c38;font-size:clamp(30px,4vw,58px);font-weight:700;line-height:1.05}:root[data-theme=oracle] .home-content h1 .l1:before{content:"> ";color:var(--gold)}:root[data-theme=oracle] .text-sweep{color:var(--gold);background:0 0;animation:none}:root[data-theme=oracle] .text-sweep:after{content:"_";animation:1s step-end infinite oBlink}@keyframes oBlink{50%{opacity:0}}:root[data-theme=oracle] .btn{border-radius:0}:root[data-theme=oracle] .btn-primary{background:var(--gold);color:#000;box-shadow:none}:root[data-theme=oracle] .btn-ghost{border:1px solid var(--gold);color:var(--gold)}:root[data-theme=oracle] .card,:root[data-theme=oracle] .uc-pill,:root[data-theme=oracle] .nameplate,:root[data-theme=oracle] .theme-select{border-radius:0}:root[data-theme=oracle] .grid-mesh{display:none}:root[data-theme=oracle] .scanlines{opacity:.7}:root[data-theme=oracle] #view-home:before{content:"";z-index:4;pointer-events:none;background:radial-gradient(#0000 55%,#0000008c 100%);position:absolute;inset:0}:root[data-theme=kinetic]{--void:#050505;--bg:#050505;--bg-2:#0a0a0a;--panel:#f5f1e808;--panel-2:#f5f1e80f;--border:#f5f1e81a;--border-2:#f5f1e833;--text:#f5f1e8;--paper:#cfcabb;--muted:#8a877f;--faint:#4a4843;--gold:#ff2a4d;--gold-bright:#ff5a72;--gold-dim:#a01024;--gold-glow:#ff2a4d33;--cyan:#5be9ff;--teal:#5be9ff;--live:#ff2a4d;--grad:linear-gradient(100deg, #ff5a72, #ff2a4d 55%, #a01024);--font-display:"Bricolage Grotesque", sans-serif}:root[data-theme=kinetic] .home-content h1{letter-spacing:-.045em;text-transform:uppercase;font-size:clamp(44px,6.2vw,88px);font-weight:800;line-height:.88}:root[data-theme=kinetic] #view-home:before{content:"ON AIR";z-index:0;pointer-events:none;font-family:var(--font-display);letter-spacing:-.05em;color:#0000;-webkit-text-stroke:1px #f5f1e814;font-size:clamp(100px,17vw,250px);font-weight:800;line-height:.8;position:absolute;top:8%;right:-1vw}:root[data-theme=kinetic] #view-home:after{content:"";z-index:3;background:var(--gold);pointer-events:none;width:5px;position:absolute;top:0;bottom:0;left:0}:root[data-theme=kinetic] .ticker{height:44px}:root[data-theme=kinetic] .ticker-track{color:var(--paper);font-size:11px}:root[data-theme=lithos]{--void:#0c0a08;--bg:#0c0a08;--bg-2:#16100a;--panel:#e8702a0d;--panel-2:#e8702a1a;--border:#f5f1e81a;--border-2:#e8702a59;--text:#f5f1e8;--paper:#d8ccbe;--muted:#9a8c7e;--faint:#5a4c40;--gold:#e8702a;--gold-bright:#ff8c4a;--gold-dim:#a04c18;--gold-glow:#e8702a38;--cyan:#e8a04a;--teal:#e8702a;--live:#e8702a;--hero:#ff9a5a;--grad:linear-gradient(100deg, #ff8c4a, #e8702a 55%, #a04c18);--font-display:"Inter", system-ui, sans-serif;--font-body:"Inter", system-ui, sans-serif;--r-sm:999px}:root[data-theme=lithos] #view-home{overflow:hidden}:root[data-theme=lithos] .home-content h1{letter-spacing:-.05em;font-weight:600;line-height:.95}:root[data-theme=lithos] .home-content h1 .l1{letter-spacing:-.03em;font-family:Playfair Display,serif;font-style:italic;font-weight:400}:root[data-theme=lithos] .text-sweep{color:var(--text);letter-spacing:-.06em;background:0 0;font-weight:600;animation:none}:root[data-theme=lithos] .btn{border-radius:999px}:root[data-theme=lithos] .grid-mesh{display:none}:root[data-theme=lithos] #view-home:before{content:"";z-index:0;pointer-events:none;background:repeating-linear-gradient(#e8702a12 0 1px,#0000 1px 26px,#ffaa640d 26px 27px,#0000 27px 52px);height:38%;position:absolute;bottom:0;left:0;right:0;-webkit-mask-image:linear-gradient(#0000,#000 60%);mask-image:linear-gradient(#0000,#000 60%)}:root[data-theme=lithos] .lithos-reveal{display:block}.lithos-reveal{z-index:1;pointer-events:none;background:repeating-radial-gradient(circle at 50% 100%,#ff8c4a38 0 2px,#0000 2px 24px) fixed,linear-gradient(#e8702a1f,#783c181a) fixed;border-radius:50%;width:600px;height:600px;position:absolute;top:-2000px;left:-2000px;-webkit-mask-image:radial-gradient(circle,#000 30%,#0000 70%);mask-image:radial-gradient(circle,#000 30%,#0000 70%)}@media (hover:none),(prefers-reduced-motion:reduce){.lithos-reveal{display:none!important}}:root[data-theme=observatory]{--void:#081a3c;--bg:#081a3c;--bg-2:#0d2356;--panel:#f3ecdc0b;--panel-2:#f3ecdc17;--border:#f3ecdc26;--border-2:#d4a64b6b;--text:#f3ecdc;--paper:#cfc6b0;--muted:#95906f;--faint:#4a5470;--gold:#d4a64b;--gold-bright:#ecc878;--gold-dim:#8a6f30;--gold-glow:#d4a64b33;--cyan:#c14935;--teal:#d4a64b;--live:#c14935;--hero:#e8d9a0;--grad:linear-gradient(100deg, #ecc878, #d4a64b 55%, #8a6f30);--font-display:"Cormorant Garamond", serif;--font-body:"Cormorant Garamond", serif}:root[data-theme=observatory] .home-content h1{font-variant:small-caps;letter-spacing:.03em;font-family:Cormorant Garamond,serif;font-weight:600;line-height:1}:root[data-theme=observatory] .home-content p{font-size:clamp(17px,1.6vw,21px)}:root[data-theme=observatory] .text-sweep{font-variant:small-caps;color:var(--gold);background:0 0;animation:none}:root[data-theme=observatory] #view-home{background:radial-gradient(42% 34% at 70% 22%, #6e50aa1f, transparent 70%), radial-gradient(36% 30% at 28% 64%, #3c78a01a, transparent 70%), radial-gradient(ellipse at 75% 30%, #d4a64b14, transparent 55%), var(--void)}:root[data-theme=observatory] .grid-mesh{opacity:1;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cg fill='%23f3ecdc'%3E%3Ccircle cx='22' cy='44' r='1.1' opacity='.9'/%3E%3Ccircle cx='74' cy='128' r='.7' opacity='.5'/%3E%3Ccircle cx='136' cy='32' r='.9' opacity='.7'/%3E%3Ccircle cx='188' cy='96' r='.6' opacity='.45'/%3E%3Ccircle cx='248' cy='52' r='1.2' opacity='.85'/%3E%3Ccircle cx='292' cy='148' r='.7' opacity='.5'/%3E%3Ccircle cx='44' cy='208' r='.8' opacity='.6'/%3E%3Ccircle cx='116' cy='258' r='1.1' opacity='.8'/%3E%3Ccircle cx='178' cy='188' r='.6' opacity='.4'/%3E%3Ccircle cx='276' cy='288' r='.7' opacity='.5'/%3E%3Ccircle cx='94' cy='174' r='.5' opacity='.35'/%3E%3Ccircle cx='156' cy='136' r='.8' opacity='.55'/%3E%3Ccircle cx='258' cy='176' r='.5' opacity='.4'/%3E%3Ccircle cx='36' cy='300' r='.9' opacity='.6'/%3E%3C/g%3E%3Cg fill='%23d4a64b'%3E%3Ccircle cx='228' cy='236' r='.9' opacity='.75'/%3E%3Ccircle cx='130' cy='84' r='.7' opacity='.6'/%3E%3C/g%3E%3C/svg%3E");background-size:320px 320px;display:block;-webkit-mask-image:none;mask-image:none}@keyframes twinkle{0%,to{opacity:.9}50%{opacity:.3}}:root[data-theme=observatory] .scanlines{opacity:1;animation:twinkle 4.5s var(--ease-in-out) infinite;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='420'%3E%3Cg fill='%23ffffff'%3E%3Ccircle cx='64' cy='80' r='1.5'/%3E%3Ccircle cx='300' cy='60' r='1.3'/%3E%3Ccircle cx='190' cy='240' r='1.4'/%3E%3Ccircle cx='360' cy='330' r='1.2'/%3E%3Ccircle cx='90' cy='350' r='1.3'/%3E%3C/g%3E%3C/svg%3E") 0 0/420px 420px;-webkit-mask-image:none;mask-image:none}:root[data-theme=observatory] #view-home:before{content:"";z-index:0;pointer-events:none;background:radial-gradient(circle at 36% 30%,#1c3a78,#0c2258 52%,#050f30 78%);border-radius:50%;width:46vw;height:46vw;position:absolute;bottom:-26vw;left:-14vw;box-shadow:inset -18px -14px 60px #0000008c,0 0 80px #d4a64b1f}@keyframes shoot{0%,90%{opacity:0;transform:rotate(24deg)translate(0)}91%{opacity:.8}97%,to{opacity:0;transform:rotate(24deg)translate(46vw)}}:root[data-theme=observatory] #view-home:after{content:"";z-index:0;pointer-events:none;background:linear-gradient(90deg,#0000,#ffffffe6);border-radius:2px;width:110px;height:1.5px;animation:18s linear infinite shoot;position:absolute;top:12%;left:16%}:root[data-theme=observatory] .home-top:after{content:"";z-index:0;pointer-events:none;background:radial-gradient(5px 5px at 14% 22%, var(--gold) 45%, transparent 55%);border:1px solid #d4a64b38;border-radius:50%;width:36vw;height:36vw;position:absolute;top:16%;right:4%}@media (width<=900px){:root[data-theme=observatory] .home-top:after{display:none}}:root[data-theme=observatory] .home-top:before{content:"";z-index:0;pointer-events:none;opacity:.5;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='170'%3E%3Cg stroke='%23f3ecdc' stroke-width='.6' opacity='.55' fill='none'%3E%3Cpath d='M20 130 L70 90 L120 100 L165 50 L210 30'/%3E%3Cpath d='M120 100 L140 140'/%3E%3C/g%3E%3Cg fill='%23f3ecdc'%3E%3Ccircle cx='20' cy='130' r='2'/%3E%3Ccircle cx='70' cy='90' r='2.6'/%3E%3Ccircle cx='120' cy='100' r='2'/%3E%3Ccircle cx='165' cy='50' r='2.4'/%3E%3Ccircle cx='210' cy='30' r='2'/%3E%3Ccircle cx='140' cy='140' r='1.8'/%3E%3C/g%3E%3Ctext x='150' y='160' font-family='serif' font-style='italic' font-size='11' fill='%23d4a64b'%3ELyra%3C/text%3E%3C/svg%3E");width:240px;height:170px;position:absolute;top:12%;left:56%}@media (width<=900px){:root[data-theme=observatory] .home-top:before{display:none}}:root[data-theme=foundry]{--void:#0a0a0a;--bg:#0a0a0a;--bg-2:#141414;--panel:#f8f5f00a;--panel-2:#f8f5f014;--border:#f8f5f024;--border-2:#c89d4a66;--text:#f8f5f0;--paper:#cfc9bf;--muted:#8a857c;--faint:#4a4640;--gold:#c89d4a;--gold-bright:#e8bd6a;--gold-dim:#8a6a28;--gold-glow:#c89d4a33;--cyan:#d73f3f;--teal:#c89d4a;--live:#d73f3f;--grad:linear-gradient(100deg, #e8bd6a, #c89d4a 55%, #8a6a28);--font-display:"Fraunces", serif;--font-body:"Inter", sans-serif}:root[data-theme=foundry] .home-content h1{font-variation-settings:"opsz" 144, "SOFT" 0, "WONK" 0;letter-spacing:-.02em;font-family:Fraunces,serif;font-weight:300;line-height:.9}:root[data-theme=foundry] .text-sweep{color:var(--gold);background:0 0;font-style:italic;animation:none}:root[data-theme=foundry] #view-home:before{content:"Aa";z-index:0;pointer-events:none;font-variation-settings:"opsz" 144;color:#0000;-webkit-text-stroke:1px #f8f5f017;font-family:Fraunces,serif;font-size:clamp(220px,38vw,560px);font-style:italic;font-weight:600;line-height:.8;position:absolute;bottom:-6vh;right:2vw}:root[data-theme=foundry] #view-home:after{content:"BRICOLAGE — FRAUNCES — JETBRAINS · OPSZ 9→144 · WGHT 300";left:var(--pad-page);z-index:3;pointer-events:none;font-family:var(--font-mono);letter-spacing:.3em;color:var(--faint);font-size:9px;position:absolute;bottom:50px}:root[data-theme=blueprint]{--void:#2c4f7d;--bg:#2c4f7d;--bg-2:#1a3556;--panel:#ffffff0f;--panel-2:#ffffff1a;--border:#ffffff38;--border-2:#ffffff73;--text:#fff;--paper:#cfdcec;--muted:#fff9;--faint:#ffffff52;--gold:#f4cc4a;--gold-bright:#ffe06a;--gold-dim:#c4a020;--gold-glow:#f4cc4a2e;--cyan:#8fc7ff;--teal:#f4cc4a;--live:#f4cc4a;--hero:#eaf6ff;--grad:linear-gradient(100deg, #ffe06a, #f4cc4a 55%, #c4a020);--font-display:"IBM Plex Sans Condensed", sans-serif;--font-body:"JetBrains Mono", monospace;--font-mono:"JetBrains Mono", monospace;--r-sm:2px;--r:2px;--r-lg:2px}:root[data-theme=blueprint] .home-content h1{text-transform:uppercase;letter-spacing:-.005em;font-family:IBM Plex Sans Condensed,sans-serif;font-weight:600;line-height:.94}:root[data-theme=blueprint] .text-sweep{color:var(--gold);background:0 0;animation:none}:root[data-theme=blueprint] .grid-mesh{opacity:1;background-image:linear-gradient(#ffffff29 1px,#0000 1px),linear-gradient(90deg,#ffffff29 1px,#0000 1px),linear-gradient(#ffffff0d 1px,#0000 1px),linear-gradient(90deg,#ffffff0d 1px,#0000 1px);background-size:100px 100px,100px 100px,20px 20px,20px 20px;-webkit-mask-image:none;mask-image:none}:root[data-theme=blueprint] #view-home:before{content:"PROJECT — SMALL TALK · UNIT — REACHY MINI ×5 · SCALE — 1:1 · SHEET 01 / 01 · REV C";right:var(--pad-page);z-index:3;pointer-events:none;outline-offset:3px;max-width:260px;font-family:var(--font-mono);letter-spacing:.18em;color:#ffffffc7;background:#1a35568c;border:1px solid #ffffff80;outline:1px solid #fff3;padding:10px 12px;font-size:9px;line-height:1.9;position:absolute;bottom:52px}:root[data-theme=blueprint] #view-home:after{content:"FIG. 01 — REACHY MINI (HOLOGRAPHIC PROJECTION)";z-index:3;pointer-events:none;font-family:var(--font-mono);letter-spacing:.22em;color:#ffffffb3;border-top:1px solid #ffffff8c;padding-top:7px;font-size:9px;position:absolute;bottom:16%;right:14%}:root[data-theme=blueprint] .home-top:before,:root[data-theme=blueprint] .home-top:after{z-index:3;pointer-events:none;font-family:var(--font-mono);color:#ffffff80;font-size:18px;font-weight:300;position:absolute}:root[data-theme=blueprint] .home-top:before{content:"+";top:70px;right:18px}:root[data-theme=blueprint] .home-top:after{content:"+";bottom:50px;left:18px}@media (width<=900px){:root[data-theme=blueprint] #view-home:before,:root[data-theme=blueprint] #view-home:after{display:none}}:root[data-theme=dossier]{--void:#e8d9b8;--bg:#e8d9b8;--bg-2:#d8c79a;--panel:#1a1a1a0d;--panel-2:#1a1a1a1a;--border:#1a1a1a33;--border-2:#1a1a1a66;--text:#1a1a1a;--paper:#2c2a26;--muted:#5a564c;--faint:#8a8470;--gold:#b03030;--gold-bright:#d04545;--gold-dim:#7a2020;--gold-glow:#b0303021;--cyan:#5b3a8a;--teal:#b03030;--live:#b03030;--hero:#4a3a28;--hero-style:solid;--grad:linear-gradient(100deg, #d04545, #b03030 55%, #7a2020);--font-display:"Special Elite", monospace;--font-body:"JetBrains Mono", monospace;--font-mono:"JetBrains Mono", monospace;--r-sm:0px;--r:0px;--r-lg:0px}:root[data-theme=dossier] .home-content h1{letter-spacing:0;font-family:Special Elite,monospace;font-weight:400;line-height:1.08}:root[data-theme=dossier] .text-sweep{color:var(--gold);background:0 0;animation:none}:root[data-theme=dossier] .btn-primary{color:#e8d9b8}:root[data-theme=dossier] .btn{border-radius:0}:root[data-theme=dossier] .grid-mesh,:root[data-theme=dossier] .scanlines,:root[data-theme=dossier] .grain{display:none}:root[data-theme=dossier] #view-home{background:radial-gradient(140px 90px at 82% 18%,#8a64281a,#0000),radial-gradient(220px 140px at 12% 78%,#8a642814,#0000),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0.45 0 0 0 0 0.38 0 0 0 0 0.25 0 0 0 0.05 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E"),#e8d9b8}:root[data-theme=dossier] #view-home:before{content:"TOP SECRET";z-index:3;pointer-events:none;letter-spacing:.32em;color:var(--gold);border:3px double var(--gold);opacity:.75;text-shadow:0 0 1px var(--gold);padding:8px 18px 6px 22px;font-family:Special Elite,monospace;font-size:clamp(20px,2.6vw,34px);position:absolute;top:14%;right:10%;transform:rotate(-9deg)}:root[data-theme=dossier] #view-home:after{content:"CASE FILE № ST-2026-014 · SUBJECT: AUTONOMOUS BROADCAST";left:var(--pad-page);z-index:3;pointer-events:none;font-family:var(--font-mono);letter-spacing:.2em;color:var(--muted);border-bottom:1px solid #1a1a1a4d;padding-bottom:5px;font-size:10px;position:absolute;top:72px}:root[data-theme=dossier] .meta-strip .ms-ch{color:#1a1a1a;background:#1a1a1a;padding:1px 4px}:root[data-theme=dossier] .uc-pill:after{content:" — [REDACTED]";color:#1a1a1a;background:#1a1a1a}:root[data-theme=concrete]{--void:#aaa79e;--bg:#aaa79e;--bg-2:#9a978e;--panel:#1a1a1a0f;--panel-2:#1a1a1a1f;--border:#1a1a1a38;--border-2:#1a1a1a73;--text:#1a1a1a;--paper:#33312c;--muted:#55524a;--faint:#75726a;--gold:#8a4a2a;--gold-bright:#a85a34;--gold-dim:#5a2e18;--gold-glow:#8a4a2a26;--cyan:#44413a;--teal:#8a4a2a;--live:#8a4a2a;--hero:#3a3833;--hero-style:solid;--grad:linear-gradient(100deg, #a85a34, #8a4a2a 55%, #5a2e18);--font-display:"Inter", sans-serif;--font-body:"Inter", sans-serif;--r-sm:0px;--r:0px;--r-lg:0px}:root[data-theme=concrete] .home-content h1{letter-spacing:-.03em;text-transform:uppercase;font-weight:200;line-height:.92}:root[data-theme=concrete] .text-sweep{color:var(--gold);background:0 0;animation:none}:root[data-theme=concrete] .btn-primary{color:#f0ece0}:root[data-theme=concrete] .btn{border-radius:0}:root[data-theme=concrete] .scanlines,:root[data-theme=concrete] .grain{display:none}:root[data-theme=concrete] .grid-mesh{opacity:1;background-image:repeating-linear-gradient(90deg,#1a1a1a14 0 1px,#0000 1px 79px),repeating-linear-gradient(0deg,#1a1a1a0d 0 1px,#0000 1px 79px);background-size:auto;display:block;-webkit-mask-image:none;mask-image:none}:root[data-theme=concrete] #view-home:before{content:"";left:calc(var(--pad-page) - 22px);z-index:0;pointer-events:none;background:#b4b1a8;width:min(640px,60vw);position:absolute;top:15%;bottom:17%;box-shadow:14px 18px #1a1a1a3d}:root[data-theme=concrete] #view-home:after{content:"ST";z-index:0;pointer-events:none;letter-spacing:-.06em;color:#1a1a1a12;font-size:clamp(200px,32vw,460px);font-weight:200;line-height:.8;position:absolute;bottom:-7vh;right:-2vw}:root[data-theme=concrete] .home-top:after{content:"— BÉTON BRUT · CAST IN PLACE · 2026";left:var(--pad-page);z-index:3;font-family:var(--font-mono);letter-spacing:.3em;color:#1a1a1a80;pointer-events:none;font-size:9px;position:absolute;bottom:52px}
frontend/dist/assets/index-D3H2VL23.js ADDED
The diff for this file is too large to render. See raw diff
 
frontend/dist/index.html CHANGED
@@ -11,8 +11,8 @@
11
  href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300;12..96,400;12..96,500;12..96,600;12..96,700;12..96,800&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,500;1,400;1,500;1,600&family=Orbitron:wght@500;700;900&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@300;500&family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400&family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,600&family=Inter+Tight:wght@200;300;400;600&family=Special+Elite&family=IBM+Plex+Sans+Condensed:wght@400;600&display=swap"
12
  rel="stylesheet"
13
  />
14
- <script type="module" crossorigin src="/assets/index-FobRO28F.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-DQK8u8kI.css">
16
  </head>
17
  <body>
18
  <div class="grain"></div>
@@ -50,11 +50,19 @@
50
  An AI-to-AI podcast hosted by Reachy Minis — tune into a live show
51
  and watch them talk it out, voices, personalities and all.
52
  </p>
53
- <div class="home-actions">
54
- <button id="showGroup" class="btn btn-primary">The group chat</button>
55
- <button id="showPodcast" class="btn btn-ghost">The podcast</button>
56
- <button id="newShowBtn" class="btn btn-ghost">+ New show</button>
57
- <button id="connectBtn" class="btn btn-ghost">Design your Reachy</button>
 
 
 
 
 
 
 
 
58
  </div>
59
  <div class="live-rail hidden" id="liveRail">
60
  <div class="lr-head">
@@ -98,6 +106,19 @@
98
  <div class="spot-strip" id="spotStrip"></div>
99
  </div>
100
  <div id="subtitles" class="subtitles hidden"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  <div id="writingRoom" class="writing-room hidden">
102
  <div class="wr-card">
103
  <div class="wr-label">PRE-PRODUCTION</div>
 
11
  href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300;12..96,400;12..96,500;12..96,600;12..96,700;12..96,800&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,500;1,400;1,500;1,600&family=Orbitron:wght@500;700;900&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@300;500&family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400&family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,600&family=Inter+Tight:wght@200;300;400;600&family=Special+Elite&family=IBM+Plex+Sans+Condensed:wght@400;600&display=swap"
12
  rel="stylesheet"
13
  />
14
+ <script type="module" crossorigin src="/assets/index-D3H2VL23.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CzU806we.css">
16
  </head>
17
  <body>
18
  <div class="grain"></div>
 
50
  An AI-to-AI podcast hosted by Reachy Minis — tune into a live show
51
  and watch them talk it out, voices, personalities and all.
52
  </p>
53
+ <div class="ha-group">
54
+ <div class="ha-label">Prerecorded demos</div>
55
+ <div class="home-actions">
56
+ <button id="showGroup" class="btn btn-primary">The group chat</button>
57
+ <button id="showPodcast" class="btn btn-ghost">The podcast</button>
58
+ </div>
59
+ </div>
60
+ <div class="ha-group">
61
+ <div class="ha-label">Live · bring your Reachy</div>
62
+ <div class="home-actions">
63
+ <button id="newShowBtn" class="btn btn-primary">+ New show</button>
64
+ <button id="connectBtn" class="btn btn-ghost">Design your Reachy</button>
65
+ </div>
66
  </div>
67
  <div class="live-rail hidden" id="liveRail">
68
  <div class="lr-head">
 
106
  <div class="spot-strip" id="spotStrip"></div>
107
  </div>
108
  <div id="subtitles" class="subtitles hidden"></div>
109
+ <div id="greenRoom" class="green-room hidden">
110
+ <div class="gr-card">
111
+ <div class="wr-label">GREEN ROOM</div>
112
+ <h3>Waiting for the cast</h3>
113
+ <p class="gr-sub">Physical Reachy Minis that connect will appear below — then the
114
+ writers build the show around them.</p>
115
+ <div class="gr-cmd"><code id="grCmd"></code><button class="btn btn-ghost btn-sm" id="grCopy">copy</button></div>
116
+ <div class="gr-foot">
117
+ <span class="gr-count" id="grCount">no robots connected yet</span>
118
+ <button class="btn btn-primary" id="grStart">Start the show</button>
119
+ </div>
120
+ </div>
121
+ </div>
122
  <div id="writingRoom" class="writing-room hidden">
123
  <div class="wr-card">
124
  <div class="wr-label">PRE-PRODUCTION</div>