| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>Alma AI Website Builder</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500;9..144,600&family=Manrope:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> |
| <style> |
| :root{ |
| --base:#0E0B08; --ink2:#141009; --surface:#1A140C; --surface2:#221A10; |
| --line:rgba(242,235,221,.12); --line2:rgba(242,235,221,.22); |
| --cream:#F2EBDD; --dim:#CBBEA6; --muted:#8E8268; |
| --accent:#D98E4F; --accent-soft:#E8A968; --accent-deep:#B5703A; |
| --ok:#7FB988; |
| --maxw:1200px; --ease:cubic-bezier(.22,.61,.36,1); |
| --shadow:0 40px 90px -40px rgba(0,0,0,.85); |
| } |
| *{box-sizing:border-box} |
| html,body{margin:0} |
| body{ |
| background: |
| radial-gradient(1100px 700px at 84% -10%, rgba(217,142,79,.12), transparent 62%), |
| var(--base); |
| color:var(--cream); |
| font-family:"Manrope",system-ui,sans-serif; |
| font-size:16.5px; font-weight:400; line-height:1.62; -webkit-font-smoothing:antialiased; |
| } |
| h1,h2,h3,h4{font-family:"Fraunces",Georgia,serif;font-weight:400;line-height:1.04;margin:0;letter-spacing:-.012em} |
| a{color:inherit;text-decoration:none} |
| button,input,textarea,select{font-family:inherit} |
| .wrap{width:min(100% - 44px,var(--maxw));margin-inline:auto} |
| .serif{font-family:"Fraunces",Georgia,serif} |
| .eyebrow{font-size:.72rem;letter-spacing:.28em;text-transform:uppercase;font-weight:600;color:var(--accent)} |
| .muted{color:var(--muted)} .dim{color:var(--dim)} |
| .accent{color:var(--accent)} |
|
|
| .btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;font-weight:600;font-size:.93rem;letter-spacing:.01em;padding:14px 26px;border-radius:2px;border:1px solid transparent;cursor:pointer;transition:transform .25s var(--ease),background .25s,color .25s,border-color .25s;white-space:nowrap} |
| .btn:focus-visible{outline:2px solid var(--accent);outline-offset:3px} |
| .btn:disabled{opacity:.45;cursor:not-allowed} |
| .btn-primary{background:var(--accent);color:#1a0f06} |
| .btn-primary:hover:not(:disabled){background:var(--accent-soft);transform:translateY(-2px)} |
| .btn-ghost{background:transparent;border-color:var(--line2);color:var(--cream)} |
| .btn-ghost:hover:not(:disabled){border-color:var(--accent);transform:translateY(-2px)} |
| .btn-line{background:transparent;border:none;color:var(--cream);padding:14px 4px;position:relative} |
| .btn-line:after{content:"";position:absolute;left:4px;right:4px;bottom:8px;height:1px;background:var(--accent);transform:scaleX(.4);transform-origin:left;transition:transform .3s var(--ease)} |
| .btn-line:hover:after{transform:scaleX(1)} |
| .btn-sm{padding:10px 16px;font-size:.84rem} |
|
|
| .screen{display:none;animation:fade .55s var(--ease)} |
| .screen.on{display:block} |
| @keyframes fade{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}} |
|
|
| /* top bar */ |
| .topbar{position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);background:linear-gradient(180deg,rgba(14,11,8,.94),rgba(14,11,8,.55));border-bottom:1px solid var(--line)} |
| .topbar-in{display:flex;align-items:center;gap:18px;padding:16px 0;width:min(100% - 44px,var(--maxw));margin-inline:auto} |
| .logo{display:flex;align-items:baseline;gap:11px;margin-right:auto} |
| .logo .nm{font-family:"Fraunces";font-size:1.35rem;font-weight:500;letter-spacing:-.01em} |
| .logo .nm b{color:var(--accent);font-weight:500} |
| .logo small{font-size:.64rem;letter-spacing:.26em;text-transform:uppercase;color:var(--muted)} |
| .steps{display:flex;gap:2px;align-items:center} |
| .steps .s{display:flex;align-items:center;gap:8px;font-size:.78rem;color:var(--muted);font-weight:500;padding:6px 12px} |
| .steps .s .n{font-family:"Fraunces";font-size:.92rem;color:var(--muted);width:22px;text-align:center} |
| .steps .s.active{color:var(--cream)} .steps .s.active .n{color:var(--accent)} |
| .steps .s.done .n{color:var(--cream)} |
| .steps .sep{width:16px;height:1px;background:var(--line2)} |
|
|
| /* intake */ |
| .intake{padding:96px 0 110px} |
| .intake .eyebrow{margin-bottom:26px;display:block} |
| .intake h1{font-size:clamp(3rem,8vw,6.4rem);font-weight:300;letter-spacing:-.03em;max-width:16ch} |
| .intake h1 em{font-style:italic;color:var(--accent);font-weight:400} |
| .intake .sub{font-size:1.18rem;color:var(--dim);max-width:54ch;margin:30px 0 0;font-weight:300} |
| .intake-card{margin:50px 0 0;max-width:740px;border-top:1px solid var(--line2);padding-top:34px} |
| .field{margin-bottom:20px} |
| .field label{display:block;font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;font-weight:600;color:var(--muted);margin-bottom:10px} |
| .field input{width:100%;background:transparent;border:none;border-bottom:1px solid var(--line2);color:var(--cream);font-size:1.4rem;font-family:"Fraunces";font-weight:300;padding:8px 0 12px;transition:.25s} |
| .field input:focus{outline:none;border-color:var(--accent)} |
| .field input::placeholder{color:var(--muted);font-style:italic} |
| .socials{display:grid;grid-template-columns:1fr 1fr;gap:20px 30px;margin-top:6px} |
| .socials .field{margin-bottom:0} |
| .socials input{font-size:1rem;font-family:"Manrope";font-weight:400} |
| .intake-actions{display:flex;gap:16px;align-items:center;margin-top:34px} |
| .hint{font-size:.86rem;color:var(--muted);margin-top:18px} |
| .feat-row{display:flex;gap:28px;flex-wrap:wrap;margin-top:64px;border-top:1px solid var(--line);padding-top:26px} |
| .feat{font-size:.82rem;color:var(--dim);font-weight:300} |
| .feat b{color:var(--cream);font-weight:500;font-family:"Fraunces"} |
|
|
| /* loading */ |
| .loading{min-height:64vh;display:grid;place-items:center;text-align:center} |
| .mono{width:8px;height:8px;border-radius:50%;background:var(--accent);margin:0 auto 34px;box-shadow:0 0 0 0 rgba(217,142,79,.5);animation:beat 2.4s var(--ease) infinite} |
| @keyframes beat{0%,100%{box-shadow:0 0 0 0 rgba(217,142,79,.45);transform:scale(1)}50%{box-shadow:0 0 0 16px rgba(217,142,79,0);transform:scale(1.25)}} |
| .loading h2{font-size:clamp(1.8rem,4vw,2.6rem);font-weight:300} |
| .loading .ld-msg{color:var(--dim);font-size:1.06rem;margin-top:14px;min-height:1.6em;transition:opacity .3s;font-weight:300;font-style:italic;font-family:"Fraunces"} |
| .prog{max-width:440px;margin:34px auto 0} |
| .prog-bar{height:1px;background:var(--line2);overflow:hidden} |
| .prog-fill{height:100%;width:8%;background:var(--accent);transition:width .6s var(--ease)} |
| .prog-list{margin-top:24px;text-align:left;display:grid;gap:11px} |
| .prog-item{display:flex;align-items:center;gap:12px;font-size:.92rem;color:var(--muted);font-weight:300} |
| .prog-item.run{color:var(--cream)} .prog-item.ok{color:var(--dim)} |
| .prog-item .ico{font-family:"Fraunces";width:18px;color:var(--accent)} |
|
|
| /* generic section pad */ |
| .pad{padding:64px 0 100px} |
| .head-2{max-width:64ch;margin-bottom:40px} |
| .head-2 .eyebrow{display:block;margin-bottom:18px} |
| .head-2 h2{font-size:clamp(2.2rem,5vw,3.6rem);font-weight:300} |
| .head-2 p{color:var(--dim);margin-top:16px;font-weight:300;font-size:1.08rem} |
|
|
| /* verify */ |
| .verify-card{display:grid;grid-template-columns:1fr 300px;gap:50px;border-top:1px solid var(--line);padding-top:40px} |
| .vk h3{font-size:2.1rem;font-weight:300;margin-bottom:4px} |
| .vk .tag{color:var(--accent);font-weight:300;font-style:italic;font-family:"Fraunces";font-size:1.15rem} |
| .conf{display:flex;align-items:center;gap:12px;font-size:.84rem;margin:18px 0 24px;color:var(--muted)} |
| .conf .bar{flex:1;max-width:160px;height:1px;background:var(--line2)} |
| .conf .bar i{display:block;height:1px;background:var(--accent)} |
| .kv{display:grid;grid-template-columns:120px 1fr;gap:14px 20px;font-size:.96rem;padding:22px 0;border-top:1px solid var(--line);font-weight:300} |
| .kv dt{color:var(--muted);font-weight:500;font-size:.74rem;letter-spacing:.12em;text-transform:uppercase} |
| .kv dd{margin:0;color:var(--cream)} |
| .chips{display:flex;flex-wrap:wrap;gap:8px} |
| .chip{font-size:.82rem;padding:5px 13px;border:1px solid var(--line2);color:var(--dim);font-weight:300} |
| .editlink{background:none;border:none;color:var(--accent);font-weight:500;cursor:pointer;font-size:.9rem;padding:0;margin-top:18px} |
| .dz-title{font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-bottom:18px} |
| .swatches{display:flex;gap:0;margin:14px 0 20px;border:1px solid var(--line)} |
| .sw{flex:1;height:54px} |
| .fontline{padding:14px 0;border-top:1px solid var(--line)} |
| .fontline .l{font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)} |
| .fontline .v{font-size:1.5rem;margin-top:4px} |
| .edit-grid{display:grid;gap:16px;margin-top:18px} |
| .edit-grid .field{margin:0} |
| .edit-grid input,.edit-grid textarea{width:100%;background:transparent;border:1px solid var(--line2);color:var(--cream);font-size:.96rem;padding:11px 13px;font-family:"Manrope"} |
| .edit-grid textarea{resize:vertical;min-height:64px} |
|
|
| /* audiences */ |
| .aud-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1px;background:var(--line);border:1px solid var(--line);margin-top:8px} |
| .aud{position:relative;background:var(--base);padding:26px 24px;cursor:pointer;transition:background .25s;min-height:170px;display:flex;flex-direction:column} |
| .aud:hover{background:var(--ink2)} |
| .aud.sel{background:var(--surface)} |
| .aud .num{font-family:"Fraunces";font-size:1.1rem;color:var(--muted)} |
| .aud.sel .num{color:var(--accent)} |
| .aud h4{font-size:1.3rem;font-weight:400;margin:8px 0 10px;max-width:90%} |
| .aud .who{font-size:.88rem;color:var(--dim);font-weight:300;margin-bottom:12px;flex:1} |
| .aud .why{font-size:.82rem;color:var(--muted);font-weight:300;border-top:1px solid var(--line);padding-top:12px} |
| .aud .why b{color:var(--accent);font-weight:500} |
| .aud .check{position:absolute;top:22px;right:22px;width:22px;height:22px;border:1px solid var(--line2);display:grid;place-items:center;font-size:.72rem;color:transparent;transition:.2s} |
| .aud.sel .check{background:var(--accent);border-color:var(--accent);color:#1a0f06} |
| .aud-bar{position:sticky;bottom:0;margin-top:30px;background:linear-gradient(180deg,rgba(14,11,8,.3),var(--base));backdrop-filter:blur(8px);border-top:1px solid var(--line);padding:22px 0;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap} |
| .aud-bar .cnt{font-size:.96rem;color:var(--dim);font-weight:300} |
| .aud-bar .cnt b{color:var(--cream);font-family:"Fraunces";font-size:1.2rem} |
|
|
| /* preview */ |
| .preview-shell{display:grid;grid-template-columns:230px 1fr;gap:30px;align-items:start} |
| .rail{position:sticky;top:96px} |
| .rail h5{font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin:0 0 14px} |
| .rail .navb{display:block;width:100%;text-align:left;background:none;border:none;border-left:1px solid var(--line);color:var(--muted);font-size:.92rem;font-weight:300;padding:10px 14px;cursor:pointer;transition:.18s} |
| .rail .navb:hover{color:var(--cream);border-color:var(--line2)} |
| .rail .navb.active{color:var(--cream);border-color:var(--accent)} |
| .rail .navb .vid{font-size:.72rem;color:var(--accent);float:right;opacity:.85} |
| .rail .grp{margin-bottom:22px} |
| .frame-wrap{border:1px solid var(--line2);overflow:hidden;box-shadow:var(--shadow);background:var(--ink2)} |
| .frame-top{display:flex;align-items:center;gap:10px;padding:13px 18px;border-bottom:1px solid var(--line)} |
| .frame-top .dots{display:flex;gap:7px} |
| .frame-top .dots i{width:10px;height:10px;border-radius:50%;background:var(--line2)} |
| .frame-top .url{flex:1;font-size:.82rem;color:var(--muted);text-align:center;font-weight:300} |
| .iframe-host{height:74vh;background:#fff} |
| .iframe-host iframe{width:100%;height:100%;border:0;display:block} |
|
|
| /* modal */ |
| .modal-scrim{position:fixed;inset:0;background:rgba(8,5,2,.78);backdrop-filter:blur(4px);z-index:90;display:none;place-items:center;padding:20px} |
| .modal-scrim.on{display:grid} |
| .modal{width:min(580px,100%);max-height:86vh;overflow:auto;background:var(--ink2);border:1px solid var(--line2);padding:34px;box-shadow:var(--shadow)} |
| .modal h3{font-size:1.7rem;font-weight:300;margin-bottom:4px} |
| .modal .x{float:right;background:none;border:1px solid var(--line2);color:var(--cream);width:34px;height:34px;cursor:pointer} |
| .vid-block{border-top:1px solid var(--line);padding:16px 0;font-size:.95rem;font-weight:300} |
| .vid-block .lbl{font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:var(--accent);font-weight:600;margin-bottom:8px} |
| .scene{border-left:1px solid var(--accent);padding:6px 0 6px 16px;margin:12px 0} |
| .scene b{color:var(--cream);font-family:"Fraunces"} |
|
|
| .toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(120px);background:var(--surface2);border:1px solid var(--line2);color:var(--cream);padding:14px 22px;font-weight:500;font-size:.9rem;z-index:120;transition:transform .4s var(--ease);box-shadow:var(--shadow)} |
| .toast.show{transform:translateX(-50%) translateY(0)} |
| .err{border:1px solid rgba(217,142,79,.5);color:var(--accent-soft);padding:14px 16px;font-size:.9rem;margin-top:16px;font-weight:300} |
|
|
| @media (max-width:900px){ |
| .verify-card,.preview-shell{grid-template-columns:1fr;gap:30px} |
| .steps .s .lbl{display:none} |
| .rail{position:static} |
| .iframe-host{height:60vh} |
| .socials{grid-template-columns:1fr} |
| } |
| @media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}} |
| </style> |
| </head> |
| <body> |
|
|
| <div class="topbar"> |
| <div class="topbar-in"> |
| <a class="logo" href="#"> |
| <span class="nm">Alma<b>·</b>AI</span> |
| <small>Website Studio</small> |
| </a> |
| <div class="steps" id="steps"> |
| <div class="s" data-step="input"><span class="n">01</span><span class="lbl">Business</span></div> |
| <span class="sep"></span> |
| <div class="s" data-step="verify"><span class="n">02</span><span class="lbl">Verify</span></div> |
| <span class="sep"></span> |
| <div class="s" data-step="audiences"><span class="n">03</span><span class="lbl">Audiences</span></div> |
| <span class="sep"></span> |
| <div class="s" data-step="preview"><span class="n">04</span><span class="lbl">Build</span></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <section class="screen on" id="screen-input"> |
| <div class="wrap intake"> |
| <span class="eyebrow">Point · Build · Convert</span> |
| <h1>The website your business is <em>missing</em>.</h1> |
| <p class="sub">Give Alma a website, a social link, or just a name. She studies the business, finds the audiences they should be selling to, and builds a premium site — a dedicated page for each one.</p> |
|
|
| <div class="intake-card"> |
| <div class="field"> |
| <label>Website, social link, or company name</label> |
| <input id="in-main" placeholder="acme-roofing.com — or — Acme Roofing, Salt Lake City" autocomplete="off" /> |
| </div> |
| <div class="socials"> |
| <div class="field"><label>Facebook</label><input id="in-fb" placeholder="optional" /></div> |
| <div class="field"><label>Instagram</label><input id="in-ig" placeholder="optional" /></div> |
| <div class="field"><label>TikTok</label><input id="in-tt" placeholder="optional" /></div> |
| <div class="field"><label>LinkedIn</label><input id="in-li" placeholder="optional" /></div> |
| </div> |
| <div class="intake-actions"> |
| <button class="btn btn-primary" id="btn-build">Research & build →</button> |
| <span class="hint">Alma verifies the business before building. You approve everything.</span> |
| </div> |
| <div id="input-err"></div> |
| </div> |
|
|
| <div class="feat-row"> |
| <span class="feat"><b>Up to 10</b> audience pages</span> |
| <span class="feat"><b>Question → Pain → Value → KPIs → CTA</b></span> |
| <span class="feat"><b>Original</b> design every build</span> |
| <span class="feat"><b>Lead forms</b> + chat</span> |
| <span class="feat"><b>Ad scripts</b> for TikTok & Facebook</span> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="screen" id="screen-loading"> |
| <div class="wrap loading"> |
| <div> |
| <div class="mono"></div> |
| <h2 id="ld-title">Alma is working</h2> |
| <p class="ld-msg" id="ld-msg">Warming up…</p> |
| <div class="prog"> |
| <div class="prog-bar"><div class="prog-fill" id="prog-fill"></div></div> |
| <div class="prog-list" id="prog-list"></div> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="screen" id="screen-verify"> |
| <div class="wrap pad"> |
| <div class="head-2"> |
| <span class="eyebrow">Step 02 — Verify</span> |
| <h2>Is this the right business?</h2> |
| <p>Alma pulled this from their website and socials. Confirm or fix anything before we find audiences.</p> |
| </div> |
| <div class="verify-card"> |
| <div id="verify-main"></div> |
| <div id="verify-side"></div> |
| </div> |
| <div style="display:flex;gap:18px;margin-top:36px;flex-wrap:wrap;align-items:center"> |
| <button class="btn btn-primary" id="btn-to-audiences">Find the audiences →</button> |
| <button class="btn btn-line" id="btn-back-input">Start over</button> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="screen" id="screen-audiences"> |
| <div class="wrap pad"> |
| <div class="head-2"> |
| <span class="eyebrow">Step 03 — Audiences</span> |
| <h2>The audiences they're <em class="serif accent" style="font-style:italic">not</em> selling to.</h2> |
| <p>Alma found the highest-value segments this business should own. Each becomes its own page on the Audience Engine Drop spine. Keep up to 10.</p> |
| </div> |
| <div class="aud-grid" id="aud-grid"></div> |
| <div class="aud-bar"> |
| <div class="cnt"><b id="aud-count">0</b> of 10 audience pages selected</div> |
| <div style="display:flex;gap:18px;align-items:center"> |
| <button class="btn btn-line" id="btn-back-verify">← Back</button> |
| <button class="btn btn-primary" id="btn-generate">Build the site →</button> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="screen" id="screen-preview"> |
| <div class="wrap pad"> |
| <div class="head-2" style="display:flex;justify-content:space-between;align-items:flex-end;gap:24px;flex-wrap:wrap;max-width:none"> |
| <div> |
| <span class="eyebrow">Step 04 — The site</span> |
| <h2 id="pv-title" style="margin-top:14px">Site ready</h2> |
| </div> |
| <div style="display:flex;gap:16px;flex-wrap:wrap;align-items:center"> |
| <button class="btn btn-line" id="btn-new">+ New build</button> |
| <button class="btn btn-ghost btn-sm" id="btn-open-tab">Open ↗</button> |
| <button class="btn btn-primary" id="btn-export">Export site</button> |
| </div> |
| </div> |
| <div class="preview-shell"> |
| <div class="rail" id="rail"></div> |
| <div class="frame-wrap"> |
| <div class="frame-top"> |
| <div class="dots"><i></i><i></i><i></i></div> |
| <div class="url" id="frame-url">yourbusiness.com</div> |
| </div> |
| <div class="iframe-host"><iframe id="site-frame" title="Generated site preview"></iframe></div> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| <div class="modal-scrim" id="vid-scrim"> |
| <div class="modal" id="vid-modal"> |
| <button class="x" id="vid-x">✕</button> |
| <span class="eyebrow">Short-form ad script</span> |
| <h3 id="vid-title" style="margin-top:8px">Video script</h3> |
| <div id="vid-body"></div> |
| </div> |
| </div> |
|
|
| <div class="toast" id="toast"></div> |
|
|
| <script> |
| /* ============================================================ |
| ALMA AI WEBSITE STUDIO |
| White-label. End users see "Alma AI" only. |
| Output follows the Ogden Web Solutions House Style + |
| the Audience Engine Drop spine. |
| Model calls run through api.php (key stays server-side). |
| ============================================================ */ |
| const API_ENDPOINT = "api.php"; |
| const MODEL = "claude-sonnet-4-6"; |
|
|
| const DISPLAY_FONTS = ["Fraunces","Cormorant Garamond","Playfair Display","Libre Caslon Display"]; |
| const BODY_FONTS = ["Manrope","Inter"]; |
|
|
| async function alma(userPrompt, {search=false, system="", retry=true}={}){ |
| const body = { model: MODEL, max_tokens: 1000, messages: [{ role:"user", content: userPrompt }] }; |
| if(system) body.system = system; |
| if(search) body.tools = [{ type:"web_search_20250305", name:"web_search" }]; |
| const res = await fetch(API_ENDPOINT, { |
| method:"POST", headers:{ "Content-Type":"application/json" }, body: JSON.stringify(body) |
| }); |
| if(!res.ok){ throw new Error("Alma couldn't reach the studio service (" + res.status + ")."); } |
| const data = await res.json(); |
| return (data.content||[]).filter(b=>b.type==="text").map(b=>b.text).join("\n").trim(); |
| } |
| function extractJSON(txt){ |
| if(!txt) throw new Error("empty"); |
| let t = txt.replace(/```json/gi,"```").trim(); |
| if(t.indexOf("```")>=0){ t = t.split("```").filter(s=>s.trim()).sort((a,b)=>b.length-a.length)[0]; } |
| const s=t.indexOf("{"), sa=t.indexOf("["); |
| let start=(sa>=0 && (sa<s||s<0))?sa:s; |
| if(start<0) throw new Error("no json"); |
| const open=t[start], close=open==="["?"]":"}"; |
| let depth=0,end=-1; |
| for(let i=start;i<t.length;i++){ if(t[i]===open)depth++; else if(t[i]===close){depth--; if(depth===0){end=i;break;}} } |
| if(end<0) end=t.length; |
| return JSON.parse(t.slice(start,end+1)); |
| } |
| async function almaJSON(prompt, opts={}){ |
| let txt = await alma(prompt, opts); |
| try{ return extractJSON(txt); } |
| catch(e){ |
| if(opts.retry===false) throw e; |
| txt = await alma(prompt + "\n\nReturn ONLY valid JSON. No prose, no markdown.", {...opts, retry:false}); |
| return extractJSON(txt); |
| } |
| } |
|
|
| const S = { input:{}, biz:null, audiences:[], pages:{}, siteHTML:"", activePage:"home" }; |
|
|
| const screens = ["input","loading","verify","audiences","preview"]; |
| function show(name){ screens.forEach(s=>document.getElementById("screen-"+s).classList.toggle("on", s===name)); window.scrollTo({top:0,behavior:"smooth"}); } |
| function setSteps(active){ |
| const order=["input","verify","audiences","preview"], ai=order.indexOf(active); |
| document.querySelectorAll("#steps .s").forEach(el=>{ const i=order.indexOf(el.dataset.step); el.classList.toggle("active",i===ai); el.classList.toggle("done",i<ai); }); |
| } |
| function toast(msg){ const t=document.getElementById("toast"); t.textContent=msg; t.classList.add("show"); clearTimeout(t._t); t._t=setTimeout(()=>t.classList.remove("show"),2600); } |
|
|
| let ldRot=null; |
| function startLoading(title, steps, msgs){ |
| show("loading"); |
| document.getElementById("ld-title").textContent = title; |
| document.getElementById("prog-list").innerHTML = steps.map((s,i)=>`<div class="prog-item" data-i="${i}"><span class="ico">·</span><span>${s}</span></div>`).join(""); |
| setProg(6); |
| let m=0; const el=document.getElementById("ld-msg"); el.textContent=msgs[0]; |
| clearInterval(ldRot); |
| ldRot=setInterval(()=>{ m=(m+1)%msgs.length; el.style.opacity=0; setTimeout(()=>{el.textContent=msgs[m];el.style.opacity=1;},220); },2700); |
| } |
| function setProg(p){ document.getElementById("prog-fill").style.width=Math.min(98,p)+"%"; } |
| function stepState(i,st){ const el=document.querySelector('.prog-item[data-i="'+i+'"]'); if(!el)return; el.className="prog-item "+(st==="ok"?"ok":st==="run"?"run":""); el.querySelector(".ico").textContent=st==="ok"?"✓":st==="run"?"›":"·"; } |
| function stopLoading(){ clearInterval(ldRot); } |
|
|
| /* ---------- research ---------- */ |
| async function runResearch(){ |
| const main=document.getElementById("in-main").value.trim(); |
| if(!main){ document.getElementById("input-err").innerHTML='<div class="err">Add a website, social link, or company name to start.</div>'; return; } |
| S.input={ main, fb:val("in-fb"), ig:val("in-ig"), tt:val("in-tt"), li:val("in-li") }; |
| startLoading("Alma is studying the business", |
| ["Finding & verifying the business","Reading the website & socials","Choosing a house-style direction"], |
| ["Reading their website…","Studying their social profiles…","Listening for their voice…","Choosing type, base & accent…","Cross-checking the details…"]); |
| stepState(0,"run"); setProg(14); |
| try{ |
| const links=[S.input.fb,S.input.ig,S.input.tt,S.input.li].filter(Boolean).join(", "); |
| const prompt= |
| `You are Alma, a B2B brand & audience strategist. Research this business with web search and capture an accurate profile. |
|
|
| INPUT: "${S.input.main}" |
| ${links?("KNOWN SOCIAL LINKS: "+links):""} |
|
|
| Search the web (website, socials, directories) to verify the real business and gather facts. If info is thin, infer sensibly from category + location and lower "confidence". |
|
|
| Return ONLY JSON: |
| { |
| "name":"", "verified":true, "confidence":0.0, "domain":"", "category":"", |
| "tagline":"short brand line", "description":"2 sentences on what they do", |
| "services":["3-7 concrete services"], "location":"city, state if known", |
| "phone":"", "email":"", "tone":"one phrase voice e.g. confident & warm", |
| "currentAudiences":["who their current site seems to speak to"], |
| "design":{ |
| "base":"#hex DEEP near-black, subtly warmed or cooled toward the brand's world", |
| "accent":"#hex — ONE characterful accent pulled from the brand (their craft, logo, region). Not generic blue.", |
| "cream":"#hex — soft off-white / warm cream for text on the base", |
| "displayFont":"one of: ${DISPLAY_FONTS.join(", ")}", |
| "bodyFont":"one of: ${BODY_FONTS.join(", ")}", |
| "vibe":"3-5 word art direction e.g. quiet rugged confident", |
| "idea":"the ONE strong idea this site should express in 4-8 words" |
| } |
| } |
| The look is premium editorial: near-black base, cream text, a single accent used sparingly. Make base & accent genuinely fit THIS brand.`; |
| const biz=await almaJSON(prompt,{search:true}); |
| stepState(0,"ok"); stepState(1,"ok"); stepState(2,"run"); setProg(72); |
| biz.design=biz.design||{}; |
| if(!DISPLAY_FONTS.includes(biz.design.displayFont)) biz.design.displayFont="Fraunces"; |
| if(!BODY_FONTS.includes(biz.design.bodyFont)) biz.design.bodyFont="Manrope"; |
| biz.design.base=cleanHex(biz.design.base)||"#0E0B08"; |
| biz.design.accent=cleanHex(biz.design.accent)||"#C98A4A"; |
| biz.design.cream=cleanHex(biz.design.cream)||"#F2EBDD"; |
| biz.socials=S.input; |
| S.biz=biz; |
| stepState(2,"ok"); setProg(100); stopLoading(); |
| renderVerify(); setSteps("verify"); show("verify"); |
| }catch(e){ |
| stopLoading(); show("input"); |
| document.getElementById("input-err").innerHTML='<div class="err">'+(e.message||"Something went wrong")+' Try a more specific name or paste the website URL.</div>'; |
| } |
| } |
| function val(id){ return document.getElementById(id).value.trim(); } |
|
|
| function renderVerify(){ |
| const b=S.biz, d=b.design, conf=Math.round((b.confidence||0.7)*100); |
| document.getElementById("verify-main").innerHTML= |
| '<div class="vk"><h3>'+esc(b.name||"Business")+'</h3>'+ |
| '<div class="tag">'+esc(b.tagline||b.category||"")+'</div>'+ |
| '<div class="conf"><span>Match confidence</span><span class="bar"><i style="width:'+conf+'%"></i></span><span style="color:var(--cream)">'+conf+'%</span></div>'+ |
| '</div>'+ |
| '<p class="dim" style="font-weight:300;font-size:1.08rem;max-width:54ch;margin:0 0 6px">'+esc(b.description||"")+'</p>'+ |
| '<dl class="kv">'+kv("Category",b.category)+kv("Location",b.location)+kv("Phone",b.phone)+kv("Email",b.email)+kv("Voice",b.tone)+'</dl>'+ |
| '<div class="kv"><dt>Services</dt><dd><div class="chips">'+(b.services||[]).map(s=>'<span class="chip">'+esc(s)+'</span>').join("")+'</div></dd></div>'+ |
| '<button class="editlink" id="btn-edit">✎ Edit these details</button><div id="edit-zone"></div>'; |
| document.getElementById("verify-side").innerHTML= |
| '<div class="dz-title">House-style direction</div>'+ |
| '<h3 class="serif" style="font-weight:300;font-size:1.7rem;font-style:italic">"'+esc(d.idea||d.vibe||"")+'"</h3>'+ |
| '<div class="muted" style="font-size:.84rem;margin:6px 0 0;font-weight:300">'+esc(d.vibe||"")+'</div>'+ |
| '<div class="swatches"><span class="sw" style="background:'+d.base+'"></span><span class="sw" style="background:'+d.accent+'"></span><span class="sw" style="background:'+d.cream+'"></span></div>'+ |
| '<div class="fontline"><div class="l">Display</div><div class="v serif" style="font-weight:300">'+esc(d.displayFont)+'</div></div>'+ |
| '<div class="fontline"><div class="l">Body</div><div class="v" style="font-family:Manrope;font-size:1.15rem">'+esc(d.bodyFont)+'</div></div>'+ |
| '<p class="muted" style="font-size:.82rem;margin-top:16px;font-weight:300">Near-black base · cream text · one accent. Every build is original.</p>'; |
| document.getElementById("btn-edit").onclick=openEdit; |
| } |
| function openEdit(){ |
| const b=S.biz; |
| document.getElementById("btn-edit").style.display="none"; |
| document.getElementById("edit-zone").innerHTML= |
| '<div class="edit-grid">'+ef("Name","name",b.name)+ef("Tagline","tagline",b.tagline)+ef("Location","location",b.location)+ef("Phone","phone",b.phone)+ef("Email","email",b.email)+ |
| '<div class="field"><label>Description</label><textarea id="ed-description">'+esc(b.description||"")+'</textarea></div>'+ |
| '<div class="field"><label>Services (comma separated)</label><textarea id="ed-services">'+esc((b.services||[]).join(", "))+'</textarea></div>'+ |
| '<button class="btn btn-ghost btn-sm" id="btn-save-edit" style="width:fit-content">Save details</button></div>'; |
| document.getElementById("btn-save-edit").onclick=()=>{ |
| ["name","tagline","location","phone","email","description"].forEach(k=>{const el=document.getElementById("ed-"+k); if(el)b[k]=el.value.trim();}); |
| b.services=document.getElementById("ed-services").value.split(",").map(x=>x.trim()).filter(Boolean); |
| renderVerify(); toast("Details updated"); |
| }; |
| } |
| function ef(l,k,v){ return '<div class="field"><label>'+l+'</label><input id="ed-'+k+'" value="'+escA(v||"")+'"></div>'; } |
| function kv(k,v){ return v?('<dt>'+k+'</dt><dd>'+esc(v)+'</dd>'):""; } |
|
|
| /* ---------- audiences ---------- */ |
| async function runAudiences(){ |
| startLoading("Alma is finding the audiences they're missing", |
| ["Mapping the buyer landscape","Ranking the highest-value segments","Selecting the strongest fits"], |
| ["Mapping who they could sell to…","Scoring each segment by value & fit…","Finding the pain their site ignores…","Ranking the strongest opportunities…"]); |
| stepState(0,"run"); setProg(20); |
| try{ |
| const b=S.biz; |
| const prompt= |
| `You are Alma, an ICP & demand strategist. For this business, identify the highest-value distinct AUDIENCE SEGMENTS it should sell to but whose specific pain its current website does NOT speak to. Each becomes its own landing page. |
|
|
| BUSINESS: ${b.name} — ${b.category}. ${b.description} |
| SERVICES: ${(b.services||[]).join(", ")} |
| LOCATION: ${b.location||"n/a"} |
| CURRENTLY SPEAKS TO: ${(b.currentAudiences||[]).join(", ")||"unclear / generic"} |
|
|
| Return ONLY a JSON array of EXACTLY 10 items (or as many distinct, non-overlapping ones as truly exist), strongest first: |
| [{ "name":"specific segment", "who":"1 sentence", "whyNow":"why target them now", "painHint":"the core tension they feel" }] |
| Prefer named verticals/roles over vague groups.`; |
| let auds=await almaJSON(prompt,{}); |
| if(!Array.isArray(auds)) auds=[]; |
| auds=auds.slice(0,10).map((a,i)=>({...a,id:"aud"+i,selected:true})); |
| S.audiences=auds; |
| stepState(0,"ok"); stepState(1,"ok"); stepState(2,"ok"); setProg(100); stopLoading(); |
| renderAudiences(); setSteps("audiences"); show("audiences"); |
| }catch(e){ stopLoading(); renderVerify(); setSteps("verify"); show("verify"); toast("Couldn't load audiences — try again"); } |
| } |
| function renderAudiences(){ |
| const grid=document.getElementById("aud-grid"); |
| grid.innerHTML=S.audiences.map((a,i)=> |
| '<div class="aud '+(a.selected?'sel':'')+'" data-id="'+a.id+'"><div class="check">✓</div>'+ |
| '<div class="num">'+String(i+1).padStart(2,"0")+'</div><h4>'+esc(a.name)+'</h4>'+ |
| '<div class="who">'+esc(a.who||"")+'</div>'+ |
| '<div class="why"><b>Why now — </b>'+esc(a.whyNow||a.painHint||"")+'</div></div>').join(""); |
| grid.querySelectorAll(".aud").forEach(el=>el.onclick=()=>toggleAud(el.dataset.id)); |
| updateAudCount(); |
| } |
| function toggleAud(id){ |
| const a=S.audiences.find(x=>x.id===id), sel=S.audiences.filter(x=>x.selected).length; |
| if(!a.selected && sel>=10){ toast("10 audience pages is the max"); return; } |
| a.selected=!a.selected; renderAudiences(); |
| } |
| function updateAudCount(){ document.getElementById("aud-count").textContent=S.audiences.filter(a=>a.selected).length; } |
|
|
| /* ---------- generate ---------- */ |
| async function runGenerate(){ |
| const chosen=S.audiences.filter(a=>a.selected); |
| if(!chosen.length){ toast("Select at least one audience"); return; } |
| S.pages={}; |
| startLoading("Alma is building the site", |
| chosen.map(a=>"Writing the "+shortName(a.name)+" page").concat(["Setting type, composing the layout"]), |
| ["Writing hooks that stop the scroll…","Naming the pain each buyer feels…","Translating value into their language…","Lining up the proof…","Composing the editorial layout…"]); |
| const b=S.biz; |
| try{ |
| for(let i=0;i<chosen.length;i++){ |
| const a=chosen[i]; |
| stepState(i,"run"); setProg(10+(i/chosen.length)*80); |
| const prompt= |
| `You are Alma, a direct-response copywriter using the "Audience Engine Drop" spine. Write a landing page for "${b.name}" (${b.category}) aimed ONLY at: ${a.name} — ${a.who}. Core pain: ${a.painHint||a.whyNow}. |
| Business value: ${b.description} Services: ${(b.services||[]).join(", ")}. |
|
|
| Return ONLY JSON following the spine: |
| { |
| "question":"a provocative HEADLINE phrased as a question that hooks THIS audience", |
| "subhead":"one supporting line", |
| "painIntro":"1-2 empathetic sentences naming what they're feeling", |
| "pains":["3 specific pain points, short"], |
| "valueProps":[{"title":"","body":"1 sentence in the CUSTOMER's language"} , 3 to 4 items], |
| "kpis":[{"stat":"e.g. 38%","label":"what it measures"} , exactly 3 — only credible numbers, no fabrication], |
| "ctaHeadline":"action-oriented close", |
| "ctaButton":"2-4 words", |
| "formTitle":"short form heading", |
| "formFields":["3-4 short field labels"] |
| } |
| Voice: ${b.tone}. Specific, confident, calm. No hype.`; |
| try{ S.pages[a.id]=await almaJSON(prompt,{}); }catch(err){ S.pages[a.id]=fallbackPage(a,b); } |
| stepState(i,"ok"); |
| } |
| stepState(chosen.length,"run"); setProg(94); |
| S.siteHTML=buildSite(b,chosen,S.pages); |
| stepState(chosen.length,"ok"); setProg(100); stopLoading(); |
| renderPreview(chosen); setSteps("preview"); show("preview"); |
| }catch(e){ stopLoading(); renderAudiences(); setSteps("audiences"); show("audiences"); toast("Build hit a snag — try again"); } |
| } |
| function fallbackPage(a,b){ |
| return { question:"Is "+a.name+" getting what they need from "+b.name+"?", subhead:b.tagline||"", painIntro:a.painHint||"", |
| pains:[a.painHint||"Underserved needs","Generic messaging","No clear next step"], |
| valueProps:(b.services||["Tailored service","Proven results","Local & responsive"]).slice(0,3).map(s=>({title:s,body:""})), |
| kpis:[{stat:"100%",label:"focused on you"},{stat:"24h",label:"response time"},{stat:"5★",label:"client rating"}], |
| ctaHeadline:"Let's talk", ctaButton:"Get started", formTitle:"Request a callback", formFields:["Name","Email","Phone"] }; |
| } |
|
|
| /* ============================================================ |
| SITE ASSEMBLER — Ogden Web Solutions House Style |
| ============================================================ */ |
| function buildSite(b, auds, pages){ |
| const d=b.design||{}; |
| const base=d.base||"#0E0B08", accent=d.accent||"#C98A4A", cream=d.cream||"#F2EBDD"; |
| const disp=d.displayFont||"Fraunces", body=d.bodyFont||"Manrope"; |
| const fp=encodeURIComponent(disp)+":opsz,wght@9..144,300;9..144,400;9..144,500&family="+encodeURIComponent(body)+":wght@300;400;500;600"; |
| const idea=d.idea||b.tagline||b.category||""; |
|
|
| const navLinks=['<a href="#home">Home</a>','<a href="#idea">Approach</a>'] |
| .concat(auds.map(a=>'<a href="#'+a.id+'">'+escA(shortName(a.name))+'</a>')) |
| .concat(['<a href="#about">About</a>','<a href="#contact">Contact</a>']); |
|
|
| const p=[]; |
| p.push('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">'); |
| p.push('<title>'+escA(b.name)+(b.tagline?' — '+escA(b.tagline):'')+'</title>'); |
| p.push('<meta name="description" content="'+escA(b.description||b.tagline||b.name)+'">'); |
| p.push('<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'); |
| p.push('<link href="https://fonts.googleapis.com/css2?family='+fp+'&display=swap" rel="stylesheet">'); |
| p.push('<style>'+siteCSS(base,accent,cream,disp,body)+'</style></head><body>'); |
|
|
| // nav |
| p.push('<header class="nav"><div class="container nav-in"><a href="#home" class="brand">'+escA(b.name)+'</a>'+ |
| '<nav class="nlinks" id="nlinks">'+navLinks.join("")+'</nav>'+ |
| '<a href="#contact" class="navcta">'+escA(b.cta||"Start a conversation")+' →</a>'+ |
| '<button class="burger" id="burger" aria-label="Menu">☰</button></div></header>'); |
|
|
| // hero |
| p.push('<section id="home" class="hero"><div class="container">'+ |
| '<div class="hero-grid"><div class="hero-copy">'+ |
| '<span class="eyebrow">'+escA(b.location||b.category||"")+'</span>'+ |
| '<h1>'+heroHeadline(b)+'</h1>'+ |
| '<p class="lead">'+escA(b.description||"")+'</p>'+ |
| '<div class="hero-cta"><a href="#contact" class="btn-fill">'+escA(b.cta||"Start a conversation")+'</a>'+ |
| '<a href="#'+(auds[0]?auds[0].id:'idea')+'" class="btn-underline">See the work</a></div>'+ |
| '</div><div class="hero-art"><div class="art-frame"><span class="art-mark">'+escA(initials(b.name))+'</span><svg viewBox="0 0 200 200" class="art-arc"><circle cx="100" cy="100" r="86" /><circle cx="100" cy="100" r="60" /></svg></div></div></div>'+ |
| '</div></section>'); |
|
|
| // the idea / approach + services |
| p.push('<section id="idea" class="band"><div class="container idea-in">'+ |
| '<div class="idea-l"><span class="eyebrow">The idea</span><h2 class="serif idea-h">'+escA(idea)+'</h2></div>'+ |
| '<div class="idea-r"><div class="svc-list">'+(b.services||[]).map((s,i)=>'<div class="svc"><span class="svc-n">'+String(i+1).padStart(2,"0")+'</span><span class="svc-t">'+escA(s)+'</span></div>').join("")+'</div></div>'+ |
| '</div></section>'); |
|
|
| // audience pages — Audience Engine Drop spine |
| auds.forEach((a,idx)=>{ |
| const pg=pages[a.id]||{}; |
| p.push('<section id="'+a.id+'" class="aud-page"><div class="container">'+ |
| '<div class="ap-head"><span class="eyebrow">For '+escA(a.name)+'</span>'+ |
| '<span class="ap-idx">'+String(idx+1).padStart(2,"0")+' / '+String(auds.length).padStart(2,"0")+'</span></div>'+ |
| '<h2 class="hook">'+escA(pg.question||"")+'</h2>'+ |
| (pg.subhead?'<p class="hook-sub">'+escA(pg.subhead)+'</p>':"")+ |
| '<div class="ap-body">'+ |
| '<div class="ap-pain"><span class="ap-label">The pain</span><p>'+escA(pg.painIntro||"")+'</p><ul>'+ |
| (pg.pains||[]).map(x=>'<li>'+escA(x)+'</li>').join("")+'</ul></div>'+ |
| '<div class="ap-value"><span class="ap-label">What changes</span>'+ |
| (pg.valueProps||[]).map(v=>'<div class="vp"><b>'+escA(v.title||"")+'</b>'+(v.body?'<span>'+escA(v.body)+'</span>':"")+'</div>').join("")+'</div>'+ |
| '</div>'+ |
| '<div class="kpis">'+(pg.kpis||[]).map(k=>'<div class="kpi"><div class="stat">'+escA(k.stat||"")+'</div><div class="klabel">'+escA(k.label||"")+'</div></div>').join("")+'</div>'+ |
| '<div class="cta-band"><div class="cta-l"><h3>'+escA(pg.ctaHeadline||"Ready when you are")+'</h3></div>'+ |
| '<form class="lead-form" onsubmit="return almaLead(event,\''+escA(a.name)+'\')">'+ |
| '<div class="lf-title">'+escA(pg.formTitle||"Get in touch")+'</div>'+ |
| (pg.formFields||["Name","Email","Phone"]).map(f=>fieldFor(f)).join("")+ |
| '<button class="btn-fill lf-btn" type="submit">'+escA(pg.ctaButton||"Send")+'</button>'+ |
| '<div class="lf-ok">Thank you — we\'ll be in touch shortly.</div>'+ |
| '</form></div>'+ |
| '</div></section>'); |
| }); |
|
|
| // about |
| p.push('<section id="about" class="band"><div class="container about-in">'+ |
| '<div><span class="eyebrow">About</span><h2 class="serif about-h">'+escA(b.name)+'</h2>'+ |
| '<p class="lead">'+escA(b.description||"")+'</p>'+(b.location?'<p class="muted-line">'+escA(b.location)+'</p>':"")+'</div>'+ |
| '<div class="about-panel"><span class="ap-vibe serif">'+escA(d.vibe||"crafted")+'</span>'+(b.tone?'<p>'+escA(b.tone)+'</p>':"")+'</div>'+ |
| '</div></section>'); |
|
|
| // contact |
| p.push('<section id="contact" class="contact"><div class="container contact-in">'+ |
| '<div><span class="eyebrow">Contact</span><h2 class="serif contact-h">Let\'s talk.</h2>'+ |
| (b.phone?'<a class="cline" href="tel:'+escA(b.phone)+'">'+escA(b.phone)+'</a>':"")+ |
| (b.email?'<a class="cline" href="mailto:'+escA(b.email)+'">'+escA(b.email)+'</a>':"")+ |
| (b.location?'<div class="cline muted-line">'+escA(b.location)+'</div>':"")+'</div>'+ |
| '<form class="lead-form" onsubmit="return almaLead(event,\'General\')">'+ |
| '<div class="lf-title">Send a message</div>'+fieldFor("Name")+fieldFor("Email")+fieldFor("Phone")+fieldFor("Message")+ |
| '<button class="btn-fill lf-btn" type="submit">Send message</button>'+ |
| '<div class="lf-ok">Thank you — we\'ll be in touch shortly.</div></form>'+ |
| '</div></section>'); |
|
|
| // footer |
| p.push('<footer class="ftr"><div class="container ftr-in"><div class="brand">'+escA(b.name)+'</div>'+ |
| '<div class="muted-line">© '+new Date().getFullYear()+' '+escA(b.name)+(b.location?' · '+escA(b.location):"")+'</div></div></footer>'); |
|
|
| // chat widget |
| p.push('<button id="cBtn" class="chat-btn" aria-label="Chat">Ask</button>'+ |
| '<div id="cBox" class="chat-box"><div class="chat-head"><b>Ask us anything</b><button id="cClose">✕</button></div>'+ |
| '<div id="cLog" class="chat-log"><div class="cm bot">Hi — how can we help you today?</div></div>'+ |
| '<div class="chat-in"><input id="cInput" placeholder="Type a message…"><button id="cSend">→</button></div></div>'); |
| p.push('<script>'+siteJS(b)+'<\/script></body></html>'); |
| return p.join(""); |
| } |
|
|
| function heroHeadline(b){ |
| // one strong idea, big and thin, with the brand name as the anchor |
| const nm=escA(b.name); |
| const tl=b.tagline?('<span class="h-sub serif">'+escA(b.tagline)+'</span>'):""; |
| return nm+tl; |
| } |
| function initials(s){ s=String(s||"").trim().split(/\s+/).map(w=>w[0]).join("").slice(0,3).toUpperCase(); return s||"·"; } |
|
|
| function fieldFor(label){ |
| const l=(label||"").toLowerCase(); |
| if(l.indexOf("message")>=0||l.indexOf("challenge")>=0||l.indexOf("need")>=0||l.indexOf("project")>=0||l.indexOf("detail")>=0) |
| return '<label class="lf">'+escA(label)+'<textarea name="'+escA(label)+'" rows="3"></textarea></label>'; |
| const t=l.indexOf("email")>=0?"email":(l.indexOf("phone")>=0?"tel":"text"); |
| return '<label class="lf">'+escA(label)+'<input type="'+t+'" name="'+escA(label)+'"></label>'; |
| } |
|
|
| function siteCSS(base,accent,cream,disp,body){ |
| const onAccent=contrast(accent); |
| const line=withAlpha(cream,.14), line2=withAlpha(cream,.26), dim=withAlpha(cream,.72), muted=withAlpha(cream,.46); |
| const tintA=withAlpha(accent,.10), tintB=withAlpha(accent,.16); |
| return [ |
| "*{box-sizing:border-box;margin:0;padding:0}", |
| "html{scroll-behavior:smooth}", |
| "body{background:"+base+";color:"+cream+";font-family:'"+body+"',system-ui,sans-serif;font-size:17px;font-weight:400;line-height:1.66;-webkit-font-smoothing:antialiased}", |
| "h1,h2,h3,h4{font-family:'"+disp+"',Georgia,serif;font-weight:400;line-height:1.04;letter-spacing:-.012em}", |
| ".serif{font-family:'"+disp+"',Georgia,serif}", |
| ".container{width:min(100% - 48px,1180px);margin-inline:auto}", |
| "a{color:inherit;text-decoration:none}", |
| ".eyebrow{font-family:'"+body+"';font-size:.72rem;letter-spacing:.26em;text-transform:uppercase;font-weight:600;color:"+accent+"}", |
| ".muted-line{color:"+muted+";font-weight:300}", |
| "section{scroll-margin-top:72px}", |
| ".btn-fill{display:inline-block;background:"+accent+";color:"+onAccent+";font-family:'"+body+"';font-weight:600;font-size:.92rem;padding:15px 30px;letter-spacing:.01em;transition:transform .25s,background .25s}", |
| ".btn-fill:hover{transform:translateY(-2px)}", |
| ".btn-underline{display:inline-block;font-family:'"+body+"';font-weight:500;font-size:.92rem;padding:15px 4px;position:relative}", |
| ".btn-underline:after{content:'';position:absolute;left:4px;right:4px;bottom:9px;height:1px;background:"+accent+";transform:scaleX(.35);transform-origin:left;transition:transform .3s}", |
| ".btn-underline:hover:after{transform:scaleX(1)}", |
| /* nav */ |
| ".nav{position:sticky;top:0;z-index:40;background:"+withAlpha(base,.86)+";backdrop-filter:blur(12px);border-bottom:1px solid "+line+"}", |
| ".nav-in{display:flex;align-items:center;gap:22px;padding:18px 0}", |
| ".brand{font-family:'"+disp+"';font-weight:500;font-size:1.4rem;margin-right:auto;letter-spacing:-.01em}", |
| ".nlinks{display:flex;gap:2px;flex-wrap:wrap}", |
| ".nlinks a{font-family:'"+body+"';font-size:.85rem;font-weight:400;color:"+dim+";padding:8px 12px;transition:.2s}", |
| ".nlinks a:hover{color:"+cream+"}", |
| ".navcta{font-family:'"+body+"';font-size:.85rem;font-weight:600;color:"+accent+";white-space:nowrap}", |
| ".burger{display:none;background:none;border:1px solid "+line2+";color:"+cream+";font-size:1.1rem;padding:7px 12px;cursor:pointer}", |
| /* hero */ |
| ".hero{padding:clamp(80px,12vw,150px) 0 clamp(70px,10vw,130px);position:relative}", |
| ".hero-grid{display:grid;grid-template-columns:1.5fr .9fr;gap:50px;align-items:center}", |
| ".hero h1{font-size:clamp(3rem,8vw,6.4rem);font-weight:300;letter-spacing:-.03em;margin:22px 0 0}", |
| ".hero h1 .h-sub{display:block;font-size:clamp(1.3rem,3vw,2.2rem);font-style:italic;color:"+accent+";font-weight:300;margin-top:14px;letter-spacing:-.01em}", |
| ".lead{font-size:1.2rem;color:"+dim+";max-width:50ch;margin-top:30px;font-weight:300}", |
| ".hero-cta{display:flex;gap:24px;align-items:center;margin-top:38px;flex-wrap:wrap}", |
| ".hero-art{display:flex;justify-content:center}", |
| ".art-frame{position:relative;width:min(320px,80%);aspect-ratio:3/4;background:linear-gradient(160deg,"+tintB+","+tintA+");border:1px solid "+line2+";display:grid;place-items:center;overflow:hidden}", |
| ".art-mark{font-family:'"+disp+"';font-size:clamp(3rem,7vw,5rem);font-weight:300;color:"+cream+";z-index:2;letter-spacing:.04em}", |
| ".art-arc{position:absolute;width:130%;height:130%;opacity:.5}.art-arc circle{fill:none;stroke:"+accent+";stroke-width:.5}", |
| /* idea band */ |
| ".band{padding:clamp(80px,11vw,150px) 0;border-top:1px solid "+line+"}", |
| ".idea-in{display:grid;grid-template-columns:1fr 1fr;gap:60px;align-items:start}", |
| ".idea-h{font-size:clamp(2rem,4.5vw,3.4rem);font-weight:300;margin-top:18px;max-width:14ch}", |
| ".svc-list{display:grid;gap:0}", |
| ".svc{display:flex;gap:20px;align-items:baseline;padding:20px 0;border-top:1px solid "+line+"}", |
| ".svc:last-child{border-bottom:1px solid "+line+"}", |
| ".svc-n{font-family:'"+disp+"';color:"+accent+";font-size:1rem;font-weight:400}", |
| ".svc-t{font-size:1.25rem;font-weight:300;font-family:'"+disp+"'}", |
| /* audience page */ |
| ".aud-page{padding:clamp(80px,11vw,150px) 0;border-top:1px solid "+line+"}", |
| ".ap-head{display:flex;justify-content:space-between;align-items:baseline;gap:16px}", |
| ".ap-idx{font-family:'"+disp+"';color:"+muted+";font-size:.95rem}", |
| ".hook{font-size:clamp(2.2rem,5.2vw,4rem);font-weight:300;max-width:17ch;margin:18px 0}", |
| ".hook-sub{font-size:1.25rem;color:"+dim+";max-width:52ch;font-weight:300;margin-bottom:8px}", |
| ".ap-body{display:grid;grid-template-columns:1fr 1fr;gap:48px;margin:50px 0 10px}", |
| ".ap-label{font-family:'"+body+"';font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:"+accent+";display:block;margin-bottom:18px}", |
| ".ap-pain p{color:"+dim+";font-weight:300;margin-bottom:18px;font-size:1.08rem}", |
| ".ap-pain ul{list-style:none;display:grid;gap:14px}", |
| ".ap-pain li{padding-left:24px;position:relative;color:"+cream+";font-weight:300}", |
| ".ap-pain li:before{content:'—';position:absolute;left:0;color:"+accent+"}", |
| ".ap-value{display:grid;gap:0;align-content:start}", |
| ".vp{padding:18px 0;border-top:1px solid "+line+"}", |
| ".vp b{display:block;font-family:'"+disp+"';font-size:1.3rem;font-weight:400;margin-bottom:4px}", |
| ".vp span{color:"+dim+";font-size:.98rem;font-weight:300}", |
| ".kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:"+line+";border:1px solid "+line+";margin:54px 0}", |
| ".kpi{background:"+base+";padding:34px 24px;text-align:center}", |
| ".stat{font-family:'"+disp+"';font-size:clamp(2.6rem,6vw,4.4rem);font-weight:300;color:"+accent+";line-height:1}", |
| ".klabel{color:"+muted+";font-size:.88rem;margin-top:10px;font-weight:300}", |
| ".cta-band{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center;border-top:1px solid "+line2+";padding-top:54px}", |
| ".cta-l h3{font-size:clamp(1.8rem,3.6vw,2.8rem);font-weight:300}", |
| /* forms */ |
| ".lead-form{}", |
| ".lf-title{font-family:'"+disp+"';font-weight:400;font-size:1.3rem;margin-bottom:18px}", |
| ".lf{display:block;font-family:'"+body+"';font-size:.68rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:"+muted+";margin-bottom:18px}", |
| ".lf input,.lf textarea{width:100%;margin-top:8px;background:transparent;border:none;border-bottom:1px solid "+line2+";color:"+cream+";font-family:'"+body+"';font-size:1rem;font-weight:400;padding:8px 0;text-transform:none;letter-spacing:0}", |
| ".lf input:focus,.lf textarea:focus{outline:none;border-color:"+accent+"}", |
| ".lf-btn{border:none;cursor:pointer;margin-top:8px;width:100%;text-align:center}", |
| ".lf-ok{display:none;margin-top:16px;color:"+accent+";font-weight:500}", |
| /* about / contact */ |
| ".about-in,.contact-in{display:grid;grid-template-columns:1.1fr .9fr;gap:56px;align-items:center}", |
| ".about-h,.contact-h{font-size:clamp(2rem,4.5vw,3.2rem);font-weight:300;margin:16px 0 22px}", |
| ".about-panel{aspect-ratio:4/3;background:linear-gradient(160deg,"+tintB+","+tintA+");border:1px solid "+line2+";padding:40px;display:flex;flex-direction:column;justify-content:flex-end}", |
| ".ap-vibe{font-size:2.2rem;font-weight:300;text-transform:capitalize}", |
| ".about-panel p{color:"+dim+";font-weight:300;margin-top:8px}", |
| ".contact{padding:clamp(80px,11vw,150px) 0;border-top:1px solid "+line+"}", |
| ".cline{display:block;font-size:1.35rem;font-family:'"+disp+"';font-weight:300;margin:12px 0;transition:.2s}", |
| "a.cline:hover{color:"+accent+"}", |
| /* footer */ |
| ".ftr{padding:46px 0;border-top:1px solid "+line+"}", |
| ".ftr-in{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;align-items:center}", |
| ".ftr .brand{font-size:1.2rem}", |
| /* chat */ |
| ".chat-btn{position:fixed;right:22px;bottom:22px;background:"+accent+";color:"+onAccent+";font-family:'"+body+"';font-weight:600;font-size:.9rem;border:none;padding:14px 22px;cursor:pointer;z-index:60;box-shadow:0 16px 34px -14px rgba(0,0,0,.6)}", |
| ".chat-box{position:fixed;right:22px;bottom:78px;width:min(340px,calc(100vw - 44px));background:"+base+";border:1px solid "+line2+";display:none;flex-direction:column;overflow:hidden;z-index:60;box-shadow:0 30px 70px -24px rgba(0,0,0,.7)}", |
| ".chat-box.open{display:flex}", |
| ".chat-head{background:"+accent+";color:"+onAccent+";padding:14px 16px;display:flex;justify-content:space-between;align-items:center;font-weight:600}", |
| ".chat-head button{background:none;border:none;color:inherit;cursor:pointer}", |
| ".chat-log{padding:14px;max-height:300px;overflow:auto;display:flex;flex-direction:column;gap:9px}", |
| ".cm{padding:10px 13px;font-size:.92rem;max-width:86%;font-weight:300}", |
| ".cm.bot{background:"+withAlpha(cream,.06)+";align-self:flex-start}", |
| ".cm.me{background:"+accent+";color:"+onAccent+";align-self:flex-end}", |
| ".chat-in{display:flex;gap:8px;padding:12px;border-top:1px solid "+line+"}", |
| ".chat-in input{flex:1;background:transparent;border:1px solid "+line2+";color:"+cream+";padding:10px 12px;font-family:'"+body+"'}", |
| ".chat-in button{background:"+accent+";color:"+onAccent+";border:none;width:42px;cursor:pointer}", |
| "@media(max-width:860px){.hero-grid,.idea-in,.ap-body,.kpis,.cta-band,.about-in,.contact-in{grid-template-columns:1fr}.hero-art{display:none}.kpis{grid-template-columns:1fr 1fr}.nlinks{display:none;position:absolute;top:64px;left:0;right:0;background:"+base+";flex-direction:column;padding:14px 24px;border-bottom:1px solid "+line+"}.nlinks.open{display:flex}.burger{display:block}.navcta{display:none}}" |
| ].join(""); |
| } |
|
|
| function siteJS(b){ |
| var lead=(b.email||"").replace(/'/g,"")||"owner@example.com"; |
| return [ |
| "var LEAD_EMAIL='"+lead+"';", |
| "var ALMA_CHAT_ENDPOINT='';", |
| "document.getElementById('burger').onclick=function(){document.getElementById('nlinks').classList.toggle('open');};", |
| "document.querySelectorAll('#nlinks a').forEach(function(a){a.onclick=function(){document.getElementById('nlinks').classList.remove('open');};});", |
| "window.almaLead=function(e,aud){e.preventDefault();var f=e.target;var fd={};Array.prototype.forEach.call(f.elements,function(el){if(el.name)fd[el.name]=el.value;});", |
| "fd['Audience Page']=aud;var b=f.querySelector('button');b.disabled=true;b.textContent='Sending…';", |
| "fetch('https://formsubmit.co/ajax/'+LEAD_EMAIL,{method:'POST',headers:{'Content-Type':'application/json',Accept:'application/json'},body:JSON.stringify(fd)})", |
| ".then(function(r){if(!r.ok)throw 0;f.querySelectorAll('label,button').forEach(function(x){x.style.display='none';});f.querySelector('.lf-ok').style.display='block';})", |
| ".catch(function(){b.disabled=false;b.textContent='Try again';alert('Please email us at '+LEAD_EMAIL);});return false;};", |
| "var cb=document.getElementById('cBox');document.getElementById('cBtn').onclick=function(){cb.classList.toggle('open');};", |
| "document.getElementById('cClose').onclick=function(){cb.classList.remove('open');};", |
| "function add(t,w){var d=document.createElement('div');d.className='cm '+w;d.textContent=t;var l=document.getElementById('cLog');l.appendChild(d);l.scrollTop=l.scrollHeight;}", |
| "function sc(){var i=document.getElementById('cInput');var v=i.value.trim();if(!v)return;add(v,'me');i.value='';", |
| "if(!ALMA_CHAT_ENDPOINT){setTimeout(function(){add('Thanks! For the fastest answer, call or email us and a team member will help right away.','bot');},500);return;}", |
| "fetch(ALMA_CHAT_ENDPOINT,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:v,business:'"+escJS(b.name)+"'})}).then(function(r){return r.json();}).then(function(d){add(d.reply||'…','bot');}).catch(function(){add('Sorry, please try again or contact us directly.','bot');});}", |
| "document.getElementById('cSend').onclick=sc;document.getElementById('cInput').addEventListener('keydown',function(e){if(e.key==='Enter')sc();});", |
| "var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.style.opacity=1;e.target.style.transform='none';io.unobserve(e.target);}})},{threshold:.08});", |
| "document.querySelectorAll('section > .container').forEach(function(el){el.style.opacity=0;el.style.transform='translateY(20px)';el.style.transition='opacity .8s cubic-bezier(.22,.61,.36,1),transform .8s cubic-bezier(.22,.61,.36,1)';io.observe(el);});" |
| ].join("\n"); |
| } |
|
|
| /* ---------- preview ---------- */ |
| function renderPreview(auds){ |
| const b=S.biz; |
| document.getElementById("pv-title").textContent=b.name+" — ready"; |
| document.getElementById("frame-url").textContent=(b.domain||slug(b.name)+".com"); |
| const rail=document.getElementById("rail"); |
| rail.innerHTML= |
| '<div class="grp"><h5>Pages</h5>'+navbBtn("home","Home")+navbBtn("idea","Approach")+'</div>'+ |
| '<div class="grp"><h5>Audience pages</h5>'+auds.map(a=>navbBtn(a.id,shortName(a.name),true)).join("")+'</div>'+ |
| '<div class="grp"><h5>More</h5>'+navbBtn("about","About")+navbBtn("contact","Contact")+'</div>'; |
| rail.querySelectorAll(".navb").forEach(el=>el.onclick=()=>jumpTo(el.dataset.t)); |
| loadFrame(); setActiveNav("home"); |
| } |
| function navbBtn(id,label,aud){ return '<button class="navb" data-t="'+id+'">'+esc(label)+(aud?'<span class="vid" onclick="event.stopPropagation();showVideo(\''+id+'\')">ad ▶</span>':'')+'</button>'; } |
| function loadFrame(){ document.getElementById("site-frame").srcdoc=S.siteHTML; } |
| function jumpTo(id){ setActiveNav(id); const f=document.getElementById("site-frame"); try{ const el=f.contentDocument.getElementById(id); if(el) el.scrollIntoView({behavior:"smooth"}); }catch(e){} } |
| function setActiveNav(id){ document.querySelectorAll(".navb").forEach(el=>el.classList.toggle("active",el.dataset.t===id)); } |
|
|
| /* ---------- video ---------- */ |
| async function showVideo(id){ |
| const a=S.audiences.find(x=>x.id===id); if(!a) return; |
| document.getElementById("vid-scrim").classList.add("on"); |
| document.getElementById("vid-title").textContent=a.name+" — TikTok / Facebook"; |
| const bodyEl=document.getElementById("vid-body"); |
| if(a._video){ bodyEl.innerHTML=renderVideo(a._video); return; } |
| bodyEl.innerHTML='<div class="vid-block" style="text-align:center;color:var(--muted)">Alma is writing the script…</div>'; |
| try{ |
| const b=S.biz; |
| const prompt=`Write a 25-second vertical short-form video ad (TikTok/Facebook/Reels) for "${b.name}" targeting ${a.name} (${a.who}). Pain: ${a.painHint||a.whyNow}. |
| Return ONLY JSON: {"hook":"first-2-second scroll-stopper","scenes":[{"visual":"what's on screen","vo":"voiceover line"} x3-4],"cta":"closing call to action","captions":["3 punchy on-screen captions"]}`; |
| a._video=await almaJSON(prompt,{}); bodyEl.innerHTML=renderVideo(a._video); |
| }catch(e){ bodyEl.innerHTML='<div class="err">Couldn\'t generate the script — try again.</div>'; } |
| } |
| function renderVideo(v){ |
| return '<div class="vid-block"><div class="lbl">Hook · 0–2s</div><b class="serif" style="font-size:1.2rem">'+esc(v.hook||"")+'</b></div>'+ |
| '<div class="vid-block"><div class="lbl">Scenes</div>'+(v.scenes||[]).map((s,i)=>'<div class="scene"><b>Scene '+(i+1)+' · </b>'+esc(s.visual||"")+'<br><span class="dim" style="font-weight:300">VO — '+esc(s.vo||"")+'</span></div>').join("")+'</div>'+ |
| '<div class="vid-block"><div class="lbl">Close</div><b class="serif" style="font-size:1.1rem">'+esc(v.cta||"")+'</b></div>'+ |
| '<div class="vid-block"><div class="lbl">On-screen captions</div>'+(v.captions||[]).map(c=>'· '+esc(c)).join("<br>")+'</div>'; |
| } |
|
|
| /* ---------- export ---------- */ |
| function exportSite(){ |
| const blob=new Blob([S.siteHTML],{type:"text/html"}), url=URL.createObjectURL(blob); |
| const a=document.createElement("a"); a.href=url; a.download=slug(S.biz.name)+"-website.html"; a.click(); URL.revokeObjectURL(url); |
| toast("Site exported — ready to host"); |
| } |
|
|
| /* ---------- utils ---------- */ |
| function esc(s){ return String(s==null?"":s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); } |
| function escA(s){ return esc(s).replace(/"/g,"""); } |
| function escJS(s){ return String(s==null?"":s).replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g," "); } |
| function slug(s){ return String(s||"site").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"site"; } |
| function shortName(s){ s=String(s||""); return s.length>22?s.slice(0,20)+"…":s; } |
| function cleanHex(h){ if(!h)return null; h=String(h).trim(); const m=h.match(/#?[0-9a-fA-F]{6}/); return m?("#"+m[0].replace("#","")):null; } |
| function hexToRgb(hex){ if(!hex)return null; hex=hex.replace("#",""); if(hex.length===3)hex=hex.split("").map(x=>x+x).join(""); if(hex.length<6)return null; return {r:parseInt(hex.slice(0,2),16),g:parseInt(hex.slice(2,4),16),b:parseInt(hex.slice(4,6),16)}; } |
| function isDark(hex){ const c=hexToRgb(hex); return c?(c.r*.299+c.g*.587+c.b*.114)<140:true; } |
| function contrast(hex){ return isDark(hex)?"#ffffff":"#16110a"; } |
| function withAlpha(hex,a){ const c=hexToRgb(hex); return c?("rgba("+c.r+","+c.g+","+c.b+","+a+")"):hex; } |
|
|
| /* ---------- wire ---------- */ |
| document.getElementById("btn-build").onclick=runResearch; |
| document.getElementById("in-main").addEventListener("keydown",e=>{ if(e.key==="Enter") runResearch(); }); |
| document.getElementById("btn-to-audiences").onclick=runAudiences; |
| document.getElementById("btn-back-input").onclick=()=>{ setSteps("input"); show("input"); }; |
| document.getElementById("btn-back-verify").onclick=()=>{ setSteps("verify"); show("verify"); }; |
| document.getElementById("btn-generate").onclick=runGenerate; |
| document.getElementById("btn-export").onclick=exportSite; |
| document.getElementById("btn-new").onclick=()=>{ S.biz=null;S.audiences=[];S.pages={};S.siteHTML=""; document.getElementById("input-err").innerHTML=""; document.getElementById("in-main").value=""; setSteps("input"); show("input"); }; |
| document.getElementById("btn-open-tab").onclick=()=>{ const w=window.open("","_blank"); if(w){ w.document.open(); w.document.write(S.siteHTML); w.document.close(); } }; |
| document.getElementById("vid-x").onclick=()=>document.getElementById("vid-scrim").classList.remove("on"); |
| document.getElementById("vid-scrim").onclick=e=>{ if(e.target.id==="vid-scrim") e.currentTarget.classList.remove("on"); }; |
| window.showVideo=showVideo; |
| setSteps("input"); |
| </script> |
| </body> |
| </html> |
| |