master
Colby Knox Merge PR #181: Add circuit generators that follow rendered text contours b00fdcb 18d ago
<!doctype html>
<html lang="en">
<head>
<script>
/* boot probe: relays load + first JS error to the server console, before anything else can crash */
(function(){
  var post = function(t){ try { fetch('console', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({level:'probe', text:t})}).catch(function(){}); } catch(e){} };
  post('boot ' + location.href + ' ua=' + navigator.userAgent.slice(0,60));
  window.addEventListener('error', function(e){ post('jserror ' + (e.message||'') + ' @ ' + (e.filename||'') + ':' + (e.lineno||'')); });
  window.addEventListener('unhandledrejection', function(e){ post('unhandledrejection ' + String(e.reason).slice(0,200)); });
})();
</script>

<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>'>
<link rel="stylesheet" href="studio.css">
<link rel="stylesheet" href="hero.css">
</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>
  </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="hbtn" id="btn-rand" data-tip="Random layout and background: a fresh starting point">
      <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>
      <span>Surprise me</span>
    </button>
    <button class="hbtn" id="btn-guides" data-tip="Show the layout guide lines on the hero">Guides</button>
    <button class="hbtn" id="btn-zen" data-tip="Hide everything but the hero (Esc restores)">Focus</button>
    <button class="hbtn" id="wiz-classic" data-tip="The full control panel, for fine-grained edits">Classic</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>Showcase placement</h3>
    <div class="hint">The showcase image is the app window shown in the hero. One word, everywhere, per hero-studio#1.</div>
    <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>
      <button class="opt" data-v="showcase" data-tip="Adom-USB style: a large showcase behind a gradient scrim. The title is STILL the message; the scrim exists so the text column keeps its white space">Showcase</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 class="hint" id="slot-size" data-tip="Click to copy the pixel size">measuring showcase image slot...</div>

    <h3>Showcase content</h3>
    <input type="file" id="showcase-file" accept="image/*">
    <label class="chk"><input type="checkbox" id="showcase-full"> Use the imported image as the FULL hero</label>
    <label class="chk" id="stamp-logo-row" style="display:none" data-tip="Bakes the ONE standard wordmark (size and corner identical to every composed hero) over the import. Leave the logo OUT of your import and stamp it here; note this disables pixel-identical passthrough since something now composes on top."><input type="checkbox" id="chk-stamp-logo"> Stamp the standard Adom wordmark</label>
    <label class="chk"><input type="checkbox" id="showcase-cover"> Crop to fill (cover) instead of letterboxing a mismatched shot</label>
    <label class="chk" data-tip="Real window screenshots already have a real titlebar; stacking the fake one above it reads as fake. Auto-off when an import is present; the CSS mock keeps it."><input type="checkbox" id="showcase-chrome"> Draw the fake window titlebar</label>
    <label class="chk"><input type="checkbox" id="chk-mqpreview"> Show the homepage-marquee overlay over the hero (judge composition and title redundancy; never exported)</label>
    <button class="btn" id="showcase-pull">Pull real screenshot from the page</button>
    <button class="btn" id="showcase-clear">Clear import</button>

    <h3>Page fields</h3>
    <div class="hint">The human at the gate is best placed to catch page-level issues (hero-studio#20). Edits preview live; Apply writes them to the wiki (or stashes them for the first publish), and Copy Changelog Prompt hands the full session's changes back to the AI that owns the package working copy.</div>
    <div class="hint">Title</div>
    <input type="text" id="pf-title" class="pf-in">
    <div class="hint">Brief (the subtitle bakes from its first sentence)</div>
    <textarea id="pf-brief" class="pf-in" rows="3"></textarea>
    <div class="hint">Tags (comma separated)</div>
    <input type="text" id="pf-tags" class="pf-in">
    <button class="btn" id="pf-apply">Apply changes to wiki</button>
    <button class="btn" id="pf-prompt">Copy Changelog Prompt</button>
    <div class="hint" id="pf-status"></div>
    <div class="hint">Import a real screenshot into the showcase image instead of the CSS mock (apps show their real UI). Full-hero mode is the re-stamp path: bring in an existing hero image, ratify it here as the human, and the export carries the studio provenance stamp. The type pill and slug still bake on top.</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 class="swatch sw-aurora" data-v="aurora" data-tip="Aurora: layered soft radials"></div>
      <div class="swatch sw-mesh" data-v="mesh" data-tip="Mesh: conic color fields, angle follows the gradient slider"></div>
      <div class="swatch sw-circuitry" data-v="circuitry" data-tip="Circuitry: procedurally generated PCB traces, gently different every roll"></div>
      <button class="swatch sw-circuit-field" data-v="circuit-field" aria-label="Routed field" data-tip="Continuous traces routed around your text and showcase"></button>
      <button class="swatch sw-circuit-tile" data-v="circuit-tile" aria-label="Seamless traces" data-tip="Seamless routed tile with quiet space around your composition"></button>
      <div class="swatch sw-image" data-v="image" id="sw-image" data-tip="Imported background art: your own brand texture as the theme (tile or cover). Import below; composed text, layouts, and the linter all still apply"></div>
    </div>
    <button class="btn" id="btn-reroll" style="display:none" data-tip="Re-roll the procedural pattern">Reroll circuitry pattern</button>
    <div id="trace-controls"></div>
    <input type="file" id="bg-file" accept="image/*" data-tip="Import background art for the image theme">
    <label class="chk"><input type="checkbox" id="bg-cover"> Cover (scale to fill) instead of tiling</label>

    <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 id="sec-brand" style="display:none">
    <h3>Owner brand</h3>
    <div class="brandrow"><label>Accent</label><input type="color" id="brand-accent" value="#00e6dc" data-tip="Tints the subtitle + screenshot art. Non-adom pages bring their own accent"></div>
    <div class="brandrow"><label>Background</label><input type="color" id="brand-bg" value="#0d1117" data-tip="Base background color behind the theme art"></div>
    <button class="btn" id="brand-clear" data-tip="Back to the neutral studio palette">Use default palette</button>
    <div class="hint">Adom-org pages get the house brand (wordmark, teal, fonts) automatically. Everyone else brings their own colors and backgrounds; the placement stays fixed.</div>
    </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>
    <div class="brandrow"><label>Title size</label><input type="range" id="fs-title" min="5" max="13" step="0.1" value="8"><span class="hint" id="fs-title-val">8</span></div>
    <div class="brandrow"><label>Subtitle size</label><input type="range" id="fs-sub" min="1.8" max="3.5" step="0.1" value="2.5"><span class="hint" id="fs-sub-val">2.5</span></div>
    <div class="brandrow"><label>Detail size</label><input type="range" id="fs-desc" min="1.0" max="2.0" step="0.05" value="1.35"><span class="hint" id="fs-desc-val">1.35</span></div>
    <div class="brandrow" style="display:none"><label>Title leading</label><input type="range" id="fs-leading" min="0.92" max="1.3" step="0.01" value="1.02"><span class="hint" id="fs-leading-val">1.02</span></div>
    <div class="brandrow" data-tip="Scales the vertical gaps between title, subtitle and detail line"><label>Text gap</label><input type="range" id="fs-gap" min="0.5" max="2" step="0.05" value="1"><span class="hint" id="fs-gap-val">1</span></div>
    <div class="brandrow" data-tip="What bakes as the hero title. The page title is metadata and stays untouched; leave empty to use it (hero-studio#33)"><label>Headline</label><input type="text" id="headline-input" placeholder="(page title)" style="flex:1;min-width:0"></div>
    <div class="hint">The defaults are tuned to read at dock size and set you up for success. You are the author: adjust if your name or copy needs it, thoughtfully.</div>

    <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">The image carries the title, the type pill (top right), and the full slug (bottom right); the page shows it bare. Only the marquee overlays dynamic info, and it brings its own. Image only is the default; the others remain for comparison.</div>
    </div>

    <h3>Image badges (baked into the PNG)</h3>
    <label class="chk"><input type="checkbox" id="chk-pill"> Type pill, top right</label>
    <label class="chk"><input type="checkbox" id="chk-slug"> Wiki slug, bottom right</label>
    <div class="hint">The page shows the hero image as-is. Only the homepage marquee overlays dynamic info (stats, author), and it brings its own.</div>
    <label class="chk" style="display:none"><input type="checkbox" id="chk-scores"></label>
    <label class="chk" style="display:none"><input type="checkbox" id="chk-author"></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 id="renderer-line" class="hint"></div>
  </div>

  <div id="stage">
    <div id="approvedbar">
      <button id="ab-close" data-tip="Hide this banner (the approval itself is safe in the PNG and the state)">&times;</button>
      <div class="ab-head">Hero approved</div>
      <div class="ab-body" id="ab-body"></div>
      <button class="btn" id="ab-again">Generate a new version</button>
    </div>
    <div id="verifybar">
      <div class="vhead"><b>Hero verification.</b> The registry requires a human to approve this hero; an AI cannot do it for you. Check each item below, adjust anything that reads wrong (left panel), then Generate. Only this studio's export carries the provenance stamp the gate accepts.</div>
      <label class="chk vchk"><input type="checkbox" id="v-title"> Title reads right</label>
      <label class="chk vchk"><input type="checkbox" id="v-sub"> Subtitle (value-prop) reads right</label>
      <label class="chk vchk"><input type="checkbox" id="v-showcase"> Showcase (the app window) shows the right content</label>
      <label class="chk vchk"><input type="checkbox" id="v-type"> <span id="v-type-label">Page type matches</span></label>
      <div id="lintbox"></div>
      <button class="btn btn-primary" id="v-generate" disabled>Generate hero PNG</button>
    </div>
    <div id="stagebar">
      <div class="seg">
        <button id="sb-png">Exactly my PNG</button>
        <button id="sb-mq">With homepage overlay</button>
      </div>
      <div class="legend"><b>amber-outlined = dynamic</b>: the homepage adds it, it is NOT in your PNG. Press D (or hold the band) to flip. The lower band stays calm in the image because the marquee overlays its own info there.</div>
    </div>
    <div id="hero" class="hero" data-theme="midnight" data-layout="bleed-bottom">
      <div class="bg"></div>
      <img class="trace-art" id="trace-art" alt="" aria-hidden="true">

      <div class="textzone">
        <div class="wordmark" id="el-logo" data-tip="Wordmark: adom-org pages get the house mark automatically; other owners show nothing here">
          <span class="wmark"></span>
        </div>
        <div class="h-title" id="el-title" data-tip="Title (image-zone text): bakes into the PNG. Size and placement are fixed; toggle under Image-zone text"></div>
        <div class="h-sub" id="el-sub" data-tip="Value-prop subtitle (image-zone text): bakes into the PNG. Keep it one line; accent styling is automatic"></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" data-tip="Showcase image: the floating app window. Content comes from your page screenshots, an import, or the mock; placement from Showcase placement">
        <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">
          <img class="showcase-img" id="showcase-img" alt="">
          <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>

      <canvas id="circuitcanvas"></canvas>
      <img class="fullimport-img" id="fullimport-img" alt="">
      <div id="mqoverlay">
        <div class="mqo-badges" id="mqo-badges"></div>
        <h3 class="mqo-title" id="mqo-title"></h3>
        <p class="mqo-brief" id="mqo-brief"></p>
        <span class="mqo-owner" id="mqo-owner"></span>
        <div class="mqo-prompt" id="mqo-prompt" style="display:none"><span id="mqo-prompt-text"></span><button id="mqo-prompt-copy">copy</button></div>
      </div>
      <span class="fi-logo" id="fi-logo" data-tip="The standard Adom wordmark (156px at export scale, standard corner), stamped over the import"></span>
      <div class="pill-tr" id="pill-tr" data-tip="Type pill: bakes into the PNG top right (app / skill / hardware). Toggle under Image badges"><span class="pill app" id="l-pill">APP</span></div>
      <div class="slug-br" id="slug-br" data-tip="Wiki slug: bakes into the PNG bottom right. Toggle under Image badges"><a class="l-slug" id="l-slug" target="_blank" href="#"></a></div>
      <div class="lower">
        <div class="lowerin">
          <div class="lmain">
            <div class="badges">
              <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-desc" id="l-desc"></div>
            <div class="l-author" id="l-author"></div>
          </div>
          <div class="l-right">
            <span class="l-mpn" id="l-mpn"></span>

          </div>
        </div>
      </div>

      <div class="guide"></div>
      <div id="weditring"></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="tourpop">
  <div class="tp-head" data-tip="Drag me out of the way to inspect what is underneath">
    <div class="tp-title" id="tp-title"></div>
  </div>
  <div class="tp-body" id="tp-body"></div>
  <div class="tp-row">
    <button class="btn btn-primary" id="tp-ok">Looks good</button>
    <button class="btn tp-flag" id="tp-flag" data-tip="Flag this item: Generate stays locked and the driving AI sees the flag in state, so it can adjust and re-present">Needs fixing</button>
    <button class="btn" id="tp-skip">Skip tour</button>
  </div>
</div>
<div id="tip"></div>
<div id="toasts"></div>
<div id="genmodal">
  <div class="genbox">
    <div class="gen-approved">Hero approved</div>
    <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>

<div id="wdock">
  <div id="wdock-grip" data-tip="Drag to resize the wizard; double-click resets"></div>
  <div id="wdockintro">You're designing the <b>hero image</b> for <span class="ref" id="wintro-ref"></span>, the picture at the top of its wiki page. Everything updates live above. Nothing is saved until you hit Generate.</div>
  <div id="wrailwrap"><div id="wsteprail"></div></div>
  <div id="wdockhead">
    <span class="wdtitle" id="wd-title"></span>
    <span class="wdcount" id="wd-count"></span>
    <span class="wdhint" id="wd-hint"></span>
    <button class="btn small" id="wbtn-finetune" style="display:none">Fine tune</button>
    <button class="btn btn-primary" id="wbtn-generate" style="display:none">Generate hero PNG</button>
    <button class="btn small btn-primary" id="wbtn-next">Next</button>
  </div>
  <div id="wrenderer" class="hint" style="display:none"></div>
  <div id="wcardsrow">
    <button class="warrow" id="warr-l">&#8249;</button>
    <div id="wcards"></div>
    <button class="warrow" id="warr-r">&#8250;</button>
  </div>
</div>

<script src="js/app.js"></script>
<script src="js/wizard.js"></script>
</body>
</html>