123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hero Studio</title>
<link rel="icon" href='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23e6edf3" stroke-width="1.8"><rect x="3" y="4" width="18" height="16" rx="2"/><line x1="3" y1="14.5" x2="21" y2="14.5"/><line x1="6" y1="17.5" x2="13" y2="17.5"/></svg>'>
<style>
@font-face { font-family: 'Familjen Grotesk'; src: url('fonts/familjen-grotesk-700.woff2') format('woff2'); font-weight: 700; font-display: swap; }
@font-face { font-family: 'Familjen Grotesk'; src: url('fonts/familjen-grotesk-500.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Satoshi'; src: url('fonts/satoshi-400.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Satoshi'; src: url('fonts/satoshi-500.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('fonts/jetbrains-mono-400.woff2') format('woff2'); font-weight: 400; font-display: swap; }

:root {
  --bg:#0d1117; --surface:#161b22; --elevated:#1c2128; --overlay:#21262d; --border:#30363d;
  --text:#e6edf3; --text-2:#8b949e; --text-3:#484f58;
  --accent:#00b8b0; --accent-bright:#00e6dc; --purple:#8c6bf7; --blue:#58a6ff;
  --green:#3fb950; --red:#f85149; --yellow:#d29922;
  --grad:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7);
}
* { box-sizing:border-box; margin:0; padding:0; }
html,body { height:100%; }
body {
  background:var(--bg); color:var(--text);
  font-family:'Satoshi',sans-serif; font-size:14px;
  display:flex; flex-direction:column; overflow:hidden;
}

/* ---------- 44px header, three zones ---------- */
header {
  height:44px; flex:0 0 44px; display:flex; align-items:center; gap:12px;
  padding:0 12px; background:var(--surface); border-bottom:1px solid var(--border);
}
.brand { display:flex; align-items:center; gap:8px; white-space:nowrap; }
.brand svg { width:16px; height:16px; stroke:var(--text); }
.brand .name { font-family:'Familjen Grotesk'; font-weight:500; font-size:14px; color:var(--accent); }
.brand .sub { color:var(--text-2); font-size:13px; }
.vsep { width:1px; height:22px; background:var(--border); flex:0 0 1px; }
.subject { display:flex; align-items:baseline; gap:10px; min-width:0; }
.subject b { font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.subject .meta { font-family:'JetBrains Mono',monospace; font-size:11.5px; color:var(--text-2); white-space:nowrap; }
.actions { margin-left:auto; display:flex; gap:6px; }
.iconbtn {
  width:30px; height:30px; border:1px solid var(--border); border-radius:8px; background:transparent;
  display:flex; align-items:center; justify-content:center; cursor:pointer; transition:all .15s ease;
}
.iconbtn svg { width:16px; height:16px; stroke:var(--text); fill:none; stroke-width:1.8; }
.iconbtn:hover { background:var(--elevated); border-color:var(--text-3); }
.iconbtn.on { border-color:var(--accent); background:rgba(0,184,176,.08); }
@media (max-width:720px){ .subject .meta { display:none; } }

/* ---------- body ---------- */
main { flex:1; display:flex; min-height:0; }
#panel {
  flex:0 0 264px; overflow-y:auto; background:var(--surface); border-right:1px solid var(--border);
  padding:14px 14px 40px;
}
#panel h3 {
  font-family:'Familjen Grotesk'; font-weight:500; font-size:11px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--text-2); margin:18px 0 8px;
}
#panel h3:first-child { margin-top:2px; }
select {
  width:100%; background:var(--elevated); color:var(--text); border:1px solid var(--border);
  border-radius:8px; padding:7px 8px; font-family:'Satoshi'; font-size:13px; cursor:pointer;
}
.optgrid { display:flex; flex-wrap:wrap; gap:6px; }
.opt {
  border:1px solid var(--border); background:var(--elevated); color:var(--text-2); border-radius:8px;
  padding:6px 9px; font-size:12.5px; cursor:pointer; transition:all .15s ease; font-family:'Satoshi';
}
.opt:hover { color:var(--text); border-color:var(--text-3); }
.opt.on { color:var(--text); border-color:var(--accent); background:rgba(0,184,176,.10); }
.swatches { display:flex; gap:6px; }
.swatch {
  width:38px; height:26px; border-radius:6px; border:1px solid var(--border); cursor:pointer;
  transition:all .15s ease; position:relative;
}
.swatch.on { outline:2px solid var(--accent); outline-offset:1px; }
.sw-midnight { background:linear-gradient(155deg,#0a0e14,#0d1117 45%,#071318); }
.sw-pcb { background:linear-gradient(155deg,#03211f,#04302c); }
.sw-onion { background:repeating-radial-gradient(circle at 90% 10%, #101a2c 0 4px, #16223a 4px 6px); }
.sw-pill { background:#0c3a3c; }
.sw-grid { background:#0c3a3c url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12"><rect x="2" y="2" width="8" height="8" fill="none" stroke="%23ffffff" stroke-opacity=".35" stroke-width="1"/></svg>'); }
.sw-grad { background:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7); }
input[type=range] { width:100%; accent-color:var(--accent); cursor:pointer; }
label.chk { display:flex; align-items:center; gap:8px; padding:5px 0; cursor:pointer; color:var(--text); font-size:13px; }
label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:pointer; }
.hint { font-size:11.5px; color:var(--text-2); line-height:1.45; margin-top:6px; }
.btn {
  width:100%; margin-top:8px; padding:8px 10px; border-radius:8px; border:1px solid var(--border);
  background:var(--elevated); color:var(--text); font-family:'Satoshi'; font-size:13px; cursor:pointer;
  transition:all .15s ease;
}
.btn:hover { border-color:var(--accent); background:rgba(0,184,176,.08); }

/* ---------- stage + hero canvas ---------- */
#stage { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:flex-start; gap:18px; padding:22px; min-width:0; overflow:auto; }
.hero {
  aspect-ratio:16/10; position:relative; overflow:hidden;
  border-radius:12px; border:1px solid var(--border); container-type:inline-size;
  --ga:155deg;
}
#hero { width:100%; max-width:1000px; flex:0 0 auto; box-shadow:0 24px 70px rgba(0,0,0,.5); }

/* backgrounds: continue to the very bottom, per the new rule */
.bg { position:absolute; inset:0; }
/* mirror mode flips the whole background composition (glows, tiles, corner motifs) */
.hero[data-layout=mirror] .bg { transform:scaleX(-1); }
.hero[data-theme=midnight] .bg {
  background:
    radial-gradient(1200px 800px at 88% -10%, rgba(0,184,177,.22), transparent 60%),
    radial-gradient(900px 700px at -5% 110%, rgba(140,107,247,.18), transparent 55%),
    linear-gradient(var(--ga),#0a0e14 0%,#0d1117 45%,#071318 100%);
}
.hero[data-theme=midnight] .bg::after {
  content:''; position:absolute; inset:0;
  background-image:radial-gradient(rgba(255,255,255,.05) 1px, transparent 1.4px);
  background-size:2.2cqw 2.2cqw;
  -webkit-mask-image:radial-gradient(700px 560px at 30% 30%, #000, transparent 75%);
          mask-image:radial-gradient(700px 560px at 30% 30%, #000, transparent 75%);
}
.hero[data-theme=pcb] .bg {
  background:
    radial-gradient(1100px 760px at 85% -8%, rgba(0,230,220,.14), transparent 60%),
    linear-gradient(var(--ga),#031b19 0%,#04302c 55%,#02211e 100%);
}
.hero[data-theme=pcb] .bg::after {
  content:''; position:absolute; inset:0; opacity:.5;
  background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160"><g stroke="%2300e6dc" stroke-opacity=".09" fill="none" stroke-width="1.4"><path d="M10 20 h56 l18 18 v50"/><path d="M84 150 v-36 l22 -22 h44"/><path d="M20 140 v-48 l14 -14"/><circle cx="10" cy="20" r="3.2"/><circle cx="84" cy="88" r="3.2"/><circle cx="150" cy="92" r="3.2"/><circle cx="20" cy="140" r="3.2"/><circle cx="34" cy="78" r="3.2"/></g></svg>');
  background-size:10cqw 10cqw;
}
.hero[data-theme=onion] .bg {
  background:
    radial-gradient(900px 700px at -5% 112%, rgba(140,107,247,.16), transparent 55%),
    linear-gradient(var(--ga),#0b1322 0%,#101a2c 55%,#0a111e 100%);
}
/* authentic Kickstand corner dome (brand deck onion motif), teal-to-purple stroke */
.hero[data-theme=onion] .bg::after {
  content:''; position:absolute; inset:0;
  background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><defs><linearGradient id="g" x1="0" y1="1" x2="1" y2="0"><stop offset="0" stop-color="%238c6bf7"/><stop offset=".55" stop-color="%2339b8ff"/><stop offset="1" stop-color="%2300e6dc"/></linearGradient></defs><g fill="none" stroke="url(%23g)" stroke-width="4" opacity=".5"><path d="M356 400 V234 Q356 60 200 60 Q44 60 44 234 V400"/><path d="M312 400 V250 Q312 110 200 110 Q88 110 88 250 V400"/><path d="M268 400 V266 Q268 160 200 160 Q132 160 132 266 V400"/><path d="M224 400 V282 Q224 210 200 210 Q176 210 176 282 V400"/></g></svg>');
  background-repeat:no-repeat;
  background-position:right -7cqw bottom -9cqw;
  background-size:50cqw auto;
}
.hero[data-theme=pill] .bg {
  background:
    radial-gradient(1000px 720px at 90% -8%, rgba(0,184,177,.12), transparent 60%),
    linear-gradient(var(--ga),#082a29 0%,#0c3a3c 55%,#062220 100%);
}
/* authentic pattern-pill: scattered capsule + circle outlines, mixed brand strokes */
.hero[data-theme=pill] .bg::after {
  content:''; position:absolute; inset:0;
  background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="260" height="200"><g fill="none" stroke-width="2"><rect x="16" y="22" width="26" height="62" rx="13" stroke="%2364ABFF" stroke-opacity=".3"/><circle cx="112" cy="38" r="12" stroke="%2300e6dc" stroke-opacity=".28"/><rect x="196" y="14" width="28" height="72" rx="14" stroke="%2339b8ff" stroke-opacity=".3"/><rect x="84" y="128" width="62" height="26" rx="13" stroke="%2300b8b1" stroke-opacity=".28"/><circle cx="226" cy="150" r="10" stroke="%238c6bf7" stroke-opacity=".26"/><rect x="20" y="150" width="26" height="40" rx="13" stroke="%2300e6dc" stroke-opacity=".2"/></g></svg>');
  background-size:16cqw auto;
}
.hero[data-theme=grid] .bg {
  background:
    radial-gradient(1000px 700px at 85% -10%, rgba(0,230,220,.10), transparent 60%),
    linear-gradient(var(--ga),#07302e 0%,#0c3a3c 55%,#052220 100%);
}
/* authentic pattern-grid: fine outlined-square field */
.hero[data-theme=grid] .bg::after {
  content:''; position:absolute; inset:0; opacity:.55;
  background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"><rect x="4" y="4" width="18" height="18" fill="none" stroke="%23ffffff" stroke-opacity=".13" stroke-width="1.3"/></svg>');
  background-size:2.4cqw 2.4cqw;
}
.hero[data-theme=gradient] .bg {
  background:
    linear-gradient(155deg, rgba(5,10,18,.42), rgba(5,10,18,.14) 55%, rgba(5,10,18,.5)),
    linear-gradient(var(--ga),#00c8bf 0%,#2f9dfa 55%,#8c6bf7 100%);
}

/* ---------- image-zone text column ---------- */
.textzone {
  position:absolute; left:5.75cqw; top:8.5cqw; width:33cqw; z-index:3;
}
.hero[data-layout=mirror] .textzone { left:auto; right:5.75cqw; }
.wordmark { display:flex; align-items:center; margin-bottom:2.2cqw; }
/* the official Adom wordmark (adom-logo.svg), recolored white for dark heroes via mask */
.wordmark .wmark {
  display:block; width:7.8cqw; height:1.72cqw; background:#e6edf3;
  -webkit-mask:url('adom-logo.svg') no-repeat left center / contain;
          mask:url('adom-logo.svg') no-repeat left center / contain;
}
.h-title {
  font-family:'Familjen Grotesk'; font-weight:700; font-size:4.1cqw; line-height:1.02;
  letter-spacing:-.09cqw; color:#e6edf3; margin-bottom:1.4cqw;
}
.h-sub { font-size:1.42cqw; line-height:1.48; color:#aeb8c4; margin-bottom:1.1cqw; }
.h-sub .accent { background:var(--grad); -webkit-background-clip:text; background-clip:text; color:transparent; font-weight:500; }
.h-sub b { color:#e6edf3; font-weight:500; }
.h-desc { font-size:1.05cqw; line-height:1.5; color:#8b949e; max-width:30cqw; }

/* ---------- the app-window mock (the "screenshot") ---------- */
.window {
  position:absolute; z-index:2; border-radius:.9cqw .9cqw 0 0; overflow:hidden;
  background:#10151c; border:1px solid rgba(255,255,255,.09);
  box-shadow:0 1.7cqw 5cqw rgba(0,0,0,.62), 0 0 8cqw rgba(0,184,177,.07);
  display:flex; flex-direction:column;
}
.hero[data-layout=bleed-bottom] .window { right:4.5cqw; top:10cqw; width:52cqw; height:56cqw; }
.hero[data-layout=bleed-right] .window { right:-8cqw; top:9cqw; width:56cqw; height:44cqw; border-radius:.9cqw 0 0 .9cqw; }
.hero[data-layout=bleed-top] .window { right:4.5cqw; top:-6cqw; width:52cqw; height:42cqw; border-radius:0 0 .9cqw .9cqw; }
.hero[data-layout=bleed-corner-br] .window { right:-7cqw; top:14cqw; width:56cqw; height:52cqw; border-radius:.9cqw 0 0 0; }
.hero[data-layout=bleed-corner-tr] .window { right:-7cqw; top:-6cqw; width:56cqw; height:44cqw; border-radius:0 0 0 .9cqw; }
.hero[data-layout=contained] .window { right:4.5cqw; top:8cqw; width:50cqw; height:33cqw; border-radius:.9cqw; }
.hero[data-layout=mirror] .window { left:4.5cqw; top:10cqw; width:52cqw; height:56cqw; }
.titlebar {
  flex:0 0 2.1cqw; background:#181f28; border-bottom:1px solid rgba(255,255,255,.06);
  display:flex; align-items:center; padding:0 .9cqw; gap:.6cqw;
}
.titlebar .t { font-family:'Satoshi'; font-size:.82cqw; color:#8b949e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.winbtns { margin-left:auto; display:flex; gap:.9cqw; align-items:center; }
.winbtns svg { width:.72cqw; height:.72cqw; stroke:#8b949e; stroke-width:2; fill:none; }
.appbody { flex:1; display:flex; min-height:0; }
.sidebar { flex:0 0 11cqw; background:#131920; border-right:1px solid rgba(255,255,255,.05); padding:.8cqw .7cqw; }
.srow { height:1.5cqw; border-radius:.35cqw; background:rgba(255,255,255,.05); margin-bottom:.55cqw; }
.srow.on { background:rgba(0,184,176,.18); border:1px solid rgba(0,184,176,.35); }
.content { flex:1; padding:1cqw 1.1cqw; display:flex; flex-direction:column; gap:.8cqw; }
.crow { display:flex; gap:.6cqw; align-items:center; }
.chipk { height:1.3cqw; border-radius:999px; background:rgba(255,255,255,.07); }
.bars { flex:1; display:flex; align-items:flex-end; gap:.5cqw; min-height:0; }
.bar { flex:1; border-radius:.3cqw .3cqw 0 0; background:linear-gradient(180deg, rgba(0,230,220,.55), rgba(0,184,176,.12)); }
.tbl { display:flex; flex-direction:column; gap:.45cqw; }
.trow { display:flex; gap:.5cqw; }
.tcell { height:1.05cqw; border-radius:.25cqw; background:rgba(255,255,255,.06); }

/* ---------- the lower-third textual overlay ---------- */
.lower {
  position:absolute; left:0; right:0; bottom:0; height:33.4%; z-index:4;
  background:linear-gradient(180deg, rgba(10,14,20,0) 0%, rgba(10,14,20,.62) 22%, rgba(10,14,20,.94) 62%, rgba(10,14,20,.98) 100%);
  display:flex; align-items:flex-end;
}
.lowerin {
  width:100%; padding:0 5.75cqw 3.1cqw; display:flex; align-items:flex-end; gap:2cqw;
}
.lmain { min-width:0; flex:1; }
.badges { display:flex; align-items:center; gap:.9cqw; margin-bottom:1cqw; }
.pill {
  font-family:'Familjen Grotesk'; font-weight:700; font-size:.85cqw; letter-spacing:.14em;
  padding:.3cqw .85cqw; border-radius:999px; border:1px solid; text-transform:uppercase;
}
.pill.app { color:#00e6dc; border-color:rgba(0,230,220,.45); background:rgba(0,184,176,.10); }
.pill.skill { color:#b39dfb; border-color:rgba(140,107,247,.5); background:rgba(140,107,247,.12); }
.pill.bootstrap { color:#79b8ff; border-color:rgba(88,166,255,.5); background:rgba(88,166,255,.12); }
.pill.component { color:#e3b341; border-color:rgba(210,153,34,.5); background:rgba(210,153,34,.12); }
.obadge {
  display:flex; align-items:center; gap:.42cqw; font-family:'JetBrains Mono',monospace;
  font-size:.95cqw; color:#aeb8c4;
}
.obadge svg { width:1cqw; height:1cqw; stroke:#8b949e; stroke-width:1.8; fill:none; }
.obadge b { color:#e6edf3; font-weight:500; }
.l-title { display:block; font-family:'Familjen Grotesk'; font-weight:700; font-size:2.3cqw; line-height:1.1; color:#e6edf3; text-decoration:none; margin-bottom:.55cqw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.l-title:hover { color:#00e6dc; }
.l-author { font-size:1.05cqw; color:#8b949e; }
.l-author a { color:#aeb8c4; text-decoration:none; }
.l-author a:hover { color:#00e6dc; }
.l-right { flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:.4cqw; padding-bottom:.15cqw; }
.l-mpn { font-family:'JetBrains Mono',monospace; font-size:.95cqw; color:#aeb8c4; display:none; }
.l-slug { font-family:'JetBrains Mono',monospace; font-size:.95cqw; color:#00b8b1; text-decoration:none; }
.l-slug:hover { color:#00e6dc; }

/* ---------- component mode ---------- */
.hero[data-mode=component] .textzone, .hero[data-mode=component] .window { display:none; }
.hero[data-mode=app] .comp-side, .hero[data-mode=app] .ctextzone { display:none; }
body[data-mode=app] .mode-component { display:none; }
body[data-mode=component] .mode-app { display:none; }
.comp-side { position:absolute; top:0; bottom:0; right:0; width:56%; z-index:1; }
.comp-img {
  position:absolute; inset:0; background-size:cover; background-position:center;
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 26%);
          mask-image:linear-gradient(90deg, transparent 0, #000 26%);
}
.hero[data-imgsrc=photo] .comp-img { background-image:url('comp/photo.png'); }
.hero[data-imgsrc=raytrace] .comp-img { background-image:url('comp/raytrace.png'); }
.hero[data-imgsrc=phong] .comp-img { background-image:url('comp/phong.png'); background-size:contain; background-repeat:no-repeat; background-position:center 40%; }
.hero[data-clayout=photo-left] .comp-side { left:0; right:auto; }
.hero[data-clayout=photo-left] .comp-img {
  -webkit-mask-image:linear-gradient(270deg, transparent 0, #000 26%);
          mask-image:linear-gradient(270deg, transparent 0, #000 26%);
}
.hero[data-clayout=photo-left] .ctextzone { left:auto; right:5.75cqw; }
.hero[data-clayout=photo-full] .comp-side { left:0; width:100%; }
.hero[data-clayout=photo-full] .comp-img { -webkit-mask-image:none; mask-image:none; }
.hero[data-clayout=photo-full] .comp-img::after {
  content:''; position:absolute; inset:0;
  background:linear-gradient(90deg, rgba(10,14,20,.88), rgba(10,14,20,.3) 55%, rgba(10,14,20,.12));
}
.ctextzone { position:absolute; left:5.75cqw; top:8.5cqw; width:36cqw; z-index:3; }
.c-chips { display:flex; gap:.8cqw; margin-bottom:2cqw; flex-wrap:wrap; }
.c-family {
  display:inline-block; font-family:'JetBrains Mono',monospace; font-size:.9cqw; letter-spacing:.12em;
  color:#00e6dc; background:rgba(5,9,14,.55); border:1px solid rgba(0,230,220,.3);
  border-radius:999px; padding:.35cqw 1cqw; text-transform:uppercase;
}
.c-mpnchip { color:#aeb8c4; border-color:rgba(255,255,255,.16); display:none; }
.c-name { font-family:'Familjen Grotesk'; font-weight:700; font-size:4.2cqw; line-height:1.02; letter-spacing:-.08cqw; color:#e6edf3; margin-bottom:.9cqw; text-shadow:0 2px 12px rgba(0,0,0,.5); }
.c-mpn { font-family:'JetBrains Mono',monospace; font-size:1.05cqw; color:#8b949e; margin-bottom:1cqw; display:none; }
.c-spec { font-size:1.5cqw; color:#aeb8c4; line-height:1.45; max-width:30cqw; text-shadow:0 1px 8px rgba(0,0,0,.5); }
.hero[data-mpn=eyebrow] .c-mpnchip { display:inline-block; }
.hero[data-mpn=under-title] .c-mpn { display:block; }
.hero[data-mpn=overlay] .l-mpn { display:block; }

/* guides */
.guide { position:absolute; left:0; right:0; top:66.6%; z-index:6; border-top:1.5px dashed rgba(0,230,220,.55); pointer-events:none; display:none; }
.guide span {
  position:absolute; right:1cqw; top:.4cqw; font-family:'JetBrains Mono',monospace; font-size:.8cqw;
  color:#00e6dc; background:rgba(10,14,20,.7); padding:.2cqw .6cqw; border-radius:.35cqw;
}
.hero.guides .guide { display:block; }

/* ---------- in-context previews (chrome copied 1:1 from git-wiki style.css) ---------- */
#contexts { display:flex; flex-wrap:wrap; gap:16px; width:100%; max-width:1000px; align-items:flex-start; }
.ctx { min-width:0; }
.ctxlabel { font-family:'JetBrains Mono',monospace; font-size:11px; color:var(--text-2); margin-bottom:6px; }
.ctxframe { background:#0d1117; border:1px solid var(--border); border-radius:10px; padding:14px; }
.clone-slot { position:absolute; inset:0; }
.clone-slot .hero { width:100%; border-radius:0; border:none; }
.heroslot .hero { width:100%; border-radius:8px; }
.hero.preview .guide { display:none !important; }
.hero.preview a { pointer-events:none; }

/* wiki type badges (style.css .badge) */
#contexts .badge { display:inline-block; padding:2px 10px; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.5px; border-radius:12px; line-height:1.6; text-align:center; }
#contexts .badge-app { background:rgba(56,139,253,.15); color:#58a6ff; border:1px solid rgba(56,139,253,.3); }
#contexts .badge-skill { background:rgba(188,140,255,.15); color:#bc8cff; border:1px solid rgba(188,140,255,.3); }
#contexts .badge-component { background:rgba(210,153,34,.15); color:#d29922; border:1px solid rgba(210,153,34,.3); }
#contexts .badge-bootstrap { background:rgba(0,184,176,.15); color:#00b8b0; border:1px solid rgba(0,184,176,.3); }

/* homepage marquee (style.css .popular-marquee / .mq-* / .ts-slide-*) */
.mqwrap { height:430px; overflow:hidden; position:relative; }
.mqwrap::before, .mqwrap::after { content:""; position:absolute; left:0; right:0; height:64px; z-index:3; pointer-events:none; }
.mqwrap::before { top:0; background:linear-gradient(to bottom,#0d1117,transparent); }
.mqwrap::after { bottom:0; background:linear-gradient(to top,#0d1117,transparent); }
.mqcol { transform:perspective(900px) rotateY(15deg); }
.mq-track { display:flex; flex-direction:column; gap:20px; will-change:transform; animation:mq-roll-up 130s linear infinite; }
.mqwrap:hover .mq-track { animation-play-state:paused; }
@keyframes mq-roll-up { from { transform:translateY(0); } to { transform:translateY(-50%); } }
.ts-slide { position:relative; aspect-ratio:16/10; display:block; overflow:hidden; background:#161b22; color:#fff; border-radius:14px; flex:none; text-decoration:none; }
.ts-hero-fallback { width:100%; height:100%; display:flex; align-items:center; justify-content:center; padding:32px; text-align:center; background:linear-gradient(135deg,#0f766e,#1d4ed8 60%,#6d28d9); font-size:26px; font-weight:700; color:#fff; font-family:'Familjen Grotesk'; }
.ts-slide-shade { position:absolute; inset:0; background:linear-gradient(to top, rgba(5,9,14,.95) 0%, rgba(5,9,14,.82) 28%, rgba(5,9,14,.5) 55%, rgba(5,9,14,0) 82%); }
.ts-slide-info { position:absolute; left:0; right:0; bottom:0; padding:20px 26px; }
.ts-slide-badges { display:flex; align-items:center; gap:10px; margin-bottom:12px; }
#contexts .ts-slide-badges .badge { background:rgba(5,9,14,.72); box-shadow:0 1px 4px rgba(0,0,0,.4); }
.ts-slide-score { display:inline-flex; align-items:center; gap:4px; font-size:12px; font-weight:600; color:#fff; background:rgba(5,9,14,.72); padding:2px 9px; border-radius:10px; box-shadow:0 1px 4px rgba(0,0,0,.4); }
.ts-slide-stars { display:inline-flex; align-items:center; gap:3px; font-size:12px; color:rgba(255,255,255,.85); background:rgba(5,9,14,.72); padding:2px 9px; border-radius:10px; box-shadow:0 1px 4px rgba(0,0,0,.4); }
.ts-slide-title { font-size:23px; font-weight:700; margin:6px 0 3px; color:#fff; font-family:'Familjen Grotesk'; text-shadow:0 1px 2px rgba(0,0,0,.85), 0 2px 14px rgba(0,0,0,.55); }
.ts-slide-brief { font-size:15px; line-height:1.5; color:rgba(255,255,255,.88); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; margin:0 0 4px; text-shadow:0 1px 2px rgba(0,0,0,.85), 0 2px 10px rgba(0,0,0,.6); }
.ts-slide-owner { font-size:12px; color:rgba(255,255,255,.72); text-shadow:0 1px 2px rgba(0,0,0,.85); }

/* index page card grid (style.css .page-grid / .page-card-*) */
.page-grid { display:grid; grid-template-columns:repeat(2, 1fr); gap:16px; }
.page-card { display:flex; flex-direction:column; text-decoration:none; color:#e6edf3; padding:0; overflow:hidden; background:#161b22; border:1px solid #30363d; border-radius:6px; transition:border-color .2s, box-shadow .2s; }
.page-card:hover { border-color:rgba(0,184,176,.3); box-shadow:0 4px 12px rgba(0,0,0,.2); }
.page-card-hero { aspect-ratio:16/10; background:#1c2128; display:flex; align-items:center; justify-content:center; overflow:hidden; border-bottom:1px solid #30363d; position:relative; }
.page-card-hero-placeholder { display:flex; align-items:center; justify-content:center; width:100%; height:100%; }
.page-card-body { padding:16px; display:flex; flex-direction:column; gap:6px; flex:1; }
.page-card-top { display:flex; align-items:center; gap:8px; }
.page-card-title { font-size:15px; font-weight:600; line-height:1.3; margin:0; }
.page-card-brief { color:#8b949e; font-size:13px; margin:0; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.page-card-meta { font-size:12px; color:#484f58; display:flex; align-items:center; gap:4px; margin-top:auto; }
.page-card-stars { display:inline-flex; align-items:center; gap:3px; }
.page-card-tags { display:flex; gap:4px; flex-wrap:wrap; margin-top:4px; }
.chip-sm { display:inline-block; padding:1px 8px; font-size:11px; background:rgba(0,184,176,.15); color:#00b8b0; border-radius:12px; border:1px solid rgba(0,184,176,.3); line-height:1.5; }
.page-card-install { margin-top:8px; padding:6px 8px; background:#0d1117; border:1px solid #30363d; border-radius:6px; font-family:'JetBrains Mono',monospace; font-size:11px; color:#8b949e; display:flex; align-items:center; gap:8px; }
.page-card-install code { background:transparent; color:#8b949e; flex:1; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
.dot-sep { color:#484f58; }

/* adom-desktop right bar (proposed v2 overlay style) */
.deskmock { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:10px; display:flex; flex-direction:column; gap:10px; }
.deskhead { font-family:'Familjen Grotesk'; font-weight:500; font-size:11px; letter-spacing:.07em; text-transform:uppercase; color:var(--text-2); }

/* render mode: ?render=1 strips all chrome for the server-side PNG shot */
body[data-render] header, body[data-render] #panel, body[data-render] #contexts,
body[data-render] #toasts, body[data-render] .guide { display:none !important; }
body[data-render] main, body[data-render] #stage { display:block; padding:0; overflow:hidden; }
body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; height:1000px; border-radius:0; border:none; box-shadow:none; }

/* generated-hero preview modal */
#genmodal { position:fixed; inset:0; z-index:99997; background:rgba(5,9,14,.8); display:none; align-items:center; justify-content:center; padding:32px; }
#genmodal.show { display:flex; }
.genbox { max-width:min(1000px, 92vw); background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px; box-shadow:0 24px 80px rgba(0,0,0,.6); }
.genbox img { display:block; width:100%; border-radius:8px; border:1px solid var(--border); }
.genrow { display:flex; align-items:center; gap:10px; margin-top:10px; }
.genrow .hint { margin:0; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.genrow a { color:var(--accent); font-size:13px; text-decoration:none; }
.genrow a:hover { color:var(--accent-bright); }
.btn-primary { border-color:var(--accent); background:rgba(0,184,176,.12); font-weight:500; }
.btn-primary:hover { background:rgba(0,184,176,.2); }
.btn-primary:disabled { opacity:.55; cursor:wait; }

/* tooltip + toast */
#tip {
  position:fixed; z-index:99999; background:var(--overlay); border:1px solid var(--border);
  color:var(--text); font-size:12px; line-height:1.4; padding:6px 9px; border-radius:8px;
  max-width:240px; pointer-events:none; opacity:0; transform:translateY(3px);
  transition:opacity .18s ease, transform .18s ease; text-transform:none;
}
#tip.show { opacity:1; transform:none; }
#toasts { position:fixed; left:50%; bottom:18px; transform:translateX(-50%); z-index:99998; display:flex; flex-direction:column-reverse; gap:8px; }
.toast {
  display:flex; align-items:center; gap:8px; background:var(--overlay); border:1px solid var(--border);
  border-left:3px solid var(--accent); border-radius:8px; padding:9px 13px; font-size:13px;
  box-shadow:0 8px 30px rgba(0,0,0,.45); animation:tin .2s ease;
}
.toast.success { border-left-color:var(--green); }
.toast.error { border-left-color:var(--red); }
@keyframes tin { from { opacity:0; transform:translateY(6px);} }
</style>
</head>
<body>

<header>
  <div class="brand">
    <svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><line x1="3" y1="14.5" x2="21" y2="14.5"/><line x1="6" y1="17.5" x2="13" y2="17.5"/></svg>
    <span class="name">Hero Studio</span>
    <span class="sub">&middot; lower-third billboard lab</span>
  </div>
  <div class="vsep"></div>
  <div class="subject"><b id="subj-title">...</b><span class="meta" id="subj-meta"></span></div>
  <div class="actions">
    <button class="iconbtn" id="btn-guides" data-tip="Toggle the lower-third guide line on the canvas">
      <svg viewBox="0 0 24 24"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z"/><circle cx="12" cy="12" r="2.6"/></svg>
    </button>
    <button class="iconbtn" id="btn-rand" data-tip="Randomize package, layout and theme">
      <svg viewBox="0 0 24 24"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/></svg>
    </button>
  </div>
</header>

<main>
  <div id="panel">
    <h3>What are you making</h3>
    <div class="optgrid" id="grp-mode">
      <button class="opt" data-v="app">App / skill</button>
      <button class="opt" data-v="component">Component</button>
    </div>

    <div class="mode-component">
    <h3>Component</h3>
    <select id="sel-comp" data-tip="Sample parts; sm02b-srss-tb is a real wiki component page"></select>

    <h3>Image source (fallback ladder)</h3>
    <div class="optgrid" id="grp-imgsrc">
      <button class="opt" data-v="photo">Real photo</button>
      <button class="opt" data-v="raytrace">Raytraced render</button>
      <button class="opt" data-v="phong">Phong 3D</button>
    </div>
    <div class="hint">The ladder: real-world photo first, else a raytraced render, else the plain 3D viewport shot (last resort).</div>

    <h3>Photo placement</h3>
    <div class="optgrid" id="grp-clayout">
      <button class="opt" data-v="photo-right">Full-bleed right</button>
      <button class="opt" data-v="photo-left">Full-bleed left</button>
      <button class="opt" data-v="photo-full">Full background</button>
    </div>

    <h3>MPN placement</h3>
    <div class="optgrid" id="grp-mpn">
      <button class="opt" data-v="eyebrow">Family chip row</button>
      <button class="opt" data-v="under-title">Under the name</button>
      <button class="opt" data-v="overlay">In the overlay</button>
    </div>
    </div>

    <div class="mode-app">
    <h3>Package (live wiki data)</h3>
    <select id="sel-page" data-tip="Real pages from adom-wiki page trending: title, type, score, stars, author"></select>

    <h3>Screenshot placement</h3>
    <div class="optgrid" id="grp-layout">
      <button class="opt" data-v="bleed-bottom">Bleed bottom</button>
      <button class="opt" data-v="bleed-right">Bleed right</button>
      <button class="opt" data-v="bleed-top">Bleed top</button>
      <button class="opt" data-v="bleed-corner-br">Corner lower-right</button>
      <button class="opt" data-v="bleed-corner-tr">Corner upper-right</button>
      <button class="opt" data-v="contained">Contained</button>
      <button class="opt" data-v="mirror">Mirror (text right)</button>
    </div>
    <div class="hint">The shot may run under the lower third; the overlay scrim covers it. That is the new rule.</div>
    </div>

    <h3>Background theme</h3>
    <div class="swatches" id="grp-theme">
      <div class="swatch sw-midnight" data-v="midnight" data-tip="Midnight: near-black gradient + faint dot grid"></div>
      <div class="swatch sw-pcb" data-v="pcb" data-tip="PCB traces: deep teal + circuit field"></div>
      <div class="swatch sw-onion" data-v="onion" data-tip="Onion: navy + concentric arcs"></div>
      <div class="swatch sw-pill" data-v="pill" data-tip="Pill field: scattered capsule outlines from the brand tiling folder"></div>
      <div class="swatch sw-grid" data-v="grid" data-tip="Square grid: fine outlined-square field from the brand tiling folder"></div>
      <div class="swatch sw-grad" data-v="gradient" data-tip="Gradient: bold teal to blue to purple, for flagship launches"></div>
    </div>

    <h3>Gradient angle</h3>
    <input type="range" id="rng-angle" min="0" max="360" step="5" value="155" data-tip="Rotate the base gradient so each hero feels unique while staying on-brand">
    <div class="hint">Angle: <span id="angle-val">155</span> deg. Brand default is 155.</div>

    <div class="mode-app">
    <h3>Image-zone text</h3>
    <label class="chk"><input type="checkbox" id="chk-logo"> Adom wordmark</label>
    <label class="chk"><input type="checkbox" id="chk-title"> Title (big headline)</label>
    <label class="chk"><input type="checkbox" id="chk-subtitle"> Value-prop subtitle</label>
    <label class="chk"><input type="checkbox" id="chk-description"> Detail line</label>

    <h3>Title placement</h3>
    <div class="optgrid" id="grp-titlemode">
      <button class="opt" data-v="overlay-only">Overlay only</button>
      <button class="opt" data-v="image-only">Image only</button>
      <button class="opt" data-v="both">Both</button>
    </div>
    <div class="hint">John's open question: if the big title lives in the image, the overlay title is redundant. Flip these to compare.</div>
    </div>

    <h3>Overlay content</h3>
    <label class="chk"><input type="checkbox" id="chk-pill"> Type pill</label>
    <label class="chk"><input type="checkbox" id="chk-scores"> Trending + stars (dynamic)</label>
    <label class="chk"><input type="checkbox" id="chk-author"> Author</label>
    <label class="chk"><input type="checkbox" id="chk-slug"> Wiki slug, lower right</label>
    <button class="btn btn-primary" id="btn-generate" data-tip="Render the current hero to a 2000x1250 PNG on the server, with provenance metadata">Generate hero PNG</button>
    <button class="btn" id="btn-reset">Reset to proposed default</button>
  </div>

  <div id="stage">
    <div id="hero" class="hero" data-theme="midnight" data-layout="bleed-bottom">
      <div class="bg"></div>

      <div class="textzone">
        <div class="wordmark" id="el-logo">
          <span class="wmark"></span>
        </div>
        <div class="h-title" id="el-title"></div>
        <div class="h-sub" id="el-sub"></div>
        <div class="h-desc" id="el-desc"></div>
      </div>

      <div class="comp-side"><div class="comp-img"></div></div>
      <div class="ctextzone">
        <div class="wordmark"><span class="wmark"></span></div>
        <div class="c-chips">
          <span class="c-family" id="c-family"></span>
          <span class="c-family c-mpnchip" id="c-mpn-chip"></span>
        </div>
        <div class="c-name" id="c-name"></div>
        <div class="c-mpn" id="c-mpn-under"></div>
        <div class="c-spec" id="c-spec"></div>
      </div>

      <div class="window">
        <div class="titlebar">
          <span class="t" id="win-label"></span>
          <span class="winbtns">
            <svg viewBox="0 0 10 10"><line x1="1" y1="5" x2="9" y2="5"/></svg>
            <svg viewBox="0 0 10 10"><rect x="1.5" y="1.5" width="7" height="7"/></svg>
            <svg viewBox="0 0 10 10"><line x1="1.5" y1="1.5" x2="8.5" y2="8.5"/><line x1="8.5" y1="1.5" x2="1.5" y2="8.5"/></svg>
          </span>
        </div>
        <div class="appbody">
          <div class="sidebar">
            <div class="srow on"></div><div class="srow"></div><div class="srow"></div>
            <div class="srow"></div><div class="srow"></div><div class="srow"></div><div class="srow"></div>
          </div>
          <div class="content">
            <div class="crow"><span class="chipk" style="width:22%"></span><span class="chipk" style="width:12%"></span><span class="chipk" style="width:16%"></span></div>
            <div class="bars">
              <div class="bar" style="height:42%"></div><div class="bar" style="height:66%"></div>
              <div class="bar" style="height:35%"></div><div class="bar" style="height:82%"></div>
              <div class="bar" style="height:58%"></div><div class="bar" style="height:91%"></div>
              <div class="bar" style="height:49%"></div><div class="bar" style="height:73%"></div>
            </div>
            <div class="tbl">
              <div class="trow"><span class="tcell" style="flex:2"></span><span class="tcell" style="flex:1"></span><span class="tcell" style="flex:1"></span></div>
              <div class="trow"><span class="tcell" style="flex:2"></span><span class="tcell" style="flex:1"></span><span class="tcell" style="flex:1"></span></div>
              <div class="trow"><span class="tcell" style="flex:2"></span><span class="tcell" style="flex:1"></span><span class="tcell" style="flex:1"></span></div>
            </div>
          </div>
        </div>
      </div>

      <div class="lower">
        <div class="lowerin">
          <div class="lmain">
            <div class="badges">
              <span class="pill app" id="l-pill">APP</span>
              <span class="obadge" id="l-trend" data-tip="Composite trending score from the wiki, recomputed continuously">
                <svg viewBox="0 0 24 24"><polyline points="3 17 9 11 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
                <b></b>
              </span>
              <span class="obadge" id="l-stars" data-tip="Live star count">
                <svg viewBox="0 0 24 24"><path d="M12 3l2.7 5.6 6.1.8-4.5 4.2 1.1 6-5.4-3-5.4 3 1.1-6L3.2 9.4l6.1-.8z"/></svg>
                <b></b>
              </span>
              <span class="obadge" id="l-installs" data-tip="Installs in the last 30 days">
                <svg viewBox="0 0 24 24"><path d="M12 4v11"/><polyline points="7 10 12 15 17 10"/><path d="M4 19h16"/></svg>
                <b></b>
              </span>
            </div>
            <a class="l-title" id="l-title" target="_blank" href="#"></a>
            <div class="l-author" id="l-author"></div>
          </div>
          <div class="l-right">
            <span class="l-mpn" id="l-mpn"></span>
            <a class="l-slug" id="l-slug" target="_blank" href="#"></a>
          </div>
        </div>
      </div>

      <div class="guide"><span>overlay zone: lower 1/3, true text only</span></div>
    </div>

    <div id="contexts">
      <div class="ctx" style="flex:1 1 46%">
        <div class="ctxlabel">In context: wiki homepage marquee</div>
        <div class="ctxframe mqwrap"><div class="mqcol"><div class="mq-track" id="mq-track"></div></div></div>
      </div>
      <div class="ctx" style="flex:1 1 46%">
        <div class="ctxlabel">In context: adom-desktop right bar (proposed v2 style)</div>
        <div class="deskmock">
          <div class="deskhead">Trending on the wiki</div>
          <div class="heroslot"></div>
        </div>
      </div>
      <div class="ctx" style="flex:1 1 100%">
        <div class="ctxlabel">In context: wiki index page (/apps grid)</div>
        <div class="ctxframe"><div class="page-grid" id="wikigrid"></div></div>
      </div>
    </div>
  </div>
</main>

<div id="tip"></div>
<div id="toasts"></div>
<div id="genmodal">
  <div class="genbox">
    <img id="gen-img" alt="Generated hero">
    <div class="genrow">
      <div class="hint" id="gen-path"></div>
      <a id="gen-open" target="_blank" href="#">Open full size</a>
      <a href="#" id="gen-close">Close</a>
    </div>
  </div>
</div>

<script>
if (new URLSearchParams(location.search).has('render')) document.body.dataset.render = '1';

/* console relay: the AI reads GET /console instead of DevTools */
(function () {
  const post = (level, args) => fetch('console', { method:'POST', headers:{'Content-Type':'application/json'},
    body: JSON.stringify({ level, text: args.map(a => { try { return typeof a === 'string' ? a : JSON.stringify(a); } catch { return String(a); } }).join(' ') }) }).catch(()=>{});
  ['log','warn','error'].forEach(k => { const o = console[k].bind(console); console[k] = (...a) => { o(...a); post(k, a); }; });
  window.onerror = (m, s, l) => post('error', [m + ' @' + s + ':' + l]);
  window.addEventListener('unhandledrejection', e => post('error', ['unhandledrejection: ' + e.reason]));
})();

let pages = [];
let comps = [];
let state = null;

const $ = (id) => document.getElementById(id);
const send = (patch) => fetch('state', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(patch) }).catch(()=>{});

function pageByKey(key) { return pages.find(p => p.owner + '/' + p.slug === key) || pages[0]; }
function compById(id) { return comps.find(c => c.id === id) || comps[0]; }
/* the object the overlay, header, and context cards render: page or component */
function displayEntry() {
  if (state.mode === 'component' && comps.length) {
    const c = compById(state.comp.id);
    return { owner: c.owner, slug: c.slug, type: 'component', title: c.name, brief: c.spec,
      author: c.author, score: c.score, stars: c.stars, installs30d: null,
      tags: [c.mfr.toLowerCase()], updated_at: null, mpn: c.mpn };
  }
  return pageByKey(state.page);
}

function firstSentence(s, max) {
  let t = (s || '').split(/(?<=\.)\s/)[0] || s || '';
  if (t.length > max) t = t.slice(0, max).replace(/\s+\S*$/, '') + '...';
  return t;
}

function accentize(text) {
  const words = text.split(' ');
  if (words.length < 3) return '<span class="accent">' + text + '</span>';
  return '<span class="accent">' + words.slice(0, 2).join(' ') + '</span> ' + words.slice(2).join(' ');
}

function render() {
  if (!state || !pages.length) return;
  const p = pageByKey(state.page);
  const d = displayEntry();
  const mode = state.mode || 'app';
  const hero = $('hero');
  document.body.dataset.mode = mode;
  hero.dataset.mode = mode;
  hero.dataset.theme = state.theme;
  hero.dataset.layout = state.layout;
  hero.dataset.clayout = state.comp.layout;
  hero.dataset.imgsrc = state.comp.imgSrc;
  hero.dataset.mpn = state.comp.mpn;
  hero.classList.toggle('guides', !!state.guides);
  hero.style.setProperty('--ga', (state.gradAngle || 155) + 'deg');
  $('rng-angle').value = state.gradAngle || 155;
  $('angle-val').textContent = state.gradAngle || 155;

  // header subject
  $('subj-title').textContent = d.title;
  $('subj-meta').textContent = mode === 'component'
    ? d.type + ' · score ' + d.score + ' · MPN ' + d.mpn
    : d.type + ' · score ' + d.score + ' · ' + d.stars + ' stars · ' + d.installs30d + ' installs/30d';
  $('btn-guides').classList.toggle('on', !!state.guides);

  // component image zone
  if (mode === 'component' && comps.length) {
    const c = compById(state.comp.id);
    $('c-family').textContent = c.family;
    $('c-mpn-chip').textContent = c.mpn;
    $('c-name').textContent = c.name;
    $('c-mpn-under').textContent = c.mpn;
    $('c-spec').textContent = c.spec;
  }

  // image zone
  const showTitleInImage = state.show.title && state.titleMode !== 'overlay-only';
  $('el-logo').style.display = state.show.logo ? 'flex' : 'none';
  $('el-title').style.display = showTitleInImage ? 'block' : 'none';
  $('el-title').textContent = p.title;
  $('el-sub').style.display = state.show.subtitle ? 'block' : 'none';
  $('el-sub').innerHTML = accentize(firstSentence(p.brief, 80));
  $('el-desc').style.display = state.show.description ? 'block' : 'none';
  $('el-desc').textContent = firstSentence(p.brief.slice(80), 110);
  $('win-label').textContent = p.title + ' · Adom webview';

  // lower third overlay
  const showTitleInOverlay = mode === 'component' || state.titleMode !== 'image-only';
  $('l-pill').style.display = state.overlay.pill ? '' : 'none';
  $('l-pill').textContent = d.type.toUpperCase();
  $('l-pill').className = 'pill ' + d.type;
  $('l-trend').style.display = state.overlay.scores ? '' : 'none';
  $('l-trend').querySelector('b').textContent = d.score.toLocaleString('en-US');
  $('l-stars').style.display = state.overlay.scores ? '' : 'none';
  $('l-stars').querySelector('b').textContent = d.stars;
  $('l-installs').style.display = (state.overlay.scores && d.installs30d != null) ? '' : 'none';
  $('l-installs').querySelector('b').textContent = d.installs30d;
  const url = 'https://wiki.adom.inc/' + d.owner + '/' + d.slug;
  $('l-title').style.display = showTitleInOverlay ? 'block' : 'none';
  $('l-title').textContent = d.title;
  $('l-title').href = url;
  $('l-author').style.display = state.overlay.author ? '' : 'none';
  $('l-author').innerHTML = 'by <a href="' + url + '" target="_blank">' + d.author + '</a>';
  $('l-mpn').textContent = d.mpn || '';
  $('l-slug').style.display = state.overlay.slug ? '' : 'none';
  $('l-slug').textContent = 'wiki.adom.inc/' + d.owner + '/' + d.slug;
  $('l-slug').href = url;

  // controls reflect state
  document.querySelectorAll('#grp-mode .opt').forEach(b => b.classList.toggle('on', b.dataset.v === mode));
  if (comps.length) $('sel-comp').value = compById(state.comp.id).id;
  document.querySelectorAll('#grp-imgsrc .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.comp.imgSrc));
  document.querySelectorAll('#grp-clayout .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.comp.layout));
  document.querySelectorAll('#grp-mpn .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.comp.mpn));
  $('sel-page').value = state.page;
  document.querySelectorAll('#grp-layout .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.layout));
  document.querySelectorAll('#grp-theme .swatch').forEach(b => b.classList.toggle('on', b.dataset.v === state.theme));
  document.querySelectorAll('#grp-titlemode .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.titleMode));
  $('chk-logo').checked = state.show.logo;
  $('chk-title').checked = state.show.title;
  $('chk-subtitle').checked = state.show.subtitle;
  $('chk-description').checked = state.show.description;
  $('chk-pill').checked = state.overlay.pill;
  $('chk-scores').checked = state.overlay.scores;
  $('chk-author').checked = state.overlay.author;
  $('chk-slug').checked = state.overlay.slug;

  syncPreviews();
}

/* in-context previews: markup mirrors git-wiki lib/templates.js (popularMarquee + categoryListPage) */
const STAR12 = '<svg width="12" height="12" viewBox="0 0 24 24" fill="#d29922" stroke="#d29922" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>';
const TREND13 = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>';
const IMGPH = '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#484f58" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>';

function relTime(iso) {
  if (!iso) return '';
  const s = (Date.now() - new Date(iso).getTime()) / 1000;
  if (s < 3600) return Math.max(1, Math.floor(s / 60)) + 'm ago';
  if (s < 86400) return Math.floor(s / 3600) + 'h ago';
  return Math.floor(s / 86400) + 'd ago';
}

function marqueeTile(p, isLive) {
  const heroPart = isLive ? '<div class="clone-slot"></div>'
    : '<div class="ts-hero-fallback"><span>' + p.title + '</span></div>';
  return '<a class="ts-slide mq-slide">' + heroPart +
    '<div class="ts-slide-shade"></div><div class="ts-slide-info">' +
    '<div class="ts-slide-badges"><span class="badge badge-' + p.type + '">' + p.type + '</span>' +
    (p.score > 0 ? '<span class="ts-slide-score">' + TREND13 + ' ' + p.score + '</span>' : '') +
    (p.stars > 0 ? '<span class="ts-slide-stars">' + STAR12 + ' ' + p.stars + '</span>' : '') +
    '</div><h3 class="ts-slide-title">' + p.title + '</h3>' +
    (p.brief ? '<p class="ts-slide-brief">' + p.brief + '</p>' : '') +
    '<span class="ts-slide-owner">' + p.owner + '</span></div></a>';
}

function indexCard(p, isLive) {
  const hero = isLive ? '<div class="clone-slot"></div>'
    : '<div class="page-card-hero-placeholder">' + IMGPH + '</div>';
  return '<a class="page-card">' +
    '<div class="page-card-hero">' + hero + '</div>' +
    '<div class="page-card-body">' +
    '<div class="page-card-top"><span class="badge badge-' + p.type + '">' + p.type + '</span></div>' +
    '<h3 class="page-card-title">' + p.title + '</h3>' +
    (p.brief ? '<p class="page-card-brief">' + p.brief + '</p>' : '') +
    '<div class="page-card-meta"><span>' + p.owner + '</span>' +
    (p.updated_at ? '<span class="dot-sep">&middot;</span><span>' + relTime(p.updated_at) + '</span>' : '') +
    (p.stars > 0 ? '<span class="dot-sep">&middot;</span><span class="page-card-stars">' + STAR12 + ' ' + p.stars + '</span>' : '') +
    '</div>' +
    (p.tags && p.tags.length ? '<div class="page-card-tags">' + p.tags.map(t => '<span class="chip-sm">' + t + '</span>').join('') + '</div>' : '') +
    '<div class="page-card-install"><code>adom-wiki pkg install ' + p.owner + '/' + p.slug + '</code></div>' +
    '</div></a>';
}

function cloneHero(imageOnly) {
  const c = $('hero').cloneNode(true);
  c.removeAttribute('id');
  c.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
  c.classList.add('preview');
  c.classList.remove('guides');
  if (imageOnly) { const l = c.querySelector('.lower'); if (l) l.remove(); }
  return c;
}

function syncPreviews() {
  const p = displayEntry();
  const nb = pages.filter(x => x.owner + '/' + x.slug !== p.owner + '/' + p.slug).slice(0, 3);
  // marquee: live tile among real neighbors, content doubled for the -50% loop
  const seq = marqueeTile(p, true) + nb.map(x => marqueeTile(x)).join('');
  $('mq-track').innerHTML = seq + seq;
  // index grid: live card first, then real neighbor cards (no-hero placeholder icon)
  $('wikigrid').innerHTML = indexCard(p, true) + nb.map(x => indexCard(x)).join('');
  // wiki surfaces get the IMAGE PART only (the wiki overlays its own text);
  // the desk bar gets the full v2 hero with its baked-in text overlay
  document.querySelectorAll('#contexts .clone-slot').forEach(s => s.appendChild(cloneHero(true)));
  document.querySelectorAll('#contexts .heroslot').forEach(s => { s.innerHTML = ''; s.appendChild(cloneHero(false)); });
}

let seenToastSeq = 0;
function handleToast(t) {
  if (t && t.seq > seenToastSeq) { seenToastSeq = t.seq; toast(t.text, t.kind); }
}

function toast(text, kind) {
  const el = document.createElement('div');
  el.className = 'toast ' + (kind || 'info');
  el.textContent = text;
  $('toasts').appendChild(el);
  setTimeout(() => el.remove(), 4000);
}

/* wire controls: server owns the truth, SSE echoes back */
document.querySelectorAll('#grp-mode .opt').forEach(b => b.addEventListener('click', () => send({ mode: b.dataset.v })));
$('sel-comp').addEventListener('change', e => send({ comp: { id: e.target.value } }));
document.querySelectorAll('#grp-imgsrc .opt').forEach(b => b.addEventListener('click', () => send({ comp: { imgSrc: b.dataset.v } })));
document.querySelectorAll('#grp-clayout .opt').forEach(b => b.addEventListener('click', () => send({ comp: { layout: b.dataset.v } })));
document.querySelectorAll('#grp-mpn .opt').forEach(b => b.addEventListener('click', () => send({ comp: { mpn: b.dataset.v } })));
$('sel-page').addEventListener('change', e => { send({ page: e.target.value }); });
document.querySelectorAll('#grp-layout .opt').forEach(b => b.addEventListener('click', () => send({ layout: b.dataset.v })));
document.querySelectorAll('#grp-theme .swatch').forEach(b => b.addEventListener('click', () => send({ theme: b.dataset.v })));
document.querySelectorAll('#grp-titlemode .opt').forEach(b => b.addEventListener('click', () => send({ titleMode: b.dataset.v })));
$('chk-logo').addEventListener('change', e => send({ show: { logo: e.target.checked } }));
$('chk-title').addEventListener('change', e => send({ show: { title: e.target.checked } }));
$('chk-subtitle').addEventListener('change', e => send({ show: { subtitle: e.target.checked } }));
$('chk-description').addEventListener('change', e => send({ show: { description: e.target.checked } }));
$('chk-pill').addEventListener('change', e => send({ overlay: { pill: e.target.checked } }));
$('chk-scores').addEventListener('change', e => send({ overlay: { scores: e.target.checked } }));
$('chk-author').addEventListener('change', e => send({ overlay: { author: e.target.checked } }));
$('chk-slug').addEventListener('change', e => send({ overlay: { slug: e.target.checked } }));
$('btn-guides').addEventListener('click', () => send({ guides: !state.guides }));
let angleTimer = null;
$('rng-angle').addEventListener('input', e => {
  $('angle-val').textContent = e.target.value;
  $('hero').style.setProperty('--ga', e.target.value + 'deg'); // instant local feedback
  clearTimeout(angleTimer);
  angleTimer = setTimeout(() => send({ gradAngle: +e.target.value }), 120);
});
$('btn-rand').addEventListener('click', () => {
  const themes = ['midnight','pcb','onion','pill','grid','gradient'];
  const angles = [65, 100, 155, 210, 245, 290, 335];
  const theme = themes[Math.floor(Math.random()*themes.length)];
  const gradAngle = angles[Math.floor(Math.random()*angles.length)];
  if (state.mode === 'component') {
    const clayouts = ['photo-right','photo-left','photo-full'];
    const srcs = ['photo','raytrace','phong'];
    const mpns = ['eyebrow','under-title','overlay'];
    const c = comps[Math.floor(Math.random() * comps.length)];
    send({ theme, gradAngle, comp: { id: c.id, layout: clayouts[Math.floor(Math.random()*clayouts.length)],
      imgSrc: srcs[Math.floor(Math.random()*srcs.length)], mpn: mpns[Math.floor(Math.random()*mpns.length)] } });
    toast('Randomized: ' + c.name, 'info');
  } else {
    const layouts = ['bleed-bottom','bleed-right','bleed-top','bleed-corner-br','bleed-corner-tr','contained','mirror'];
    const p = pages[Math.floor(Math.random() * pages.length)];
    send({ page: p.owner + '/' + p.slug, layout: layouts[Math.floor(Math.random()*layouts.length)], theme, gradAngle });
    toast('Randomized: ' + p.title, 'info');
  }
});
let seenGenSeq = 0;
function showGenerated(g) {
  $('gen-img').src = g.url + '?t=' + g.ts;
  $('gen-path').textContent = g.file;
  $('gen-open').href = g.url;
  $('genmodal').classList.add('show');
}
/* fires for local clicks AND for API-triggered generates (SSE or poll), deduped by seq */
function handleGen(g) {
  if (g && g.ok && g.seq > seenGenSeq) { seenGenSeq = g.seq; showGenerated(g); }
}
$('gen-close').addEventListener('click', e => { e.preventDefault(); $('genmodal').classList.remove('show'); });
$('genmodal').addEventListener('click', e => { if (e.target === $('genmodal')) $('genmodal').classList.remove('show'); });
$('btn-generate').addEventListener('click', () => {
  const b = $('btn-generate');
  b.disabled = true; b.textContent = 'Generating...';
  toast('Rendering the hero on the server', 'info');
  fetch('generate', { method: 'POST' }).then(r => r.json()).then(g => {
    b.disabled = false; b.textContent = 'Generate hero PNG';
    if (g.ok) handleGen(g); // success toast arrives via the live channel
    else toast('Generate failed: ' + (g.error || 'unknown') + (g.hint ? '. ' + g.hint : ''), 'error');
  }).catch(() => { b.disabled = false; b.textContent = 'Generate hero PNG'; toast('Generate failed: network error', 'error'); });
});

$('btn-reset').addEventListener('click', () => {
  send({ mode:'app', layout:'bleed-bottom', theme:'midnight', titleMode:'overlay-only', gradAngle:155,
    comp:{ id:'sm02b-srss-tb', imgSrc:'photo', layout:'photo-right', mpn:'under-title' },
    show:{ logo:true, title:true, subtitle:true, description:false },
    overlay:{ pill:true, scores:true, author:true, slug:true }, guides:true });
  toast('Reset to the proposed default: overlay title, image keeps value-prop', 'success');
});

/* tooltips: fixed div, 600ms delay, clamped to viewport on both axes */
(function () {
  const tip = $('tip');
  let timer = null;
  document.addEventListener('mouseover', e => {
    const t = e.target.closest('[data-tip]');
    if (!t) return;
    clearTimeout(timer);
    timer = setTimeout(() => {
      tip.textContent = t.getAttribute('data-tip');
      tip.classList.add('show');
      const r = t.getBoundingClientRect();
      let x = r.left + 14, y = r.bottom + 10;
      const w = tip.offsetWidth, h = tip.offsetHeight, M = 8;
      if (y + h > innerHeight - M) y = r.top - h - 10;
      x = Math.max(M, Math.min(x, innerWidth - w - M));
      y = Math.max(M, Math.min(y, innerHeight - h - M));
      tip.style.left = x + 'px'; tip.style.top = y + 'px';
    }, 600);
  });
  document.addEventListener('mouseout', e => {
    if (e.target.closest && e.target.closest('[data-tip]')) { clearTimeout(timer); tip.classList.remove('show'); }
  });
})();

/* boot: load real pages, subscribe to the live channel */
Promise.all([
  fetch('api/pages').then(r => r.json()),
  fetch('api/components').then(r => r.json()),
  fetch('state').then(r => r.json()),
]).then(([pg, cp, st]) => {
  pages = pg;
  comps = cp;
  state = st;
  seenToastSeq = (st._toast && st._toast.seq) || 0; // don't replay a stale toast on load
  seenGenSeq = (st._gen && st._gen.seq) || 0;       // nor a stale generated modal
  const sel = $('sel-page');
  sel.innerHTML = pages.map(p =>
    '<option value="' + p.owner + '/' + p.slug + '">' + p.title + ' (' + p.type + ')</option>').join('');
  $('sel-comp').innerHTML = comps.map(c =>
    '<option value="' + c.id + '">' + c.name + (c.sample ? ' (sample)' : '') + '</option>').join('');
  render();
  console.log('hero-studio ready: ' + pages.length + ' live pages');
  if (location.hash) {
    const el = document.querySelector(location.hash);
    if (el) setTimeout(() => el.scrollIntoView({ block: 'start' }), 300);
  }
  const es = new EventSource('events');
  es.addEventListener('state', e => { state = JSON.parse(e.data); render(); });
  es.addEventListener('toast', e => { handleToast(JSON.parse(e.data)); });
  es.addEventListener('generated', e => { handleGen(JSON.parse(e.data)); });
  // poll fallback in case the proxy buffers the event stream
  setInterval(() => {
    fetch('state').then(r => r.json()).then(st => {
      handleToast(st._toast);
      handleGen(st._gen);
      if (JSON.stringify(st) !== JSON.stringify(state)) { state = st; render(); }
    }).catch(()=>{});
  }, 1000);
});
</script>
</body>
</html>