Add Kickstarter story image HTML sources and build scripts

This commit is contained in:
pat
2026-09-25 17:36:23 +00:00
parent e5adccf279
commit d05d283d6d
54 changed files with 3447 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

@@ -0,0 +1,34 @@
{
"laptop": {
"vw": 1120,
"vh": 700,
"essay": [
328,
95.21875,
201.921875,
29
],
"workflow": [
357,
364.09375,
200.046875,
37.625
]
},
"phone": {
"vw": 390,
"vh": 844,
"tryit": [
61,
114.8125,
48.3125,
15
],
"updated": [
61,
769.78125,
170.75,
19
]
}
}
@@ -0,0 +1,18 @@
{
"w": 1000,
"h": 620,
"pins": {
"orlando": [
820.8,
532.8
],
"denver": [
345.5,
276.4
],
"villapark": [
653.8,
225.8
]
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
# Owner-supplied photos
Drop real photos here (jpg, png or webp) using these base names, then run `python build.py` from `src/`.
Images whose slots are filled lose the `-PLACEHOLDER` suffix.
| File name (any of .jpg/.png/.webp) | Used in | Minimum size |
|---|---|---|
| cover | 01b welcome (final front cover) | 1600 px tall, web version of the final cover |
| retreat-venue | 07b Retreat | 1360 × 765, landscape |
| site-orlando, site-north-denver, site-villa-park | 08c Academy sites | 800 × 600 each, landscape |
| reader-1, reader-2, reader-3 | 13b early reader quotes | 400 × 400, square |
| author-1, author-2 (add more in build.py) | 14b authors | 800 × 800, square |
| team-thy, team-lfg, team-ceo, team-gtm, team-osinachi-patrick, team-academy-lead | 14b team | 800 × 800, square |
+645
View File
@@ -0,0 +1,645 @@
"""Build and render the We Own AI Kickstarter story images.
Usage: python build.py (render everything into ../)
Owner photos: drop files into src/assets/ using the names in SLOT_FILES (jpg/png/webp) and re-run;
images whose photo slots are still empty get a -PLACEHOLDER suffix.
Sources of truth: ks-research/kickstarter-campaign-guide.md (v2), Story section and Part 3.1."""
import asyncio, json, math, pathlib, re, sys
from icons import ICONS, ILLUS, MARK, icon
HERE = pathlib.Path(__file__).parent
OUT = HERE.parent
PAGES = HERE / 'pages'
PAGES.mkdir(exist_ok=True)
MAP = json.loads((HERE / 'assets-gen' / 'us-map.json').read_text())
MAP_SVG = (HERE / 'assets-gen' / 'us-map.svg').read_text()
SCREENS = json.loads((HERE / 'assets-gen' / 'screens.json').read_text())
TITLE = 'We Own AI — The New American Dream'
HEAD = """<!doctype html><html lang="en"><head><meta charset="utf-8">
{extra_css}<link rel="stylesheet" href="../styles.css"><style>{css}</style></head><body>{body}
<script>
window.__ready = (async () => {{
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {{
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {{
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => {{ const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; }});
if (ok) {{ s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }}
}}
if (!ok) empty++;
}}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {{
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) {{ fs -= 0.5; el.style.fontSize = fs + 'px'; }}
}}
if (window.afterLayout) await window.afterLayout();
return empty;
}})();
</script></body></html>"""
def slot(src, label, size, cls='', style=''):
return (f'<div class="slot {cls}" data-src="../assets/{src}" style="{style}"><div class="ph">{ICONS["camera"]}'
f'<b>{label}</b><small>{size}</small></div></div>')
pages = [] # dicts: name, body, css, sel, w, h(optional), dpr
def page(name, body, css='', w=680, h=None, extra_css='', px_h=None):
pages.append(dict(name=name, body=body, css=css, w=w, h=h, extra_css=extra_css, px_h=px_h))
# ---------------------------------------------------------------- headers (1360 x 300)
HEADERS = [
('01', 'welcome', '👋', 'Welcome'), ('02', 'why-this-book', '❤️', 'Why this book'),
('03', 'who-its-for', '🎯', 'Who it’s for'), ('04', 'whats-inside', '📖', 'What’s inside'),
('05', 'try-one-workflow', '🧪', 'Try one workflow'), ('06', 'three-ways-to-back', '🧭', 'Three ways to back'),
('07', 'the-retreat', '🏕️', 'The Retreat'), ('08', 'the-academy', '🎓', 'The Academy'),
('09', 'cloud-and-aibook-fun', '☁️', 'Cloud and AiBook.fun'), ('10', 'stretch-goals', '🎁', 'Stretch goals'),
('11', 'timeline', '⏳', 'Timeline'), ('12', 'shipping-and-delivery', '📦', 'Shipping and delivery'),
('13', 'early-readers', '👀', 'Early readers'), ('14', 'authors-and-team', '✍️', 'The authors and team'),
('15', 'special-thanks', '🙏', 'Special thanks'), ('16', 'how-to-help', '📣', 'How to help'),
]
def header(num, slug, emoji, title):
body = (f'<div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div>'
f'<div class="mark">{MARK}</div><div class="badge"><span class="emoji">{emoji}</span></div>'
f'<div style="min-width:0;flex:1"><div class="kicker">{TITLE}</div><h1 data-fit>{title}</h1></div><div class="rule"></div></div>')
return body
HDR = {n: (f'{n}-header-{s}', header(n, s, e, t)) for n, s, e, t in HEADERS}
def add_header(n):
name, body = HDR[n]; page(name, body, w=680, h=150)
# ---------------------------------------------------------------- 01 welcome
add_header('01')
page('01b-welcome-cover-icons', f'''
<div class="canvas" id="shot">
<div class="wel">
<div class="bookwrap"><div class="book">{slot("cover", "Book cover goes here", "Final cover · owner supplies", "cover")}</div></div>
<div class="wtxt">
<h2>Welcome</h2>
<p class="t">{TITLE}</p>
<p class="s">A field book for advisors who want to use AI in their practice in ways they control.</p>
<p class="pub">BurnedOut Media / WeOwnNet</p>
</div>
</div>
<div class="trio">
<div>{icon("authors", "ico big")}<p>Multi-author field book</p></div>
<div>{icon("tested", "ico big")}<p>Tested AI workflows</p></div>
<div>{icon("updated", "ico big")}<p>Library updated often</p></div>
</div>
</div>''', css='''
.wel{display:flex;gap:34px;align-items:center;padding:6px 6px 26px}
.bookwrap{flex:none;padding:6px 14px 14px 4px}
.book{width:184px;height:258px;border-radius:3px 8px 8px 3px;position:relative;box-shadow:10px 12px 0 -3px #efe9db,10px 12px 0 -2px #d6cdba,0 26px 40px -18px rgba(18,63,52,.5)}
.book .slot{position:absolute;inset:0;border-radius:3px 8px 8px 3px}
.book .slot .ph b{font-size:15px}
.wtxt h2{font-family:var(--display);font-weight:900;font-size:50px;letter-spacing:-.03em;line-height:1;color:var(--pine-deep)}
.wtxt .t{font-family:var(--display);font-weight:600;font-size:22px;letter-spacing:-.02em;line-height:1.2;margin:14px 0 12px}
.wtxt .s{font-size:16px;line-height:1.5;color:var(--muted);max-width:25rem}
.wtxt .pub{font-family:var(--mono);font-size:13px;letter-spacing:.06em;color:var(--pine);margin-top:16px}
.trio{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.trio>div{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:20px 14px 18px;text-align:center;box-shadow:var(--sh-sm)}
.ico.big{width:58px;height:58px;padding:13px;border-radius:999px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:block;margin:0 auto 12px}
.trio p{font-weight:700;font-size:16px;line-height:1.3}
''')
# ---------------------------------------------------------------- 02 why this book
add_header('02')
WHY = [('newsletters', 'Too much noise', 'Each week brings a new tool and a new prediction, and very little of it fits a real client file.'),
('lockedwindow', 'Processes you don’t control', 'When your process lives inside a vendor’s product, a price change or a feature change can undo it.'),
('clock11', 'No time to learn', 'The job already takes up the evenings, and learning AI on top of it feels like a second job.'),
('chair', 'Clients are changing', 'Firms and clients are changing how they work faster than most playbooks are updated.')]
page('02b-why-this-book-cards', '<div class="canvas" id="shot"><div class="g">' + ''.join(
f'<div class="card c"><div class="il">{ILLUS[k]}</div><h3>{t}</h3><p>{d}</p></div>' for k, t, d in WHY) + '</div></div>', css='''
.g{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.c{padding:20px 22px 22px}
.il{width:120px;height:84px;margin:0 0 12px -6px}
.c h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.15;margin-bottom:8px}
.c p{font-size:15px;line-height:1.5;color:var(--muted)}
''')
# ---------------------------------------------------------------- 03 who it's for
add_header('03')
page('03b-audience-cards', f'''<div class="canvas" id="shot">
<p class="spec">Mainly for</p>
<div class="two">
<div class="card a">{icon("accounting", "ico big")}<h3>Accounting advisors</h3><p class="k">Exit to Client</p></div>
<div class="card a">{icon("chart", "ico big")}<h3>Financial advisors</h3><p class="k">CKA® and CEPA® focus</p></div>
</div>
<p class="spec" style="margin-top:20px">Also for</p>
<div class="card b">{icon("briefcase", "ico mid")}<h3>Other professionals who advise clients</h3></div>
</div>''', css='''
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
.a{padding:24px 24px 26px;border-top:4px solid var(--pine)}
.ico.big{width:60px;height:60px;padding:14px;border-radius:14px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:block;margin-bottom:18px}
.a h3{font-family:var(--display);font-weight:900;font-size:28px;letter-spacing:-.025em;line-height:1.08}
.a .k{font-family:var(--mono);font-size:15px;font-weight:600;letter-spacing:.02em;color:var(--pine);margin-top:10px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:inline-block;padding:5px 12px;border-radius:999px}
.b{margin-top:12px;padding:16px 22px;display:flex;align-items:center;gap:16px}
.ico.mid{width:44px;height:44px;padding:10px;border-radius:12px;background:var(--paper-2);border:1px solid var(--line);color:var(--muted)}
.b h3{font-family:var(--display);font-weight:600;font-size:20px;letter-spacing:-.02em}
''')
# ---------------------------------------------------------------- 04 what's inside
add_header('04')
RECIPE = [('Problem', 'The problem', 'The real task it solves.'),
('Who runs it', 'Who runs it', 'A person, a role, or an AI agent working under a person’s supervision.'),
('Setup', 'Setup', 'The tools, sample files, and expected cost.'),
('Safeguards', 'Safeguards', 'How to handle client data, where a person must review the work, and what can go wrong. These come before the steps.'),
('Steps', 'The steps', 'With the actual output the tools produced.'),
('Evidence', 'Evidence', 'What the result looked like when we ran it.'),
('Try it', 'Try it', 'A short exercise of about 15 minutes, using sample data.'),
('Record', 'Costs, author, and sources', '')]
rows = ''
for i, (k, t, d) in enumerate(RECIPE, 1):
first = ' first' if i == 1 else ''; last = ' last' if i == len(RECIPE) else ''
bars = '<i style="width:88%"></i><i style="width:64%"></i>'
if k == 'Steps': bars = '<i style="width:80%"></i><span class="term"></span>'
if k == 'Try it': bars = '<i style="width:72%"></i><span class="pill"></span>'
if k == 'Safeguards': bars = '<span class="gd"></span><span class="gd"></span>'
rows += (f'<div class="pc{first}{last}"><span class="k">{k}</span><span class="bars">{bars}</span><b class="n">{i}</b></div>'
f'<div class="ln"></div><div class="co"><h4>{t}</h4>' + (f'<p>{d}</p>' if d else '') + '</div>')
page('04b-workflow-recipe-diagram', f'''<div class="canvas" id="shot">
<p class="spec">Every workflow follows the same recipe</p>
<div class="rg">
<div class="ptop"><span>Workflow page</span><em>~15 min</em></div><div></div><div></div>
{rows}
</div></div>''', css='''
.rg{display:grid;grid-template-columns:250px 34px 1fr;margin-top:16px;align-items:stretch}
.ptop{background:linear-gradient(#fff,var(--paper));border:1px solid var(--line);border-bottom:1px solid var(--line);border-radius:16px 16px 0 0;padding:12px 16px;display:flex;justify-content:space-between;align-items:center}
.ptop span{font-family:var(--mono);font-size:12px;color:var(--muted)}
.ptop em{font-style:normal;font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--pine);background:var(--pine-wash);border:1px solid var(--pine-tint);padding:3px 9px;border-radius:999px}
.pc{background:#fff;border-left:1px solid var(--line);border-right:1px solid var(--line);padding:11px 30px 11px 16px;position:relative;display:flex;flex-direction:column;justify-content:center;gap:7px;border-top:1px dashed var(--line-2)}
.pc.first{border-top:none;padding-top:16px}
.pc.last{border-bottom:1px solid var(--line);border-radius:0 0 16px 16px;padding-bottom:16px}
.pc .k{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--pine)}
.bars{display:flex;flex-direction:column;gap:5px}
.bars i{display:block;height:6px;border-radius:3px;background:#ece6db}
.bars .term{display:block;height:20px;border-radius:5px;background:#14110e}
.bars .pill{display:block;width:70px;height:14px;border-radius:999px;background:rgba(180,121,31,.14);border:1px solid rgba(180,121,31,.3)}
.bars .gd{display:block;height:6px;border-radius:3px;background:linear-gradient(90deg,var(--pine) 0 8px,transparent 8px 14px,#ece6db 14px);width:86%}
.pc .n{position:absolute;right:-13px;top:50%;transform:translateY(-50%);width:26px;height:26px;border-radius:999px;background:var(--pine);color:#fff;font-family:var(--mono);font-size:13px;font-weight:600;display:grid;place-items:center;box-shadow:0 0 0 3px var(--paper-2);z-index:2}
.ln{position:relative}.ln::before{content:"";position:absolute;left:13px;right:4px;top:50%;border-top:1.5px solid var(--pine);opacity:.5}
.co{padding:8px 0 8px 4px;display:flex;flex-direction:column;justify-content:center}
.co h4{font-weight:700;font-size:16px;line-height:1.25}
.co p{font-size:14px;line-height:1.42;color:var(--muted);margin-top:2px}
''')
CHAPTERS = ['Real Estate', 'Financial Advisors', 'Fractional <span style="white-space:nowrap">C-Suite</span>', 'Higher Education', 'Communities', 'Technology']
page('04c-chapter-tiles-DRAFT', '<div class="canvas" id="shot"><p class="spec">Chapters</p><div class="tg">' + ''.join(
f'<div class="card t"><span class="n">{i:02d}</span><h3>{c}</h3>{icon("book")}</div>' for i, c in enumerate(CHAPTERS, 1)) + '</div></div>', css='''
.tg{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:14px}
.t{padding:18px 18px 20px;position:relative;min-height:118px;display:flex;flex-direction:column}
.t .n{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.08em}
.t h3{font-family:var(--display);font-weight:600;font-size:22px;letter-spacing:-.02em;line-height:1.12;margin-top:auto;padding-top:22px}
.t .ico{position:absolute;right:16px;top:16px;width:22px;height:22px;opacity:.8}
''')
# ---------------------------------------------------------------- 05 try one workflow
add_header('05')
CHECK = ['Use the included sample_listings.csv (no live client file).',
'Produce the market-1pager brief.',
'Walk the human checklist.',
'Mark complete when you have a brief you would send after review.']
page('05b-sample-workflow-try-it', f'''<div class="canvas" id="shot">
<div class="win">
<div class="bar"><i></i><i></i><i></i><span class="url">aibook.fun</span></div>
<div class="scr">
<article class="recipe">
<div class="recipe-top"><span class="recipe-id"><b>RE-01</b> · v1.2 · ~15 min · ≈ $0.03</span><span class="recipe-vert">Real Estate</span></div>
<div class="recipe-body">
<h3 class="recipe-title">Draft a property-market brief with an AI agent without leaking client data.</h3>
<div class="xrow"><h4 class="k">Safeguards</h4><div class="v">
<div class="guard">{ICONS["shield"]}<span>Redaction runs on your machine. Raw client data never reaches the model.</span></div>
<div class="guard">{ICONS["shield"]}<span>A human checklist is required before the brief is shared.</span></div>
</div></div>
<div class="xrow try"><h4 class="k">Try-it</h4><div class="v">
<p class="mins">About 15 minutes</p>
<ul class="cl">{''.join(f'<li><span class="box"></span>{c}</li>' for c in CHECK)}</ul>
<div class="acts"><span class="chip">◷ cost to try ≈ $0.03</span><span class="btn btn-ghost">Mark complete</span></div>
</div></div>
</div>
</article>
</div>
</div>
<p class="foot-note">Sample workflow RE-01, shown in the AiBook.fun reader.</p>
</div>''', extra_css='<link rel="stylesheet" href="../reader/globals.css">', css='''
body{background:#fff}
.win{background:#fff;border:1px solid var(--line-2);border-radius:14px;overflow:hidden;box-shadow:var(--sh-lg)}
.bar{display:flex;align-items:center;gap:7px;padding:10px 14px;background:#f1eee7;border-bottom:1px solid var(--line)}
.bar i{width:11px;height:11px;border-radius:999px;background:#d8d1c4;display:block}
.bar .url{margin-left:14px;flex:1;background:#fff;border:1px solid var(--line);border-radius:999px;padding:4px 14px;font-family:var(--mono);font-size:13px;color:var(--muted)}
.scr{padding:20px;background:var(--paper)}
.recipe{box-shadow:var(--sh-md);border-radius:16px}
.recipe-top{padding:14px 18px}
.recipe-id{font-size:13px}
.recipe-vert{font-size:12px}
.recipe-body{padding:22px 20px 22px}
.recipe-title{font-size:23px;margin-bottom:16px}
.xrow{display:grid;grid-template-columns:96px 1fr;gap:14px;padding:14px 0;border-top:1px dashed var(--line-2)}
.xrow .k{font-family:var(--mono);font-size:12.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--pine);padding-top:3px;font-weight:500}
.guard{font-size:15px;padding:5px 0;color:var(--muted);gap:9px;line-height:1.45}
.guard svg{width:18px;height:18px;color:var(--pine);flex:none;margin-top:1px}
.mins{font-weight:700;font-size:16px;margin-bottom:8px}
.cl li{display:flex;gap:11px;align-items:flex-start;font-size:15px;line-height:1.45;padding:5px 0}
.cl .box{width:18px;height:18px;border:1.6px solid var(--pine);border-radius:5px;flex:none;margin-top:2px;background:#fff}
.acts{display:flex;align-items:center;gap:12px;margin-top:12px;flex-wrap:wrap}
.chip{font-size:13px}
.btn{font-size:15px;padding:10px 18px}
.foot-note{text-align:center}
''')
page('05c-button-try-the-workflow', f'''<div class="canvas" id="shot">
<div class="cta"><span class="b">Try the sample workflow {ICONS["arrow"]}</span>
<p>Free · about 15 minutes · sample data · aibook.fun</p></div></div>''', css='''
.canvas{padding:26px 32px 24px}
.cta{text-align:center}
.b{display:inline-flex;align-items:center;gap:12px;background:var(--pine);color:#f7fbf9;font-weight:700;font-size:24px;letter-spacing:-.01em;border-radius:999px;padding:18px 34px;box-shadow:0 10px 24px -10px rgba(28,93,76,.6)}
.b svg{width:24px;height:24px}
.cta p{font-family:var(--mono);font-size:14px;color:var(--muted);letter-spacing:.03em;margin-top:14px}
''')
# ---------------------------------------------------------------- 06 three ways to back
add_header('06')
def three_ways(paperback=True):
cloud_items = [('$19', 'Early Bird eBook', 'First 200 backers', False),
('$25', 'eBook and backer-only updates', '', False),
('$47', 'eBook and 12 months of the AiBook.fun library', '', True)]
rows = ''.join(f'<li class="{"feat" if f else ""}"><b>{p}</b><span>{t}' + (f'<small>{s}</small>' if s else '') + '</span></li>'
for p, t, s, f in cloud_items)
if paperback:
rows += f'<li><b class="np">{ICONS["book"]}</b><span>Signed paperback and eBook<small>Price on the reward</small></span></li>'
cloud_deliv = ('<li>Library access: within about 14 days of a successful campaign</li><li>eBook: January 2027</li>'
+ ('<li>Signed paperback: February 2027</li>' if paperback else ''))
inc = f'<ul class="inc"><li>{icon("seat")}Your seat</li><li>{icon("book")}The eBook</li><li>{icon("library")}12 months of AiBook.fun</li></ul>'
return f'''<div class="canvas" id="shot"><div class="cols">
<div class="col card">
<p class="nm">Retreat</p><h3>Burned Out Advisor Retreat</h3>
<div class="sec"><p class="lb">What it is</p><p>Three days in person with the authors and other advisors.</p>
<p class="fact">{icon("people")}369 seats</p></div>
<div class="sec"><p class="lb">Includes</p>{inc}</div>
<div class="sec"><p class="lb">When</p><p class="fact">{icon("calendar")}4–6 November 2026</p></div>
<div class="pr"><p class="price">Price on the reward</p></div>
</div>
<div class="col card">
<p class="nm">Academy</p><h3>AiCoach + AOP.biz BootCamp</h3>
<div class="sec"><p class="lb">What it is</p><p>A seat in the next cohort at one of three sites:</p>
<p class="fact">{icon("pin")}Orlando, Florida</p><p class="fact">{icon("pin")}North Denver, Colorado</p><p class="fact">{icon("pin")}Villa Park, Chicagoland, Illinois</p></div>
<div class="sec"><p class="lb">Includes</p>{inc}</div>
<div class="sec"><p class="lb">When</p><p class="fact">{icon("calendar")}The next cohort at your chosen site</p></div>
<div class="pr"><p class="price">Price on the reward</p></div>
</div>
<div class="col card cloud">
<p class="nm">Cloud</p><h3>Do it yourself, online</h3>
<div class="sec span2"><p class="lb">Choose one</p><ul class="prices">{rows}</ul></div>
<div class="sec"><p class="lb">When</p><ul class="dl">{cloud_deliv}</ul></div>
<div class="pr"></div>
</div>
</div></div>'''
TW_CSS = '''
.cols{display:grid;grid-template-columns:1fr 1fr 1.3fr;grid-template-rows:repeat(6,auto);column-gap:10px}
.col{padding:18px 15px 16px;display:grid;grid-template-rows:subgrid;grid-row:span 6}
.nm{font-family:var(--mono);font-size:13px;letter-spacing:.12em;text-transform:uppercase;color:var(--pine);font-weight:600}
.col h3{font-family:var(--display);font-weight:600;font-size:19px;letter-spacing:-.02em;line-height:1.15;margin:6px 0 10px}
.sec{border-top:1px dashed var(--line-2);padding:10px 0 12px}
.span2{grid-row:span 2}
.lb{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.sec p{font-size:14px;line-height:1.4}
.fact{display:flex;gap:7px;align-items:flex-start;margin-top:6px;font-weight:600}
.lb+.fact{margin-top:0}
.fact .ico,.inc .ico{width:17px;height:17px;margin-top:1px}
.inc li{display:flex;gap:7px;align-items:flex-start;font-size:14px;line-height:1.35;padding:2px 0}
.pr{align-self:end}
.price{font-family:var(--mono);font-size:12.5px;font-weight:600;color:var(--pine);background:var(--pine-wash);border:1px solid var(--pine-tint);border-radius:999px;padding:7px 6px;text-align:center;white-space:nowrap}
.cloud{border-color:rgba(28,93,76,.28)}
.prices li{display:grid;grid-template-columns:58px 1fr;gap:8px;align-items:baseline;padding:7px 8px;border-radius:10px;font-size:14px;line-height:1.35}
.prices li+li{margin-top:4px}
.prices b{font-family:var(--display);font-weight:900;font-size:24px;letter-spacing:-.03em;color:var(--ink)}
.prices b.np{align-self:start;color:var(--muted);width:22px;height:22px;margin:1px 0 0 4px}
.prices small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff}
.dl li{font-size:14px;line-height:1.4;padding:2px 0 2px 14px;position:relative}
.dl li::before{content:"";position:absolute;left:0;top:8px;width:6px;height:6px;border-radius:999px;background:var(--pine)}
'''
page('06b-three-ways-to-back', three_ways(True), css=TW_CSS)
page('06c-three-ways-to-back-no-paperback', three_ways(False), css=TW_CSS)
# ---------------------------------------------------------------- 07 retreat
add_header('07')
page('07b-retreat-venue', f'''<div class="canvas" id="shot">
{slot("retreat-venue", "Venue photo goes here", "Landscape · at least 1360 × 765 px", "venue")}
<div class="ov"><p class="spec">{TITLE.split(" — ")[0]}</p><h3>Burned Out Advisor Retreat</h3><p class="d">{icon("calendar")}4 to 6 November 2026</p></div>
<div class="seats"><b>369</b><span>seats</span></div>
</div>''', h=383, px_h=765, css='''
.canvas{padding:0;height:383px}
.venue{position:absolute;inset:0}
.venue .ph{border-radius:0;border-width:0;justify-content:flex-start;padding-top:70px}
.ov{position:absolute;left:28px;bottom:26px;background:rgba(252,251,248,.96);border-radius:16px;padding:16px 22px 18px;box-shadow:var(--sh-lg);max-width:420px}
.ov h3{font-family:var(--display);font-weight:900;font-size:30px;letter-spacing:-.025em;line-height:1.05;margin:6px 0 10px}
.ov .d{display:flex;align-items:center;gap:9px;font-weight:700;font-size:18px}
.ov .d .ico{width:22px;height:22px}
.seats{position:absolute;right:28px;top:26px;width:108px;height:108px;border-radius:999px;background:var(--pine);color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;box-shadow:0 0 0 5px rgba(252,251,248,.9),var(--sh-lg)}
.seats b{font-family:var(--display);font-weight:900;font-size:38px;line-height:1;letter-spacing:-.03em}
.seats span{font-family:var(--mono);font-size:14px;letter-spacing:.12em;text-transform:uppercase;color:#cfe0d6;margin-top:3px}
''')
# ---------------------------------------------------------------- 08 academy
add_header('08')
W, H = MAP['w'], MAP['h']
pins = MAP['pins']
def pct(k): return pins[k][0] / W * 100, pins[k][1] / H * 100
SITES = [('villapark', 'Chicagoland, Illinois', 'Villa Park', 'lab-r'),
('denver', 'North Denver, Colorado', '#NoDe', 'lab-l'),
('orlando', 'Orlando, Florida', 'Azur Resorts', 'lab-l')]
pin_html = ''
for k, city, sub, cls in SITES:
x, y = pct(k)
pin_html += (f'<div class="pin" style="left:{x:.2f}%;top:{y:.2f}%">{ICONS["pin"]}'
f'<div class="lab {cls}"><b>{sub}</b><span>{city}</span></div></div>')
page('08b-academy-map', f'''<div class="canvas" id="shot">
<p class="spec" style="text-transform:none;letter-spacing:.04em;font-size:14px">AiCoach + AOP.biz BootCamp · three sites</p>
<div class="map">{MAP_SVG}{pin_html}</div>
</div>''', css=f'''
.map{{position:relative;margin-top:14px;aspect-ratio:{W}/{H}}}
.map>svg{{position:absolute;inset:0;width:100%;height:100%}}
.pin{{position:absolute;width:0;height:0}}
.pin>svg{{position:absolute;width:40px;height:40px;left:-20px;top:-36px;color:#fff;fill:var(--pine);stroke:#fff;stroke-width:1.6;filter:drop-shadow(0 3px 4px rgba(0,0,0,.25))}}
.lab{{position:absolute;background:#fff;border:1px solid var(--line);border-radius:12px;padding:8px 13px 9px;box-shadow:var(--sh-md);white-space:nowrap}}
.lab b{{display:block;font-family:var(--display);font-weight:600;font-size:18px;letter-spacing:-.02em;line-height:1.1}}
.lab span{{display:block;font-size:14px;color:var(--muted);margin-top:2px}}
.lab-r{{left:26px;top:-46px}}
.lab-l{{right:26px;top:-46px}}
''')
SITE_CARDS = [('site-orlando', 'Azur Resorts', 'Orlando, Florida'), ('site-north-denver', '#NoDe', 'North Denver, Colorado'),
('site-villa-park', 'Villa Park', 'Chicagoland, Illinois')]
page('08c-academy-sites', '<div class="canvas" id="shot"><div class="sg">' + ''.join(
f'<div class="card s">{slot(src, "Site photo goes here", "At least 800 × 600 px", "sp")}<div class="b"><p class="n">Site {i}</p><h3>{name}</h3><p class="pl">{icon("pin")}{place}</p></div></div>'
for i, (src, name, place) in enumerate(SITE_CARDS, 1)) + '</div></div>', css='''
.sg{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.s{overflow:hidden}
.sp{aspect-ratio:4/3}
.sp .ph{border-radius:0;border-width:0 0 2px 0}
.sp .ph b{font-size:14px}.sp .ph small{font-size:11.5px}
.b{padding:14px 15px 16px}
.n{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.b h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;margin:4px 0 6px}
.pl{display:flex;gap:6px;align-items:flex-start;font-size:14px;line-height:1.35;color:var(--ink)}
.pl .ico{width:17px;height:17px;margin-top:1px}
''')
# ---------------------------------------------------------------- 09 cloud and aibook.fun (device mockup)
add_header('09')
L, Ph = SCREENS['laptop'], SCREENS['phone']
def tgt(box, vw, vh, name, side='left', gap=16):
"""Callout target just outside the text box (left or right side), vertically centred."""
x, y, w, h = box
cx = x - gap if side == 'left' else x + w + gap
return f'<i class="tg" data-t="{name}" style="left:{cx / vw * 100:.2f}%;top:{(y + h / 2) / vh * 100:.2f}%"></i>'
page('09b-aibook-fun-device-mockup', f'''<div class="canvas" id="shot">
<div class="stage">
<div class="laptop"><div class="lid"><div class="scr"><img src="../assets-gen/screen-laptop.png">
{tgt(L["essay"], L["vw"], L["vh"], "essay", gap=22)}{tgt(L["workflow"], L["vw"], L["vh"], "workflow", gap=22)}</div></div><div class="base"></div></div>
<div class="phone"><div class="scr"><img src="../assets-gen/screen-phone.png">
{tgt(Ph["tryit"], Ph["vw"], Ph["vh"], "tryit", "right", 22)}{tgt(Ph["updated"], Ph["vw"], Ph["vh"], "updated", "right", 22)}</div></div>
<span class="co" data-c="essay" style="left:0;top:0">Chapter essay</span>
<span class="co" data-c="workflow" style="left:0;bottom:0">Workflow</span>
<span class="co" data-c="tryit" style="right:0;top:0">Try-it (about 15 minutes)</span>
<span class="co" data-c="updated" style="right:0;bottom:0">Last updated</span>
<svg class="lines"></svg>
</div>
<p class="foot-note">Mockup of the AiBook.fun reader, showing the sample Real Estate chapter.</p>
</div>
<script>
window.afterLayout = async () => {{
await Promise.all([...document.images].map(i => i.complete ? 1 : new Promise(r => i.onload = r)));
const st = document.querySelector('.stage').getBoundingClientRect();
const svg = document.querySelector('.lines');
let s = '';
for (const c of document.querySelectorAll('.co')) {{
const t = document.querySelector('.tg[data-t="' + c.dataset.c + '"]').getBoundingClientRect();
const r = c.getBoundingClientRect();
const tx = t.left - st.left, ty = t.top - st.top;
const top = r.top - st.top < st.height / 2;
const cx = Math.min(Math.max(tx, r.left - st.left + 16), r.right - st.left - 16);
const cy = top ? r.bottom - st.top : r.top - st.top;
s += `<path d="M${{cx}} ${{cy}} L${{cx}} ${{ty}} L${{tx}} ${{ty}}" />`;
s += `<circle cx="${{tx}}" cy="${{ty}}" r="4.5" class="dot"/><circle cx="${{tx}}" cy="${{ty}}" r="8" class="halo"/>`;
}}
svg.innerHTML = s;
}};
</script>''', css='''
.canvas{padding:24px 26px 22px}
.stage{position:relative;height:500px}
.laptop{position:absolute;left:18px;top:56px;width:470px}
.lid{background:#1f1b17;border-radius:14px 14px 4px 4px;padding:11px 11px 13px;box-shadow:var(--sh-lg)}
.laptop .scr{position:relative;aspect-ratio:1120/700;overflow:hidden;border-radius:3px;background:#fff}
.base{height:14px;margin:0 -30px;background:linear-gradient(#d9d4cb,#bdb6aa);border-radius:0 0 14px 14px;position:relative;box-shadow:0 14px 22px -12px rgba(0,0,0,.35)}
.base::before{content:"";position:absolute;left:50%;top:0;width:80px;height:6px;transform:translateX(-50%);background:#a9a295;border-radius:0 0 8px 8px}
.phone{position:absolute;right:18px;top:62px;width:176px;background:#1f1b17;border-radius:30px;padding:8px;box-shadow:var(--sh-lg),0 0 0 1px #3a342b inset}
.phone .scr{position:relative;aspect-ratio:390/844;overflow:hidden;border-radius:23px;background:#fff}
.scr img{position:absolute;inset:0;width:100%;height:100%;display:block}
.tg{position:absolute;width:0;height:0}
.co{position:absolute;z-index:3;background:#fff;border:1.5px solid var(--pine);color:var(--pine-deep);font-weight:700;font-size:16px;padding:8px 16px;border-radius:999px;box-shadow:var(--sh-md);white-space:nowrap}
.lines{position:absolute;inset:0;width:100%;height:100%;z-index:2;overflow:visible}
.lines path{fill:none;stroke:var(--pine);stroke-width:1.8}
.lines .dot{fill:var(--pine);stroke:#fff;stroke-width:2}
.lines .halo{fill:none;stroke:var(--pine);stroke-width:1.5;opacity:.35}
.foot-note{text-align:center;margin-top:14px}
''')
# ---------------------------------------------------------------- 10 stretch goals (1360 x 500)
add_header('10')
GOALS = [(15000, '$15,000', 'Bonus chapter', 'PDF for every backer whose reward includes the eBook'),
(35000, '$35,000', 'Audiobook sample', 'One chapter recorded as audio, for every backer'),
(54000, '$54,000', 'Live Q&A replay pack', 'Recording of a live Q&A with the authors, for every backer')]
GX = [16, 50, 84] # schematic milestone positions (%), evenly spaced so labels never collide
def stretch(unlocked):
fill = 0 if unlocked == 0 else GX[unlocked - 1]
marks = ''
for i, ((v, amt, name, desc), x) in enumerate(zip(GOALS, GX), 1):
on = i <= unlocked
marks += (f'<div class="m {"on" if on else ""}" style="left:{x}%">'
f'<span class="lk">{ICONS["unlock" if on else "lock"]}</span>'
f'<div class="lbl"><b>{amt}' + ('<span class="ul">Unlocked</span>' if on else '') + '</b>'
f'<span class="nm">{name}</span><span class="ds">{desc}</span></div></div>')
status = 'Not yet unlocked' if unlocked == 0 else ('Unlocked: ' + ', '.join(g[1] for g in GOALS[:unlocked]))
return f'''<div class="canvas" id="shot">
<div class="top"><p class="spec">Stretch goals</p><p class="st">{status}</p></div>
<div class="track"><div class="fill" style="width:{fill}%"></div>
<div class="goal"><span class="gf">{ICONS["flag"]}</span><div class="gl"><b>Goal $5,000</b></div></div>
{marks}
</div></div>'''
ST_CSS = '''
.canvas{height:250px;padding:24px 32px 18px}
.top{display:flex;justify-content:space-between;align-items:baseline}
.st{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.02em}
.track{position:relative;height:14px;border-radius:999px;background:#e6e0d4;margin:58px 0 0;box-shadow:inset 0 1px 2px rgba(0,0,0,.08)}
.fill{position:absolute;left:0;top:0;bottom:0;border-radius:999px;background:linear-gradient(90deg,#23745f,var(--pine))}
.m{position:absolute;top:50%;width:0;height:0}
.lk{position:absolute;left:-20px;top:-20px;width:40px;height:40px;border-radius:999px;background:#fff;border:2px solid var(--line-2);color:var(--muted);display:grid;place-items:center;box-shadow:var(--sh-sm)}
.lk svg{width:21px;height:21px}
.m.on .lk{background:var(--pine);border-color:#fff;color:#fff;box-shadow:0 0 0 2px var(--pine),var(--sh-md)}
.m.on .lbl b{color:var(--pine-deep)}
.ul{display:inline-block;vertical-align:4px;margin-left:8px;font-family:var(--mono);font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--pine);padding:3px 10px;border-radius:999px;white-space:nowrap}
.lbl{position:absolute;top:30px;width:196px;left:-98px;text-align:center}
.lbl b{display:block;font-family:var(--display);font-weight:900;font-size:23px;letter-spacing:-.03em;line-height:1.1}
.lbl .nm{display:block;font-weight:700;font-size:15.5px;margin-top:2px}
.lbl .ds{display:block;font-size:13.5px;line-height:1.35;color:var(--muted);margin-top:3px}
.goal{position:absolute;left:0;top:50%;width:0;height:0}
.goal::before{content:"";position:absolute;left:0;top:-7px;width:14px;height:14px;border-radius:999px;background:#fff;border:3px solid var(--amber)}
.gf{position:absolute;left:3px;top:-44px;width:24px;height:24px;color:var(--amber)}
.gf svg{width:24px;height:24px}
.gl{position:absolute;left:28px;top:-42px;white-space:nowrap}
.gl b{font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.04em;color:var(--amber)}
'''
for n, nm in [(0, '10b-stretch-goals-locked'), (1, '10c-stretch-goals-15k-unlocked'), (2, '10d-stretch-goals-35k-unlocked'), (3, '10e-stretch-goals-54k-unlocked')]:
page(nm, stretch(n), css=ST_CSS, h=250)
# ---------------------------------------------------------------- 11 timeline
add_header('11')
STOPS = [('Fri 2 Oct 2026', 'The campaign opens.'),
('Wed 4 Nov 2026', 'The campaign ends. The Burned Out Advisor Retreat runs 4 to 6 November.'),
('By mid-November 2026', 'AiBook.fun access for backers whose reward includes it, and the backer survey.'),
('Dates to be announced', 'Academy BootCamp cohorts in Orlando, North Denver, and Villa Park.'),
('January 2027', 'Final layout and eBook files. The eBook is delivered and the paperbacks are signed.'),
('February 2027', 'Signed paperbacks are delivered.')]
XS = [96, 290, 484]
Y1, Y2 = 30, 250
pos = [(XS[0], Y1), (XS[1], Y1), (XS[2], Y1), (XS[2], Y2), (XS[1], Y2), (XS[0], Y2)]
stops = ''.join(f'<div class="stop{" tba" if i == 4 else ""}" style="left:{x}px;top:{y}px"><span class="num">{i}</span><div class="tx"><b>{d}</b><p>{t}</p></div></div>'
for i, ((x, y), (d, t)) in enumerate(zip(pos, STOPS), 1))
page('11b-timeline', f'''<div class="canvas" id="shot"><div class="tl">
<svg class="path" viewBox="0 0 616 420"><path d="M{XS[0]} {Y1} H{XS[2]} C{XS[2]+160} {Y1} {XS[2]+160} {Y2} {XS[2]} {Y2} H{XS[0]}" /></svg>
{stops}</div></div>''', css=f'''
.tl{{position:relative;height:396px}}
.path{{position:absolute;inset:0;width:616px;height:420px}}
.path path{{fill:none;stroke:var(--pine);stroke-width:3;stroke-dasharray:2 9;stroke-linecap:round;opacity:.7}}
.stop{{position:absolute;width:0;height:0}}
.num{{position:absolute;left:-23px;top:-23px;width:46px;height:46px;border-radius:999px;background:var(--pine);color:#fff;font-family:var(--display);font-weight:900;font-size:22px;display:grid;place-items:center;box-shadow:0 0 0 5px var(--paper-2),var(--sh-md)}}
.stop.tba .num{{background:#fff;color:var(--pine);border:2.5px solid var(--pine)}}
.tx{{position:absolute;top:34px;left:-92px;width:184px;text-align:center}}
.tx b{{display:block;font-family:var(--display);font-weight:600;font-size:18px;letter-spacing:-.02em;line-height:1.15;color:var(--ink)}}
.tx p{{font-size:14px;line-height:1.4;color:var(--muted);margin-top:5px}}
.stop.tba .tx b{{color:var(--pine)}}
''')
# ---------------------------------------------------------------- 12 shipping and delivery
add_header('12')
def lane(title, sub, ic, steps):
st = ''
for j, (i2, t, d) in enumerate(steps):
if j: st += f'<span class="ar">{ICONS["arrow"]}</span>'
st += f'<div class="stp">{icon(i2, "ico ci")}<b>{t}</b>' + (f'<p>{d}</p>' if d else '') + '</div>'
return f'<div class="card lane"><div class="lh">{icon(ic, "ico lhi")}<div><h3>{title}</h3><p>{sub}</p></div></div><div class="steps n{len(steps)}">{st}</div></div>'
page('12b-shipping-and-delivery-lanes', '<div class="canvas" id="shot">' +
lane('Digital rewards', 'Arrive by email', 'mail', [
('mail', 'Email', 'To the address on your Kickstarter account'),
('library', 'Library access', 'Within about 14 days of a successful campaign'),
('book', 'eBook', 'January 2027')]) +
lane('Signed paperbacks', 'Ship in February 2027', 'box', [
('survey', 'Survey', 'Kickstarter’s Pledge Manager asks for your address and charges shipping and any taxes'),
('printer', 'Printing', ''),
('pen', 'Signing', 'January 2027'),
('truck', 'Shipping', 'February 2027')]) +
'</div>', css='''
.lane{padding:16px 18px 18px}
.lane+.lane{margin-top:14px}
.lh{display:flex;align-items:center;gap:12px;padding-bottom:12px;border-bottom:1px dashed var(--line-2);margin-bottom:14px}
.ico.lhi{width:40px;height:40px;padding:9px;border-radius:12px;background:var(--pine);color:#fff}
.lh h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.1}
.lh p{font-family:var(--mono);font-size:13px;color:var(--muted);margin-top:3px}
.steps{display:flex;align-items:flex-start}
.stp{flex:1;min-width:0;text-align:center;padding:0 4px}
.ar{flex:none;width:18px;height:18px;color:var(--line-2);margin-top:14px}
.ico.ci{width:46px;height:46px;padding:11px;border-radius:999px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:block;margin:0 auto 8px}
.stp b{display:block;font-size:15.5px;font-weight:700}
.stp p{font-size:13.5px;line-height:1.38;color:var(--muted);margin-top:3px}
''')
# ---------------------------------------------------------------- 13 early readers (placeholder cards)
add_header('13')
# Fill in once early readers approve their quotes in writing (photo files: assets/reader-1.jpg etc.).
QUOTES = [ # (quote, name, role and firm)
('Early reader quote 1 goes here.', 'Name goes here', 'Role and firm'),
('Early reader quote 2 goes here.', 'Name goes here', 'Role and firm'),
('Early reader quote 3 goes here.', 'Name goes here', 'Role and firm'),
]
page('13b-early-reader-quotes', '<div class="canvas" id="shot">' + ''.join(
f'<div class="card q">{slot(f"reader-{i}", "Photo", "400 × 400", "qp")}<div class="qt"><p class="qq">“{q}”</p>'
f'<p class="who"><b>{n}</b> · {r}</p></div></div>' for i, (q, n, r) in enumerate(QUOTES, 1)) + '</div>', css='''
.q{display:flex;gap:18px;align-items:center;padding:18px 20px}
.q+.q{margin-top:12px}
.qp{width:84px;height:84px;border-radius:999px;flex:none}
.qp .ph{border-width:2px;gap:2px}.qp .ph svg{width:20px;height:20px}.qp .ph b{font-size:12px}.qp .ph small{font-size:10px}
.qq{font-family:var(--display);font-style:italic;font-size:21px;letter-spacing:-.01em;color:#8d8579;line-height:1.3}
.who{font-size:14.5px;color:var(--muted);margin-top:6px}
.who b{color:var(--ink)}
''')
# ---------------------------------------------------------------- 14 authors and team (placeholders)
add_header('14')
TEAM = [('@THY', 'Name goes here', 'Author and Retreat host'),
('@LFG', 'Name goes here', 'Creative lead for design, website, campaign page, and video'),
('@CEO', 'Name goes here', 'Business strategy and partnerships'),
('@GTM', 'Name goes here', 'Payments, email, and technical setup'),
('', 'Osinachi Patrick', 'Product lead, AiBook.fun'),
('', 'Name goes here', 'Academy BootCamp lead')]
team_html = ''.join(
f'<div class="card tm">{slot("team-" + (h[1:].lower() if h else ("osinachi-patrick" if n.startswith("Osinachi") else "academy-lead")), "Photo", "800 × 800", "tp")}'
f'<div><p class="tn">{n}</p>' + (f'<p class="th">{h}</p>' if h else '') + f'<p class="tr">{r}</p></div></div>' for h, n, r in TEAM)
page('14b-authors-and-team', f'''<div class="canvas" id="shot">
<p class="spec">The authors</p>
<div class="ag">
<div class="card au">{slot("author-1", "Author photo goes here", "800 × 800", "ap")}<p class="an">Author name</p><p class="ar">Role in the book</p></div>
<div class="card au">{slot("author-2", "Author photo goes here", "800 × 800", "ap")}<p class="an">Author name</p><p class="ar">Role in the book</p></div>
</div>
<p class="spec" style="margin-top:22px">The team</p>
<div class="tg">{team_html}</div>
</div>''', css='''
.ag{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
.au{padding:14px 14px 18px}
.ap{aspect-ratio:1/1;border-radius:10px}
.an{font-family:var(--display);font-weight:600;font-size:22px;letter-spacing:-.02em;margin-top:12px;color:#8d8579}
.ar{font-size:15px;color:var(--muted);margin-top:2px}
.tg{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}
.tm{padding:12px;display:flex;gap:11px;align-items:flex-start}
.tp{width:60px;height:60px;border-radius:999px;flex:none}
.tp .ph{gap:1px}.tp .ph svg{width:16px;height:16px}.tp .ph b{font-size:11px}.tp .ph small{display:none}
.tn{font-weight:700;font-size:14.5px;line-height:1.25}
.th{font-family:var(--mono);font-size:12px;color:var(--pine);margin-top:2px}
.tr{font-size:13px;line-height:1.35;color:var(--muted);margin-top:3px}
''')
page('14c-author-headshot-800x800', f'''<div class="canvas" id="shot">{slot("headshot-template", "Author photo goes here", "800 × 800 px · square", "hs")}</div>''',
w=400, h=400, css='''.canvas{width:400px;height:400px;padding:0}.hs{position:absolute;inset:0}.hs .ph{border-radius:0;border-width:3px}.hs .ph b{font-size:20px}.hs .ph small{font-size:14px}.hs .ph svg{width:44px;height:44px}''')
# ---------------------------------------------------------------- 15, 16
add_header('15')
add_header('16')
# ---------------------------------------------------------------- render
async def render(only=None):
from playwright.async_api import async_playwright
results = []
async with async_playwright() as p:
b = await p.chromium.launch(executable_path='/usr/bin/google-chrome')
for pg in pages:
if only and not re.search(only, pg['name']): continue
html = HEAD.format(body=pg['body'], css=pg['css'], extra_css=pg['extra_css'])
f = PAGES / f"{pg['name']}.html"; f.write_text(html)
page_ = await b.new_page(viewport={'width': pg['w'], 'height': math.ceil(pg['h'] or 800)}, device_scale_factor=2)
await page_.goto(f.as_uri())
empty = await page_.evaluate('window.__ready')
name = pg['name'] + ('-PLACEHOLDER' if empty else '')
for old in OUT.glob(pg['name'] + '*.png'): old.unlink()
el = await page_.query_selector('#shot')
if pg['h']: # fixed-size artwork: clip exactly (element screenshots round fractional heights up)
await page_.screenshot(path=str(OUT / f'{name}.png'), clip={'x': 0, 'y': 0, 'width': pg['w'], 'height': pg['h']})
else:
await el.screenshot(path=str(OUT / f'{name}.png'))
if pg.get('px_h'): # exact pixel height (e.g. 16:9 = 1360 x 765)
from PIL import Image
im = Image.open(OUT / f'{name}.png'); im.crop((0, 0, im.width, pg['px_h'])).save(OUT / f'{name}.png')
results.append((name, empty))
await page_.close()
await b.close()
for r in results: print(*r)
if __name__ == '__main__':
asyncio.run(render(sys.argv[1] if len(sys.argv) > 1 else None))
@@ -0,0 +1,30 @@
"""Contact sheet of all story images in order, with filenames and pixel sizes."""
import asyncio, pathlib
from PIL import Image
from playwright.async_api import async_playwright
HERE = pathlib.Path(__file__).parent; OUT = HERE.parent
files = sorted(p for p in OUT.glob('*.png') if p.name != 'contact-sheet.png')
cells = ''
for f in files:
w, h = Image.open(f).size
cells += f'<figure><div class="im"><img src="../../{f.name}"></div><figcaption><b>{f.name}</b><span>{w} × {h}</span></figcaption></figure>'
html = f'''<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../styles.css"><style>
body{{background:#fff}} .sheet{{width:1400px;padding:40px;background:#fcfbf8}}
h1{{font-family:var(--display);font-weight:900;font-size:34px;letter-spacing:-.02em}} .sub{{font-family:var(--mono);font-size:14px;color:var(--muted);margin:6px 0 28px}}
.grid{{display:grid;grid-template-columns:1fr 1fr;gap:28px 32px;align-items:start}}
figure{{margin:0}} .im{{border:1px solid var(--line-2);background:#fff}} img{{width:100%;display:block}}
figcaption{{display:flex;justify-content:space-between;font-family:var(--mono);font-size:14px;margin-top:8px}} figcaption span{{color:var(--muted)}}
</style></head><body><div class="sheet" id="shot"><h1>We Own AI — Kickstarter story images</h1>
<p class="sub">{len(files)} images in story order · ks-research/story-images/ · files marked PLACEHOLDER or DRAFT need owner input before upload</p>
<div class="grid">{cells}</div></div></body></html>'''
(HERE / 'pages' / 'contact-sheet.html').write_text(html)
async def main():
async with async_playwright() as p:
b = await p.chromium.launch(executable_path='/usr/bin/google-chrome')
pg = await b.new_page(viewport={'width': 1400, 'height': 1000}, device_scale_factor=1)
await pg.goto((HERE / 'pages' / 'contact-sheet.html').as_uri())
await pg.wait_for_load_state('networkidle'); await pg.evaluate('document.fonts.ready')
await (await pg.query_selector('#shot')).screenshot(path=str(OUT / 'contact-sheet.png'))
await b.close()
asyncio.run(main())
print(len(files), Image.open(OUT / 'contact-sheet.png').size)
+82
View File
@@ -0,0 +1,82 @@
"""Line icons (24x24, stroke = currentColor) and small line illustrations, drawn for this series."""
def _i(body, vb='0 0 24 24', sw=1.7):
return (f'<svg viewBox="{vb}" fill="none" stroke="currentColor" stroke-width="{sw}" '
f'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{body}</svg>')
ICONS = {
'authors': _i('<circle cx="8.5" cy="8" r="3"/><path d="M3 19c.6-3.2 2.8-5 5.5-5s4.9 1.8 5.5 5"/><circle cx="16.5" cy="9" r="2.5"/><path d="M15.4 14.2c.4-.1.7-.2 1.1-.2 2.3 0 4.1 1.5 4.6 4.2"/>'),
'tested': _i('<rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="m8.5 10 1.5 1.5 3-3"/><path d="M14.5 10.5H16"/><path d="m8.5 15.5 1.5 1.5 3-3"/><path d="M14.5 16H16"/>'),
'updated': _i('<path d="M4 6.5C6.5 5 9 5 12 6.5v12C9 17 6.5 17 4 18.5z"/><path d="M12 6.5c1-.5 2-.8 3-.9"/><path d="M20 12a4 4 0 1 1-1.2-2.9"/><path d="M19.2 6.6v2.6h-2.6"/>'),
'calendar': _i('<rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/><path d="M7.5 13h2M11 13h2M14.5 13h2M7.5 16.5h2M11 16.5h2"/>'),
'seat': _i('<path d="M6 11V6.5A2.5 2.5 0 0 1 8.5 4h7A2.5 2.5 0 0 1 18 6.5V11"/><path d="M4 11.5a2 2 0 0 1 4 0V14h8v-2.5a2 2 0 0 1 4 0V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/><path d="M6.5 18v2M17.5 18v2"/>'),
'people': _i('<circle cx="12" cy="8" r="3"/><path d="M6.5 19c.6-3 2.8-4.8 5.5-4.8s4.9 1.8 5.5 4.8"/><circle cx="5" cy="10" r="2"/><circle cx="19" cy="10" r="2"/><path d="M2 17c.3-1.7 1.4-2.8 3-3M22 17c-.3-1.7-1.4-2.8-3-3"/>'),
'pin': _i('<path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/>'),
'check': _i('<circle cx="12" cy="12" r="8.5"/><path d="m8.2 12.3 2.6 2.6 5-5.2"/>'),
'book': _i('<path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/>'),
'library': _i('<rect x="3.5" y="4.5" width="17" height="12" rx="1.6"/><path d="M2 19.5h20"/><path d="M8 8.5h8M8 11.5h5"/>'),
'mail': _i('<rect x="3" y="5.5" width="18" height="13" rx="2"/><path d="m3.8 7 8.2 6 8.2-6"/>'),
'survey': _i('<rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="M8.5 9.5h7M8.5 13h7M8.5 16.5h4"/>'),
'printer': _i('<path d="M7 9V4h10v5"/><rect x="3.5" y="9" width="17" height="8" rx="2"/><path d="M7 14h10v6H7z"/><path d="M17 12h.01"/>'),
'pen': _i('<path d="M4 20c2.5 0 3.5-2 5-2s2 1.5 3.5 1.5"/><path d="m14 4.5 5.5 5.5L11 18.5l-6 1.5 1.5-6z"/><path d="m12.5 6 5.5 5.5"/>'),
'truck': _i('<path d="M2.5 6.5h11v9h-11z"/><path d="M13.5 9.5h4l3 3.5v2.5h-7"/><circle cx="6.5" cy="17" r="1.8"/><circle cx="17" cy="17" r="1.8"/>'),
'globe': _i('<circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.5 2.3 3.6 5.2 3.6 8.5S14.5 18.2 12 20.5C9.5 18.2 8.4 15.3 8.4 12S9.5 5.8 12 3.5z"/>'),
'lock': _i('<rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/>'),
'unlock': _i('<rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/>'),
'flag': _i('<path d="M5 21V4"/><path d="M5 4.5h11l-2 3.5 2 3.5H5"/>'),
'audio': _i('<path d="M4 14v-2a8 8 0 0 1 16 0v2"/><rect x="3" y="13.5" width="4" height="6.5" rx="1.5"/><rect x="17" y="13.5" width="4" height="6.5" rx="1.5"/>'),
'video': _i('<rect x="3" y="6" width="13" height="12" rx="2"/><path d="m16 10.5 5-3v9l-5-3"/>'),
'camera': _i('<path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/>'),
'compass': _i('<circle cx="12" cy="12" r="8.5"/><path d="m15.5 8.5-2 5-5 2 2-5z"/>'),
'accounting': _i('<rect x="5" y="3.5" width="14" height="17" rx="2"/><path d="M8 7h8v3H8z"/><path d="M8.5 13.5h.01M12 13.5h.01M15.5 13.5h.01M8.5 17h.01M12 17h.01M15.5 17h.01"/>', sw=1.8),
'chart': _i('<path d="M4 20h16"/><path d="M6.5 16.5v-4M11 16.5V8.5M15.5 16.5v-6"/><path d="m5.5 9 5-4 4 3 4-3.5"/>'),
'briefcase': _i('<rect x="3" y="7.5" width="18" height="12" rx="2"/><path d="M9 7.5V6a1.5 1.5 0 0 1 1.5-1.5h3A1.5 1.5 0 0 1 15 6v1.5"/><path d="M3 12.5h18"/>'),
'arrow': _i('<path d="M5 12h14M13 6l6 6-6 6"/>'),
'clock': _i('<circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/>'),
'box': _i('<path d="M3.5 7.5 12 3.5l8.5 4v9L12 20.5l-8.5-4z"/><path d="M3.5 7.5 12 11.5l8.5-4M12 11.5v9"/>'),
}
# Book3D compass mark from marketing-site/components/Book3D.jsx
MARK = ('<svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/>'
'<path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg>')
P, A, L = '#1c5d4c', '#b4791f', '#d8d1c4'
def _ill(body):
return (f'<svg viewBox="0 0 120 84" fill="none" stroke="{P}" stroke-width="2" stroke-linecap="round" '
f'stroke-linejoin="round" aria-hidden="true">{body}</svg>')
ILLUS = {
# a pile of newsletters
'newsletters': _ill(
'<g transform="rotate(-8 60 50)"><rect x="26" y="34" width="62" height="40" rx="3" fill="#fff"/></g>'
'<g transform="rotate(5 60 46)"><rect x="30" y="26" width="62" height="40" rx="3" fill="#fff"/></g>'
'<rect x="28" y="14" width="62" height="44" rx="3" fill="#fff"/>'
f'<path d="M36 23h30" stroke="{A}" stroke-width="3"/><path d="M36 31h46M36 37h46M36 43h32M36 49h40" stroke="{L}"/>'
'<rect x="72" y="20" width="12" height="8" rx="1.5" stroke-width="1.6"/>'),
# a padlock on a software window
'lockedwindow': _ill(
'<rect x="18" y="12" width="84" height="60" rx="5" fill="#fff"/><path d="M18 24h84"/>'
f'<circle cx="26" cy="18" r="1.8" fill="{L}" stroke="none"/><circle cx="32" cy="18" r="1.8" fill="{L}" stroke="none"/><circle cx="38" cy="18" r="1.8" fill="{L}" stroke="none"/>'
f'<path d="M28 34h22M28 41h16M28 48h20M28 55h12" stroke="{L}"/>'
f'<rect x="62" y="42" width="26" height="21" rx="3" fill="#fff" stroke="{A}" stroke-width="2.4"/>'
f'<path d="M67 42v-5a8 8 0 0 1 16 0v5" stroke="{A}" stroke-width="2.4"/><path d="M75 50v5" stroke="{A}" stroke-width="2.4"/>'),
# a clock at 11 PM
'clock11': _ill(
'<circle cx="54" cy="44" r="30" fill="#fff"/>'
+ ''.join(f'<path d="M54 {17 if i%3==0 else 18.5}v{5 if i%3==0 else 3}" transform="rotate({i*30} 54 44)" stroke="{L if i%3 else P}"/>' for i in range(12)) +
'<path d="M54 44 44.5 28.5" stroke-width="3"/><path d="M54 44V21" stroke-width="2.2"/>'
f'<circle cx="54" cy="44" r="2.4" fill="{P}" stroke="none"/>'
f'<path d="M99 14a9 9 0 1 0 8 13 7 7 0 0 1-8-13z" fill="#fbf1e0" stroke="{A}"/>'),
# an empty client chair (armchair, front view)
'chair': _ill(
'<path d="M38 50V24a8 8 0 0 1 8-8h28a8 8 0 0 1 8 8v26" fill="#fff"/>'
'<path d="M30 44a6 6 0 0 1 12 0v8h36v-8a6 6 0 0 1 12 0v18a3 3 0 0 1-3 3H33a3 3 0 0 1-3-3z" fill="#fff"/>'
'<path d="M42 52h36"/><path d="M36 65v8M84 65v8"/>'
f'<path d="M48 30h24" stroke="{L}"/><path d="M14 73h92" stroke="{L}"/>'),
}
def icon(name, cls='ico'):
return f'<span class="{cls}">{ICONS[name]}</span>'
# ShieldCheck from marketing-site/components/reader/WorkflowBlock.jsx (16x16)
ICONS['shield'] = ('<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z" '
'stroke="#1C5D4C" stroke-width="1.3" stroke-linejoin="round"/><path d="m5.6 8 1.7 1.7L10.6 6" stroke="#1C5D4C" stroke-width="1.3" '
'stroke-linecap="round" stroke-linejoin="round"/></svg>')
+37
View File
@@ -0,0 +1,37 @@
"""Build a lower-48 US outline SVG (Albers equal-area) from public-domain Census-derived state boundaries.
Source: PublicaMundi/MappingAPI us-states.json (US Census cartographic boundaries). Output: assets-gen/us-map.svg"""
import json, math, sys
src = sys.argv[1] if len(sys.argv) > 1 else 'assets-gen/us-states.geojson'
d = json.load(open(src))
phi1, phi2, phi0, lam0 = map(math.radians, (29.5, 45.5, 37.5, -96))
n = (math.sin(phi1) + math.sin(phi2)) / 2
C = math.cos(phi1) ** 2 + 2 * n * math.sin(phi1)
rho0 = math.sqrt(C - 2 * n * math.sin(phi0)) / n
def proj(lon, lat):
lam, phi = math.radians(lon), math.radians(lat)
rho = math.sqrt(C - 2 * n * math.sin(phi)) / n
th = n * (lam - lam0)
return rho * math.sin(th), -(rho0 - rho * math.cos(th))
skip = {'Alaska', 'Hawaii', 'Puerto Rico'}
polys = []
for f in d['features']:
if f['properties']['name'] in skip: continue
g = f['geometry']
rings = g['coordinates'] if g['type'] == 'Polygon' else [r for p in g['coordinates'] for r in p]
for ring in rings:
polys.append([proj(*pt[:2]) for pt in ring])
xs = [p[0] for r in polys for p in r]; ys = [p[1] for r in polys for p in r]
minx, maxx, miny, maxy = min(xs), max(xs), min(ys), max(ys)
W = 1000; s = W / (maxx - minx); H = (maxy - miny) * s
def tx(p): return ((p[0] - minx) * s, (p[1] - miny) * s)
paths = []
for r in polys:
pts = [tx(p) for p in r]
paths.append('M' + 'L'.join(f'{x:.1f},{y:.1f}' for x, y in pts) + 'Z')
cities = {'orlando': (-81.3789, 28.5384), 'denver': (-104.9903, 39.7392), 'villapark': (-87.9887, 41.8898)}
out = {k: tx(proj(*v)) for k, v in cities.items()}
svg = f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H:.0f}"><path d="{"".join(paths)}" fill="#efebe2" stroke="#cdc3b3" stroke-width="1.2" stroke-linejoin="round"/></svg>'
import os; os.makedirs('assets-gen', exist_ok=True)
open('assets-gen/us-map.svg', 'w').write(svg)
json.dump({'w': W, 'h': round(H), 'pins': {k: [round(x, 1), round(y, 1)] for k, (x, y) in out.items()}}, open('assets-gen/us-map.json', 'w'), indent=1)
print(W, H, out, len(svg))
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">👋</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Welcome</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,54 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.wel{display:flex;gap:34px;align-items:center;padding:6px 6px 26px}
.bookwrap{flex:none;padding:6px 14px 14px 4px}
.book{width:184px;height:258px;border-radius:3px 8px 8px 3px;position:relative;box-shadow:10px 12px 0 -3px #efe9db,10px 12px 0 -2px #d6cdba,0 26px 40px -18px rgba(18,63,52,.5)}
.book .slot{position:absolute;inset:0;border-radius:3px 8px 8px 3px}
.book .slot .ph b{font-size:15px}
.wtxt h2{font-family:var(--display);font-weight:900;font-size:50px;letter-spacing:-.03em;line-height:1;color:var(--pine-deep)}
.wtxt .t{font-family:var(--display);font-weight:600;font-size:22px;letter-spacing:-.02em;line-height:1.2;margin:14px 0 12px}
.wtxt .s{font-size:16px;line-height:1.5;color:var(--muted);max-width:25rem}
.wtxt .pub{font-family:var(--mono);font-size:13px;letter-spacing:.06em;color:var(--pine);margin-top:16px}
.trio{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.trio>div{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:20px 14px 18px;text-align:center;box-shadow:var(--sh-sm)}
.ico.big{width:58px;height:58px;padding:13px;border-radius:999px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:block;margin:0 auto 12px}
.trio p{font-weight:700;font-size:16px;line-height:1.3}
</style></head><body>
<div class="canvas" id="shot">
<div class="wel">
<div class="bookwrap"><div class="book"><div class="slot cover" data-src="../assets/cover" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Book cover goes here</b><small>Final cover · owner supplies</small></div></div></div></div>
<div class="wtxt">
<h2>Welcome</h2>
<p class="t">We Own AI — The New American Dream</p>
<p class="s">A field book for advisors who want to use AI in their practice in ways they control.</p>
<p class="pub">BurnedOut Media / WeOwnNet</p>
</div>
</div>
<div class="trio">
<div><span class="ico big"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8.5" cy="8" r="3"/><path d="M3 19c.6-3.2 2.8-5 5.5-5s4.9 1.8 5.5 5"/><circle cx="16.5" cy="9" r="2.5"/><path d="M15.4 14.2c.4-.1.7-.2 1.1-.2 2.3 0 4.1 1.5 4.6 4.2"/></svg></span><p>Multi-author field book</p></div>
<div><span class="ico big"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="m8.5 10 1.5 1.5 3-3"/><path d="M14.5 10.5H16"/><path d="m8.5 15.5 1.5 1.5 3-3"/><path d="M14.5 16H16"/></svg></span><p>Tested AI workflows</p></div>
<div><span class="ico big"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6.5C6.5 5 9 5 12 6.5v12C9 17 6.5 17 4 18.5z"/><path d="M12 6.5c1-.5 2-.8 3-.9"/><path d="M20 12a4 4 0 1 1-1.2-2.9"/><path d="M19.2 6.6v2.6h-2.6"/></svg></span><p>Library updated often</p></div>
</div>
</div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">❤️</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Why this book</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,30 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.g{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.c{padding:20px 22px 22px}
.il{width:120px;height:84px;margin:0 0 12px -6px}
.c h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.15;margin-bottom:8px}
.c p{font-size:15px;line-height:1.5;color:var(--muted)}
</style></head><body><div class="canvas" id="shot"><div class="g"><div class="card c"><div class="il"><svg viewBox="0 0 120 84" fill="none" stroke="#1c5d4c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><g transform="rotate(-8 60 50)"><rect x="26" y="34" width="62" height="40" rx="3" fill="#fff"/></g><g transform="rotate(5 60 46)"><rect x="30" y="26" width="62" height="40" rx="3" fill="#fff"/></g><rect x="28" y="14" width="62" height="44" rx="3" fill="#fff"/><path d="M36 23h30" stroke="#b4791f" stroke-width="3"/><path d="M36 31h46M36 37h46M36 43h32M36 49h40" stroke="#d8d1c4"/><rect x="72" y="20" width="12" height="8" rx="1.5" stroke-width="1.6"/></svg></div><h3>Too much noise</h3><p>Each week brings a new tool and a new prediction, and very little of it fits a real client file.</p></div><div class="card c"><div class="il"><svg viewBox="0 0 120 84" fill="none" stroke="#1c5d4c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="18" y="12" width="84" height="60" rx="5" fill="#fff"/><path d="M18 24h84"/><circle cx="26" cy="18" r="1.8" fill="#d8d1c4" stroke="none"/><circle cx="32" cy="18" r="1.8" fill="#d8d1c4" stroke="none"/><circle cx="38" cy="18" r="1.8" fill="#d8d1c4" stroke="none"/><path d="M28 34h22M28 41h16M28 48h20M28 55h12" stroke="#d8d1c4"/><rect x="62" y="42" width="26" height="21" rx="3" fill="#fff" stroke="#b4791f" stroke-width="2.4"/><path d="M67 42v-5a8 8 0 0 1 16 0v5" stroke="#b4791f" stroke-width="2.4"/><path d="M75 50v5" stroke="#b4791f" stroke-width="2.4"/></svg></div><h3>Processes you don’t control</h3><p>When your process lives inside a vendor’s product, a price change or a feature change can undo it.</p></div><div class="card c"><div class="il"><svg viewBox="0 0 120 84" fill="none" stroke="#1c5d4c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="54" cy="44" r="30" fill="#fff"/><path d="M54 17v5" transform="rotate(0 54 44)" stroke="#1c5d4c"/><path d="M54 18.5v3" transform="rotate(30 54 44)" stroke="#d8d1c4"/><path d="M54 18.5v3" transform="rotate(60 54 44)" stroke="#d8d1c4"/><path d="M54 17v5" transform="rotate(90 54 44)" stroke="#1c5d4c"/><path d="M54 18.5v3" transform="rotate(120 54 44)" stroke="#d8d1c4"/><path d="M54 18.5v3" transform="rotate(150 54 44)" stroke="#d8d1c4"/><path d="M54 17v5" transform="rotate(180 54 44)" stroke="#1c5d4c"/><path d="M54 18.5v3" transform="rotate(210 54 44)" stroke="#d8d1c4"/><path d="M54 18.5v3" transform="rotate(240 54 44)" stroke="#d8d1c4"/><path d="M54 17v5" transform="rotate(270 54 44)" stroke="#1c5d4c"/><path d="M54 18.5v3" transform="rotate(300 54 44)" stroke="#d8d1c4"/><path d="M54 18.5v3" transform="rotate(330 54 44)" stroke="#d8d1c4"/><path d="M54 44 44.5 28.5" stroke-width="3"/><path d="M54 44V21" stroke-width="2.2"/><circle cx="54" cy="44" r="2.4" fill="#1c5d4c" stroke="none"/><path d="M99 14a9 9 0 1 0 8 13 7 7 0 0 1-8-13z" fill="#fbf1e0" stroke="#b4791f"/></svg></div><h3>No time to learn</h3><p>The job already takes up the evenings, and learning AI on top of it feels like a second job.</p></div><div class="card c"><div class="il"><svg viewBox="0 0 120 84" fill="none" stroke="#1c5d4c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M38 50V24a8 8 0 0 1 8-8h28a8 8 0 0 1 8 8v26" fill="#fff"/><path d="M30 44a6 6 0 0 1 12 0v8h36v-8a6 6 0 0 1 12 0v18a3 3 0 0 1-3 3H33a3 3 0 0 1-3-3z" fill="#fff"/><path d="M42 52h36"/><path d="M36 65v8M84 65v8"/><path d="M48 30h24" stroke="#d8d1c4"/><path d="M14 73h92" stroke="#d8d1c4"/></svg></div><h3>Clients are changing</h3><p>Firms and clients are changing how they work faster than most playbooks are updated.</p></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🎯</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Who it’s for</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,41 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
.a{padding:24px 24px 26px;border-top:4px solid var(--pine)}
.ico.big{width:60px;height:60px;padding:14px;border-radius:14px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:block;margin-bottom:18px}
.a h3{font-family:var(--display);font-weight:900;font-size:28px;letter-spacing:-.025em;line-height:1.08}
.a .k{font-family:var(--mono);font-size:15px;font-weight:600;letter-spacing:.02em;color:var(--pine);margin-top:10px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:inline-block;padding:5px 12px;border-radius:999px}
.b{margin-top:12px;padding:16px 22px;display:flex;align-items:center;gap:16px}
.ico.mid{width:44px;height:44px;padding:10px;border-radius:12px;background:var(--paper-2);border:1px solid var(--line);color:var(--muted)}
.b h3{font-family:var(--display);font-weight:600;font-size:20px;letter-spacing:-.02em}
</style></head><body><div class="canvas" id="shot">
<p class="spec">Mainly for</p>
<div class="two">
<div class="card a"><span class="ico big"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="3.5" width="14" height="17" rx="2"/><path d="M8 7h8v3H8z"/><path d="M8.5 13.5h.01M12 13.5h.01M15.5 13.5h.01M8.5 17h.01M12 17h.01M15.5 17h.01"/></svg></span><h3>Accounting advisors</h3><p class="k">Exit to Client</p></div>
<div class="card a"><span class="ico big"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h16"/><path d="M6.5 16.5v-4M11 16.5V8.5M15.5 16.5v-6"/><path d="m5.5 9 5-4 4 3 4-3.5"/></svg></span><h3>Financial advisors</h3><p class="k">CKA® and CEPA® focus</p></div>
</div>
<p class="spec" style="margin-top:20px">Also for</p>
<div class="card b"><span class="ico mid"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="7.5" width="18" height="12" rx="2"/><path d="M9 7.5V6a1.5 1.5 0 0 1 1.5-1.5h3A1.5 1.5 0 0 1 15 6v1.5"/><path d="M3 12.5h18"/></svg></span><h3>Other professionals who advise clients</h3></div>
</div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">📖</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>What’s inside</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,48 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.rg{display:grid;grid-template-columns:250px 34px 1fr;margin-top:16px;align-items:stretch}
.ptop{background:linear-gradient(#fff,var(--paper));border:1px solid var(--line);border-bottom:1px solid var(--line);border-radius:16px 16px 0 0;padding:12px 16px;display:flex;justify-content:space-between;align-items:center}
.ptop span{font-family:var(--mono);font-size:12px;color:var(--muted)}
.ptop em{font-style:normal;font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--pine);background:var(--pine-wash);border:1px solid var(--pine-tint);padding:3px 9px;border-radius:999px}
.pc{background:#fff;border-left:1px solid var(--line);border-right:1px solid var(--line);padding:11px 30px 11px 16px;position:relative;display:flex;flex-direction:column;justify-content:center;gap:7px;border-top:1px dashed var(--line-2)}
.pc.first{border-top:none;padding-top:16px}
.pc.last{border-bottom:1px solid var(--line);border-radius:0 0 16px 16px;padding-bottom:16px}
.pc .k{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--pine)}
.bars{display:flex;flex-direction:column;gap:5px}
.bars i{display:block;height:6px;border-radius:3px;background:#ece6db}
.bars .term{display:block;height:20px;border-radius:5px;background:#14110e}
.bars .pill{display:block;width:70px;height:14px;border-radius:999px;background:rgba(180,121,31,.14);border:1px solid rgba(180,121,31,.3)}
.bars .gd{display:block;height:6px;border-radius:3px;background:linear-gradient(90deg,var(--pine) 0 8px,transparent 8px 14px,#ece6db 14px);width:86%}
.pc .n{position:absolute;right:-13px;top:50%;transform:translateY(-50%);width:26px;height:26px;border-radius:999px;background:var(--pine);color:#fff;font-family:var(--mono);font-size:13px;font-weight:600;display:grid;place-items:center;box-shadow:0 0 0 3px var(--paper-2);z-index:2}
.ln{position:relative}.ln::before{content:"";position:absolute;left:13px;right:4px;top:50%;border-top:1.5px solid var(--pine);opacity:.5}
.co{padding:8px 0 8px 4px;display:flex;flex-direction:column;justify-content:center}
.co h4{font-weight:700;font-size:16px;line-height:1.25}
.co p{font-size:14px;line-height:1.42;color:var(--muted);margin-top:2px}
</style></head><body><div class="canvas" id="shot">
<p class="spec">Every workflow follows the same recipe</p>
<div class="rg">
<div class="ptop"><span>Workflow page</span><em>~15 min</em></div><div></div><div></div>
<div class="pc first"><span class="k">Problem</span><span class="bars"><i style="width:88%"></i><i style="width:64%"></i></span><b class="n">1</b></div><div class="ln"></div><div class="co"><h4>The problem</h4><p>The real task it solves.</p></div><div class="pc"><span class="k">Who runs it</span><span class="bars"><i style="width:88%"></i><i style="width:64%"></i></span><b class="n">2</b></div><div class="ln"></div><div class="co"><h4>Who runs it</h4><p>A person, a role, or an AI agent working under a person’s supervision.</p></div><div class="pc"><span class="k">Setup</span><span class="bars"><i style="width:88%"></i><i style="width:64%"></i></span><b class="n">3</b></div><div class="ln"></div><div class="co"><h4>Setup</h4><p>The tools, sample files, and expected cost.</p></div><div class="pc"><span class="k">Safeguards</span><span class="bars"><span class="gd"></span><span class="gd"></span></span><b class="n">4</b></div><div class="ln"></div><div class="co"><h4>Safeguards</h4><p>How to handle client data, where a person must review the work, and what can go wrong. These come before the steps.</p></div><div class="pc"><span class="k">Steps</span><span class="bars"><i style="width:80%"></i><span class="term"></span></span><b class="n">5</b></div><div class="ln"></div><div class="co"><h4>The steps</h4><p>With the actual output the tools produced.</p></div><div class="pc"><span class="k">Evidence</span><span class="bars"><i style="width:88%"></i><i style="width:64%"></i></span><b class="n">6</b></div><div class="ln"></div><div class="co"><h4>Evidence</h4><p>What the result looked like when we ran it.</p></div><div class="pc"><span class="k">Try it</span><span class="bars"><i style="width:72%"></i><span class="pill"></span></span><b class="n">7</b></div><div class="ln"></div><div class="co"><h4>Try it</h4><p>A short exercise of about 15 minutes, using sample data.</p></div><div class="pc last"><span class="k">Record</span><span class="bars"><i style="width:88%"></i><i style="width:64%"></i></span><b class="n">8</b></div><div class="ln"></div><div class="co"><h4>Costs, author, and sources</h4></div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,30 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.tg{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:14px}
.t{padding:18px 18px 20px;position:relative;min-height:118px;display:flex;flex-direction:column}
.t .n{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.08em}
.t h3{font-family:var(--display);font-weight:600;font-size:22px;letter-spacing:-.02em;line-height:1.12;margin-top:auto;padding-top:22px}
.t .ico{position:absolute;right:16px;top:16px;width:22px;height:22px;opacity:.8}
</style></head><body><div class="canvas" id="shot"><p class="spec">Chapters</p><div class="tg"><div class="card t"><span class="n">01</span><h3>Real Estate</h3><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span></div><div class="card t"><span class="n">02</span><h3>Financial Advisors</h3><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span></div><div class="card t"><span class="n">03</span><h3>Fractional <span style="white-space:nowrap">C-Suite</span></h3><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span></div><div class="card t"><span class="n">04</span><h3>Higher Education</h3><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span></div><div class="card t"><span class="n">05</span><h3>Communities</h3><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span></div><div class="card t"><span class="n">06</span><h3>Technology</h3><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🧪</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Try one workflow</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,70 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../reader/globals.css"><link rel="stylesheet" href="../styles.css"><style>
body{background:#fff}
.win{background:#fff;border:1px solid var(--line-2);border-radius:14px;overflow:hidden;box-shadow:var(--sh-lg)}
.bar{display:flex;align-items:center;gap:7px;padding:10px 14px;background:#f1eee7;border-bottom:1px solid var(--line)}
.bar i{width:11px;height:11px;border-radius:999px;background:#d8d1c4;display:block}
.bar .url{margin-left:14px;flex:1;background:#fff;border:1px solid var(--line);border-radius:999px;padding:4px 14px;font-family:var(--mono);font-size:13px;color:var(--muted)}
.scr{padding:20px;background:var(--paper)}
.recipe{box-shadow:var(--sh-md);border-radius:16px}
.recipe-top{padding:14px 18px}
.recipe-id{font-size:13px}
.recipe-vert{font-size:12px}
.recipe-body{padding:22px 20px 22px}
.recipe-title{font-size:23px;margin-bottom:16px}
.xrow{display:grid;grid-template-columns:96px 1fr;gap:14px;padding:14px 0;border-top:1px dashed var(--line-2)}
.xrow .k{font-family:var(--mono);font-size:12.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--pine);padding-top:3px;font-weight:500}
.guard{font-size:15px;padding:5px 0;color:var(--muted);gap:9px;line-height:1.45}
.guard svg{width:18px;height:18px;color:var(--pine);flex:none;margin-top:1px}
.mins{font-weight:700;font-size:16px;margin-bottom:8px}
.cl li{display:flex;gap:11px;align-items:flex-start;font-size:15px;line-height:1.45;padding:5px 0}
.cl .box{width:18px;height:18px;border:1.6px solid var(--pine);border-radius:5px;flex:none;margin-top:2px;background:#fff}
.acts{display:flex;align-items:center;gap:12px;margin-top:12px;flex-wrap:wrap}
.chip{font-size:13px}
.btn{font-size:15px;padding:10px 18px}
.foot-note{text-align:center}
</style></head><body><div class="canvas" id="shot">
<div class="win">
<div class="bar"><i></i><i></i><i></i><span class="url">aibook.fun</span></div>
<div class="scr">
<article class="recipe">
<div class="recipe-top"><span class="recipe-id"><b>RE-01</b> · v1.2 · ~15 min · ≈ $0.03</span><span class="recipe-vert">Real Estate</span></div>
<div class="recipe-body">
<h3 class="recipe-title">Draft a property-market brief with an AI agent without leaking client data.</h3>
<div class="xrow"><h4 class="k">Safeguards</h4><div class="v">
<div class="guard"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z" stroke="#1C5D4C" stroke-width="1.3" stroke-linejoin="round"/><path d="m5.6 8 1.7 1.7L10.6 6" stroke="#1C5D4C" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg><span>Redaction runs on your machine. Raw client data never reaches the model.</span></div>
<div class="guard"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z" stroke="#1C5D4C" stroke-width="1.3" stroke-linejoin="round"/><path d="m5.6 8 1.7 1.7L10.6 6" stroke="#1C5D4C" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg><span>A human checklist is required before the brief is shared.</span></div>
</div></div>
<div class="xrow try"><h4 class="k">Try-it</h4><div class="v">
<p class="mins">About 15 minutes</p>
<ul class="cl"><li><span class="box"></span>Use the included sample_listings.csv (no live client file).</li><li><span class="box"></span>Produce the market-1pager brief.</li><li><span class="box"></span>Walk the human checklist.</li><li><span class="box"></span>Mark complete when you have a brief you would send after review.</li></ul>
<div class="acts"><span class="chip">◷ cost to try ≈ $0.03</span><span class="btn btn-ghost">Mark complete</span></div>
</div></div>
</div>
</article>
</div>
</div>
<p class="foot-note">Sample workflow RE-01, shown in the AiBook.fun reader.</p>
</div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,32 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{padding:26px 32px 24px}
.cta{text-align:center}
.b{display:inline-flex;align-items:center;gap:12px;background:var(--pine);color:#f7fbf9;font-weight:700;font-size:24px;letter-spacing:-.01em;border-radius:999px;padding:18px 34px;box-shadow:0 10px 24px -10px rgba(28,93,76,.6)}
.b svg{width:24px;height:24px}
.cta p{font-family:var(--mono);font-size:14px;color:var(--muted);letter-spacing:.03em;margin-top:14px}
</style></head><body><div class="canvas" id="shot">
<div class="cta"><span class="b">Try the sample workflow <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
<p>Free · about 15 minutes · sample data · aibook.fun</p></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🧭</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Three ways to back</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,72 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.cols{display:grid;grid-template-columns:1fr 1fr 1.3fr;grid-template-rows:repeat(6,auto);column-gap:10px}
.col{padding:18px 15px 16px;display:grid;grid-template-rows:subgrid;grid-row:span 6}
.nm{font-family:var(--mono);font-size:13px;letter-spacing:.12em;text-transform:uppercase;color:var(--pine);font-weight:600}
.col h3{font-family:var(--display);font-weight:600;font-size:19px;letter-spacing:-.02em;line-height:1.15;margin:6px 0 10px}
.sec{border-top:1px dashed var(--line-2);padding:10px 0 12px}
.span2{grid-row:span 2}
.lb{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.sec p{font-size:14px;line-height:1.4}
.fact{display:flex;gap:7px;align-items:flex-start;margin-top:6px;font-weight:600}
.lb+.fact{margin-top:0}
.fact .ico,.inc .ico{width:17px;height:17px;margin-top:1px}
.inc li{display:flex;gap:7px;align-items:flex-start;font-size:14px;line-height:1.35;padding:2px 0}
.pr{align-self:end}
.price{font-family:var(--mono);font-size:12.5px;font-weight:600;color:var(--pine);background:var(--pine-wash);border:1px solid var(--pine-tint);border-radius:999px;padding:7px 6px;text-align:center;white-space:nowrap}
.cloud{border-color:rgba(28,93,76,.28)}
.prices li{display:grid;grid-template-columns:58px 1fr;gap:8px;align-items:baseline;padding:7px 8px;border-radius:10px;font-size:14px;line-height:1.35}
.prices li+li{margin-top:4px}
.prices b{font-family:var(--display);font-weight:900;font-size:24px;letter-spacing:-.03em;color:var(--ink)}
.prices b.np{align-self:start;color:var(--muted);width:22px;height:22px;margin:1px 0 0 4px}
.prices small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff}
.dl li{font-size:14px;line-height:1.4;padding:2px 0 2px 14px;position:relative}
.dl li::before{content:"";position:absolute;left:0;top:8px;width:6px;height:6px;border-radius:999px;background:var(--pine)}
</style></head><body><div class="canvas" id="shot"><div class="cols">
<div class="col card">
<p class="nm">Retreat</p><h3>Burned Out Advisor Retreat</h3>
<div class="sec"><p class="lb">What it is</p><p>Three days in person with the authors and other advisors.</p>
<p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="3"/><path d="M6.5 19c.6-3 2.8-4.8 5.5-4.8s4.9 1.8 5.5 4.8"/><circle cx="5" cy="10" r="2"/><circle cx="19" cy="10" r="2"/><path d="M2 17c.3-1.7 1.4-2.8 3-3M22 17c-.3-1.7-1.4-2.8-3-3"/></svg></span>369 seats</p></div>
<div class="sec"><p class="lb">Includes</p><ul class="inc"><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 11V6.5A2.5 2.5 0 0 1 8.5 4h7A2.5 2.5 0 0 1 18 6.5V11"/><path d="M4 11.5a2 2 0 0 1 4 0V14h8v-2.5a2 2 0 0 1 4 0V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/><path d="M6.5 18v2M17.5 18v2"/></svg></span>Your seat</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span>The eBook</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="12" rx="1.6"/><path d="M2 19.5h20"/><path d="M8 8.5h8M8 11.5h5"/></svg></span>12 months of AiBook.fun</li></ul></div>
<div class="sec"><p class="lb">When</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/><path d="M7.5 13h2M11 13h2M14.5 13h2M7.5 16.5h2M11 16.5h2"/></svg></span>4–6 November 2026</p></div>
<div class="pr"><p class="price">Price on the reward</p></div>
</div>
<div class="col card">
<p class="nm">Academy</p><h3>AiCoach + AOP.biz BootCamp</h3>
<div class="sec"><p class="lb">What it is</p><p>A seat in the next cohort at one of three sites:</p>
<p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>Orlando, Florida</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>North Denver, Colorado</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>Villa Park, Chicagoland, Illinois</p></div>
<div class="sec"><p class="lb">Includes</p><ul class="inc"><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 11V6.5A2.5 2.5 0 0 1 8.5 4h7A2.5 2.5 0 0 1 18 6.5V11"/><path d="M4 11.5a2 2 0 0 1 4 0V14h8v-2.5a2 2 0 0 1 4 0V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/><path d="M6.5 18v2M17.5 18v2"/></svg></span>Your seat</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span>The eBook</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="12" rx="1.6"/><path d="M2 19.5h20"/><path d="M8 8.5h8M8 11.5h5"/></svg></span>12 months of AiBook.fun</li></ul></div>
<div class="sec"><p class="lb">When</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/><path d="M7.5 13h2M11 13h2M14.5 13h2M7.5 16.5h2M11 16.5h2"/></svg></span>The next cohort at your chosen site</p></div>
<div class="pr"><p class="price">Price on the reward</p></div>
</div>
<div class="col card cloud">
<p class="nm">Cloud</p><h3>Do it yourself, online</h3>
<div class="sec span2"><p class="lb">Choose one</p><ul class="prices"><li class=""><b>$19</b><span>Early Bird eBook<small>First 200 backers</small></span></li><li class=""><b>$25</b><span>eBook and backer-only updates</span></li><li class="feat"><b>$47</b><span>eBook and 12 months of the AiBook.fun library</span></li><li><b class="np"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></b><span>Signed paperback and eBook<small>Price on the reward</small></span></li></ul></div>
<div class="sec"><p class="lb">When</p><ul class="dl"><li>Library access: within about 14 days of a successful campaign</li><li>eBook: January 2027</li><li>Signed paperback: February 2027</li></ul></div>
<div class="pr"></div>
</div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,72 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.cols{display:grid;grid-template-columns:1fr 1fr 1.3fr;grid-template-rows:repeat(6,auto);column-gap:10px}
.col{padding:18px 15px 16px;display:grid;grid-template-rows:subgrid;grid-row:span 6}
.nm{font-family:var(--mono);font-size:13px;letter-spacing:.12em;text-transform:uppercase;color:var(--pine);font-weight:600}
.col h3{font-family:var(--display);font-weight:600;font-size:19px;letter-spacing:-.02em;line-height:1.15;margin:6px 0 10px}
.sec{border-top:1px dashed var(--line-2);padding:10px 0 12px}
.span2{grid-row:span 2}
.lb{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.sec p{font-size:14px;line-height:1.4}
.fact{display:flex;gap:7px;align-items:flex-start;margin-top:6px;font-weight:600}
.lb+.fact{margin-top:0}
.fact .ico,.inc .ico{width:17px;height:17px;margin-top:1px}
.inc li{display:flex;gap:7px;align-items:flex-start;font-size:14px;line-height:1.35;padding:2px 0}
.pr{align-self:end}
.price{font-family:var(--mono);font-size:12.5px;font-weight:600;color:var(--pine);background:var(--pine-wash);border:1px solid var(--pine-tint);border-radius:999px;padding:7px 6px;text-align:center;white-space:nowrap}
.cloud{border-color:rgba(28,93,76,.28)}
.prices li{display:grid;grid-template-columns:58px 1fr;gap:8px;align-items:baseline;padding:7px 8px;border-radius:10px;font-size:14px;line-height:1.35}
.prices li+li{margin-top:4px}
.prices b{font-family:var(--display);font-weight:900;font-size:24px;letter-spacing:-.03em;color:var(--ink)}
.prices b.np{align-self:start;color:var(--muted);width:22px;height:22px;margin:1px 0 0 4px}
.prices small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff}
.dl li{font-size:14px;line-height:1.4;padding:2px 0 2px 14px;position:relative}
.dl li::before{content:"";position:absolute;left:0;top:8px;width:6px;height:6px;border-radius:999px;background:var(--pine)}
</style></head><body><div class="canvas" id="shot"><div class="cols">
<div class="col card">
<p class="nm">Retreat</p><h3>Burned Out Advisor Retreat</h3>
<div class="sec"><p class="lb">What it is</p><p>Three days in person with the authors and other advisors.</p>
<p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="3"/><path d="M6.5 19c.6-3 2.8-4.8 5.5-4.8s4.9 1.8 5.5 4.8"/><circle cx="5" cy="10" r="2"/><circle cx="19" cy="10" r="2"/><path d="M2 17c.3-1.7 1.4-2.8 3-3M22 17c-.3-1.7-1.4-2.8-3-3"/></svg></span>369 seats</p></div>
<div class="sec"><p class="lb">Includes</p><ul class="inc"><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 11V6.5A2.5 2.5 0 0 1 8.5 4h7A2.5 2.5 0 0 1 18 6.5V11"/><path d="M4 11.5a2 2 0 0 1 4 0V14h8v-2.5a2 2 0 0 1 4 0V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/><path d="M6.5 18v2M17.5 18v2"/></svg></span>Your seat</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span>The eBook</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="12" rx="1.6"/><path d="M2 19.5h20"/><path d="M8 8.5h8M8 11.5h5"/></svg></span>12 months of AiBook.fun</li></ul></div>
<div class="sec"><p class="lb">When</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/><path d="M7.5 13h2M11 13h2M14.5 13h2M7.5 16.5h2M11 16.5h2"/></svg></span>4–6 November 2026</p></div>
<div class="pr"><p class="price">Price on the reward</p></div>
</div>
<div class="col card">
<p class="nm">Academy</p><h3>AiCoach + AOP.biz BootCamp</h3>
<div class="sec"><p class="lb">What it is</p><p>A seat in the next cohort at one of three sites:</p>
<p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>Orlando, Florida</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>North Denver, Colorado</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>Villa Park, Chicagoland, Illinois</p></div>
<div class="sec"><p class="lb">Includes</p><ul class="inc"><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 11V6.5A2.5 2.5 0 0 1 8.5 4h7A2.5 2.5 0 0 1 18 6.5V11"/><path d="M4 11.5a2 2 0 0 1 4 0V14h8v-2.5a2 2 0 0 1 4 0V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/><path d="M6.5 18v2M17.5 18v2"/></svg></span>Your seat</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span>The eBook</li><li><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="12" rx="1.6"/><path d="M2 19.5h20"/><path d="M8 8.5h8M8 11.5h5"/></svg></span>12 months of AiBook.fun</li></ul></div>
<div class="sec"><p class="lb">When</p><p class="fact"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/><path d="M7.5 13h2M11 13h2M14.5 13h2M7.5 16.5h2M11 16.5h2"/></svg></span>The next cohort at your chosen site</p></div>
<div class="pr"><p class="price">Price on the reward</p></div>
</div>
<div class="col card cloud">
<p class="nm">Cloud</p><h3>Do it yourself, online</h3>
<div class="sec span2"><p class="lb">Choose one</p><ul class="prices"><li class=""><b>$19</b><span>Early Bird eBook<small>First 200 backers</small></span></li><li class=""><b>$25</b><span>eBook and backer-only updates</span></li><li class="feat"><b>$47</b><span>eBook and 12 months of the AiBook.fun library</span></li></ul></div>
<div class="sec"><p class="lb">When</p><ul class="dl"><li>Library access: within about 14 days of a successful campaign</li><li>eBook: January 2027</li></ul></div>
<div class="pr"></div>
</div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🏕️</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>The Retreat</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,39 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{padding:0;height:383px}
.venue{position:absolute;inset:0}
.venue .ph{border-radius:0;border-width:0;justify-content:flex-start;padding-top:70px}
.ov{position:absolute;left:28px;bottom:26px;background:rgba(252,251,248,.96);border-radius:16px;padding:16px 22px 18px;box-shadow:var(--sh-lg);max-width:420px}
.ov h3{font-family:var(--display);font-weight:900;font-size:30px;letter-spacing:-.025em;line-height:1.05;margin:6px 0 10px}
.ov .d{display:flex;align-items:center;gap:9px;font-weight:700;font-size:18px}
.ov .d .ico{width:22px;height:22px}
.seats{position:absolute;right:28px;top:26px;width:108px;height:108px;border-radius:999px;background:var(--pine);color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;box-shadow:0 0 0 5px rgba(252,251,248,.9),var(--sh-lg)}
.seats b{font-family:var(--display);font-weight:900;font-size:38px;line-height:1;letter-spacing:-.03em}
.seats span{font-family:var(--mono);font-size:14px;letter-spacing:.12em;text-transform:uppercase;color:#cfe0d6;margin-top:3px}
</style></head><body><div class="canvas" id="shot">
<div class="slot venue" data-src="../assets/retreat-venue" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Venue photo goes here</b><small>Landscape · at least 1360 × 765 px</small></div></div>
<div class="ov"><p class="spec">We Own AI</p><h3>Burned Out Advisor Retreat</h3><p class="d"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/><path d="M7.5 13h2M11 13h2M14.5 13h2M7.5 16.5h2M11 16.5h2"/></svg></span>4 to 6 November 2026</p></div>
<div class="seats"><b>369</b><span>seats</span></div>
</div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🎓</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>The Academy</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,35 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.sg{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.s{overflow:hidden}
.sp{aspect-ratio:4/3}
.sp .ph{border-radius:0;border-width:0 0 2px 0}
.sp .ph b{font-size:14px}.sp .ph small{font-size:11.5px}
.b{padding:14px 15px 16px}
.n{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.b h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;margin:4px 0 6px}
.pl{display:flex;gap:6px;align-items:flex-start;font-size:14px;line-height:1.35;color:var(--ink)}
.pl .ico{width:17px;height:17px;margin-top:1px}
</style></head><body><div class="canvas" id="shot"><div class="sg"><div class="card s"><div class="slot sp" data-src="../assets/site-orlando" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Site photo goes here</b><small>At least 800 × 600 px</small></div></div><div class="b"><p class="n">Site 1</p><h3>Azur Resorts</h3><p class="pl"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>Orlando, Florida</p></div></div><div class="card s"><div class="slot sp" data-src="../assets/site-north-denver" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Site photo goes here</b><small>At least 800 × 600 px</small></div></div><div class="b"><p class="n">Site 2</p><h3>#NoDe</h3><p class="pl"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>North Denver, Colorado</p></div></div><div class="card s"><div class="slot sp" data-src="../assets/site-villa-park" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Site photo goes here</b><small>At least 800 × 600 px</small></div></div><div class="b"><p class="n">Site 3</p><h3>Villa Park</h3><p class="pl"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg></span>Chicagoland, Illinois</p></div></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">☁️</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Cloud and AiBook.fun</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,74 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{padding:24px 26px 22px}
.stage{position:relative;height:500px}
.laptop{position:absolute;left:18px;top:56px;width:470px}
.lid{background:#1f1b17;border-radius:14px 14px 4px 4px;padding:11px 11px 13px;box-shadow:var(--sh-lg)}
.laptop .scr{position:relative;aspect-ratio:1120/700;overflow:hidden;border-radius:3px;background:#fff}
.base{height:14px;margin:0 -30px;background:linear-gradient(#d9d4cb,#bdb6aa);border-radius:0 0 14px 14px;position:relative;box-shadow:0 14px 22px -12px rgba(0,0,0,.35)}
.base::before{content:"";position:absolute;left:50%;top:0;width:80px;height:6px;transform:translateX(-50%);background:#a9a295;border-radius:0 0 8px 8px}
.phone{position:absolute;right:18px;top:62px;width:176px;background:#1f1b17;border-radius:30px;padding:8px;box-shadow:var(--sh-lg),0 0 0 1px #3a342b inset}
.phone .scr{position:relative;aspect-ratio:390/844;overflow:hidden;border-radius:23px;background:#fff}
.scr img{position:absolute;inset:0;width:100%;height:100%;display:block}
.tg{position:absolute;width:0;height:0}
.co{position:absolute;z-index:3;background:#fff;border:1.5px solid var(--pine);color:var(--pine-deep);font-weight:700;font-size:16px;padding:8px 16px;border-radius:999px;box-shadow:var(--sh-md);white-space:nowrap}
.lines{position:absolute;inset:0;width:100%;height:100%;z-index:2;overflow:visible}
.lines path{fill:none;stroke:var(--pine);stroke-width:1.8}
.lines .dot{fill:var(--pine);stroke:#fff;stroke-width:2}
.lines .halo{fill:none;stroke:var(--pine);stroke-width:1.5;opacity:.35}
.foot-note{text-align:center;margin-top:14px}
</style></head><body><div class="canvas" id="shot">
<div class="stage">
<div class="laptop"><div class="lid"><div class="scr"><img src="../assets-gen/screen-laptop.png">
<i class="tg" data-t="essay" style="left:27.32%;top:15.67%"></i><i class="tg" data-t="workflow" style="left:29.91%;top:54.70%"></i></div></div><div class="base"></div></div>
<div class="phone"><div class="scr"><img src="../assets-gen/screen-phone.png">
<i class="tg" data-t="tryit" style="left:33.67%;top:14.49%"></i><i class="tg" data-t="updated" style="left:65.06%;top:92.33%"></i></div></div>
<span class="co" data-c="essay" style="left:0;top:0">Chapter essay</span>
<span class="co" data-c="workflow" style="left:0;bottom:0">Workflow</span>
<span class="co" data-c="tryit" style="right:0;top:0">Try-it (about 15 minutes)</span>
<span class="co" data-c="updated" style="right:0;bottom:0">Last updated</span>
<svg class="lines"></svg>
</div>
<p class="foot-note">Mockup of the AiBook.fun reader, showing the sample Real Estate chapter.</p>
</div>
<script>
window.afterLayout = async () => {
await Promise.all([...document.images].map(i => i.complete ? 1 : new Promise(r => i.onload = r)));
const st = document.querySelector('.stage').getBoundingClientRect();
const svg = document.querySelector('.lines');
let s = '';
for (const c of document.querySelectorAll('.co')) {
const t = document.querySelector('.tg[data-t="' + c.dataset.c + '"]').getBoundingClientRect();
const r = c.getBoundingClientRect();
const tx = t.left - st.left, ty = t.top - st.top;
const top = r.top - st.top < st.height / 2;
const cx = Math.min(Math.max(tx, r.left - st.left + 16), r.right - st.left - 16);
const cy = top ? r.bottom - st.top : r.top - st.top;
s += `<path d="M${cx} ${cy} L${cx} ${ty} L${tx} ${ty}" />`;
s += `<circle cx="${tx}" cy="${ty}" r="4.5" class="dot"/><circle cx="${tx}" cy="${ty}" r="8" class="halo"/>`;
}
svg.innerHTML = s;
};
</script>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🎁</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Stretch goals</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,51 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{height:250px;padding:24px 32px 18px}
.top{display:flex;justify-content:space-between;align-items:baseline}
.st{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.02em}
.track{position:relative;height:14px;border-radius:999px;background:#e6e0d4;margin:58px 0 0;box-shadow:inset 0 1px 2px rgba(0,0,0,.08)}
.fill{position:absolute;left:0;top:0;bottom:0;border-radius:999px;background:linear-gradient(90deg,#23745f,var(--pine))}
.m{position:absolute;top:50%;width:0;height:0}
.lk{position:absolute;left:-20px;top:-20px;width:40px;height:40px;border-radius:999px;background:#fff;border:2px solid var(--line-2);color:var(--muted);display:grid;place-items:center;box-shadow:var(--sh-sm)}
.lk svg{width:21px;height:21px}
.m.on .lk{background:var(--pine);border-color:#fff;color:#fff;box-shadow:0 0 0 2px var(--pine),var(--sh-md)}
.m.on .lbl b{color:var(--pine-deep)}
.ul{display:inline-block;vertical-align:4px;margin-left:8px;font-family:var(--mono);font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--pine);padding:3px 10px;border-radius:999px;white-space:nowrap}
.lbl{position:absolute;top:30px;width:196px;left:-98px;text-align:center}
.lbl b{display:block;font-family:var(--display);font-weight:900;font-size:23px;letter-spacing:-.03em;line-height:1.1}
.lbl .nm{display:block;font-weight:700;font-size:15.5px;margin-top:2px}
.lbl .ds{display:block;font-size:13.5px;line-height:1.35;color:var(--muted);margin-top:3px}
.goal{position:absolute;left:0;top:50%;width:0;height:0}
.goal::before{content:"";position:absolute;left:0;top:-7px;width:14px;height:14px;border-radius:999px;background:#fff;border:3px solid var(--amber)}
.gf{position:absolute;left:3px;top:-44px;width:24px;height:24px;color:var(--amber)}
.gf svg{width:24px;height:24px}
.gl{position:absolute;left:28px;top:-42px;white-space:nowrap}
.gl b{font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.04em;color:var(--amber)}
</style></head><body><div class="canvas" id="shot">
<div class="top"><p class="spec">Stretch goals</p><p class="st">Not yet unlocked</p></div>
<div class="track"><div class="fill" style="width:0%"></div>
<div class="goal"><span class="gf"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 21V4"/><path d="M5 4.5h11l-2 3.5 2 3.5H5"/></svg></span><div class="gl"><b>Goal $5,000</b></div></div>
<div class="m " style="left:16%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/></svg></span><div class="lbl"><b>$15,000</b><span class="nm">Bonus chapter</span><span class="ds">PDF for every backer whose reward includes the eBook</span></div></div><div class="m " style="left:50%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/></svg></span><div class="lbl"><b>$35,000</b><span class="nm">Audiobook sample</span><span class="ds">One chapter recorded as audio, for every backer</span></div></div><div class="m " style="left:84%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/></svg></span><div class="lbl"><b>$54,000</b><span class="nm">Live Q&A replay pack</span><span class="ds">Recording of a live Q&A with the authors, for every backer</span></div></div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,51 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{height:250px;padding:24px 32px 18px}
.top{display:flex;justify-content:space-between;align-items:baseline}
.st{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.02em}
.track{position:relative;height:14px;border-radius:999px;background:#e6e0d4;margin:58px 0 0;box-shadow:inset 0 1px 2px rgba(0,0,0,.08)}
.fill{position:absolute;left:0;top:0;bottom:0;border-radius:999px;background:linear-gradient(90deg,#23745f,var(--pine))}
.m{position:absolute;top:50%;width:0;height:0}
.lk{position:absolute;left:-20px;top:-20px;width:40px;height:40px;border-radius:999px;background:#fff;border:2px solid var(--line-2);color:var(--muted);display:grid;place-items:center;box-shadow:var(--sh-sm)}
.lk svg{width:21px;height:21px}
.m.on .lk{background:var(--pine);border-color:#fff;color:#fff;box-shadow:0 0 0 2px var(--pine),var(--sh-md)}
.m.on .lbl b{color:var(--pine-deep)}
.ul{display:inline-block;vertical-align:4px;margin-left:8px;font-family:var(--mono);font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--pine);padding:3px 10px;border-radius:999px;white-space:nowrap}
.lbl{position:absolute;top:30px;width:196px;left:-98px;text-align:center}
.lbl b{display:block;font-family:var(--display);font-weight:900;font-size:23px;letter-spacing:-.03em;line-height:1.1}
.lbl .nm{display:block;font-weight:700;font-size:15.5px;margin-top:2px}
.lbl .ds{display:block;font-size:13.5px;line-height:1.35;color:var(--muted);margin-top:3px}
.goal{position:absolute;left:0;top:50%;width:0;height:0}
.goal::before{content:"";position:absolute;left:0;top:-7px;width:14px;height:14px;border-radius:999px;background:#fff;border:3px solid var(--amber)}
.gf{position:absolute;left:3px;top:-44px;width:24px;height:24px;color:var(--amber)}
.gf svg{width:24px;height:24px}
.gl{position:absolute;left:28px;top:-42px;white-space:nowrap}
.gl b{font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.04em;color:var(--amber)}
</style></head><body><div class="canvas" id="shot">
<div class="top"><p class="spec">Stretch goals</p><p class="st">Unlocked: $15,000</p></div>
<div class="track"><div class="fill" style="width:16%"></div>
<div class="goal"><span class="gf"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 21V4"/><path d="M5 4.5h11l-2 3.5 2 3.5H5"/></svg></span><div class="gl"><b>Goal $5,000</b></div></div>
<div class="m on" style="left:16%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/></svg></span><div class="lbl"><b>$15,000<span class="ul">Unlocked</span></b><span class="nm">Bonus chapter</span><span class="ds">PDF for every backer whose reward includes the eBook</span></div></div><div class="m " style="left:50%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/></svg></span><div class="lbl"><b>$35,000</b><span class="nm">Audiobook sample</span><span class="ds">One chapter recorded as audio, for every backer</span></div></div><div class="m " style="left:84%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/></svg></span><div class="lbl"><b>$54,000</b><span class="nm">Live Q&A replay pack</span><span class="ds">Recording of a live Q&A with the authors, for every backer</span></div></div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,51 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{height:250px;padding:24px 32px 18px}
.top{display:flex;justify-content:space-between;align-items:baseline}
.st{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.02em}
.track{position:relative;height:14px;border-radius:999px;background:#e6e0d4;margin:58px 0 0;box-shadow:inset 0 1px 2px rgba(0,0,0,.08)}
.fill{position:absolute;left:0;top:0;bottom:0;border-radius:999px;background:linear-gradient(90deg,#23745f,var(--pine))}
.m{position:absolute;top:50%;width:0;height:0}
.lk{position:absolute;left:-20px;top:-20px;width:40px;height:40px;border-radius:999px;background:#fff;border:2px solid var(--line-2);color:var(--muted);display:grid;place-items:center;box-shadow:var(--sh-sm)}
.lk svg{width:21px;height:21px}
.m.on .lk{background:var(--pine);border-color:#fff;color:#fff;box-shadow:0 0 0 2px var(--pine),var(--sh-md)}
.m.on .lbl b{color:var(--pine-deep)}
.ul{display:inline-block;vertical-align:4px;margin-left:8px;font-family:var(--mono);font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--pine);padding:3px 10px;border-radius:999px;white-space:nowrap}
.lbl{position:absolute;top:30px;width:196px;left:-98px;text-align:center}
.lbl b{display:block;font-family:var(--display);font-weight:900;font-size:23px;letter-spacing:-.03em;line-height:1.1}
.lbl .nm{display:block;font-weight:700;font-size:15.5px;margin-top:2px}
.lbl .ds{display:block;font-size:13.5px;line-height:1.35;color:var(--muted);margin-top:3px}
.goal{position:absolute;left:0;top:50%;width:0;height:0}
.goal::before{content:"";position:absolute;left:0;top:-7px;width:14px;height:14px;border-radius:999px;background:#fff;border:3px solid var(--amber)}
.gf{position:absolute;left:3px;top:-44px;width:24px;height:24px;color:var(--amber)}
.gf svg{width:24px;height:24px}
.gl{position:absolute;left:28px;top:-42px;white-space:nowrap}
.gl b{font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.04em;color:var(--amber)}
</style></head><body><div class="canvas" id="shot">
<div class="top"><p class="spec">Stretch goals</p><p class="st">Unlocked: $15,000, $35,000</p></div>
<div class="track"><div class="fill" style="width:50%"></div>
<div class="goal"><span class="gf"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 21V4"/><path d="M5 4.5h11l-2 3.5 2 3.5H5"/></svg></span><div class="gl"><b>Goal $5,000</b></div></div>
<div class="m on" style="left:16%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/></svg></span><div class="lbl"><b>$15,000<span class="ul">Unlocked</span></b><span class="nm">Bonus chapter</span><span class="ds">PDF for every backer whose reward includes the eBook</span></div></div><div class="m on" style="left:50%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/></svg></span><div class="lbl"><b>$35,000<span class="ul">Unlocked</span></b><span class="nm">Audiobook sample</span><span class="ds">One chapter recorded as audio, for every backer</span></div></div><div class="m " style="left:84%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/><path d="M12 14.5v2.5"/></svg></span><div class="lbl"><b>$54,000</b><span class="nm">Live Q&A replay pack</span><span class="ds">Recording of a live Q&A with the authors, for every backer</span></div></div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,51 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.canvas{height:250px;padding:24px 32px 18px}
.top{display:flex;justify-content:space-between;align-items:baseline}
.st{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.02em}
.track{position:relative;height:14px;border-radius:999px;background:#e6e0d4;margin:58px 0 0;box-shadow:inset 0 1px 2px rgba(0,0,0,.08)}
.fill{position:absolute;left:0;top:0;bottom:0;border-radius:999px;background:linear-gradient(90deg,#23745f,var(--pine))}
.m{position:absolute;top:50%;width:0;height:0}
.lk{position:absolute;left:-20px;top:-20px;width:40px;height:40px;border-radius:999px;background:#fff;border:2px solid var(--line-2);color:var(--muted);display:grid;place-items:center;box-shadow:var(--sh-sm)}
.lk svg{width:21px;height:21px}
.m.on .lk{background:var(--pine);border-color:#fff;color:#fff;box-shadow:0 0 0 2px var(--pine),var(--sh-md)}
.m.on .lbl b{color:var(--pine-deep)}
.ul{display:inline-block;vertical-align:4px;margin-left:8px;font-family:var(--mono);font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--pine);padding:3px 10px;border-radius:999px;white-space:nowrap}
.lbl{position:absolute;top:30px;width:196px;left:-98px;text-align:center}
.lbl b{display:block;font-family:var(--display);font-weight:900;font-size:23px;letter-spacing:-.03em;line-height:1.1}
.lbl .nm{display:block;font-weight:700;font-size:15.5px;margin-top:2px}
.lbl .ds{display:block;font-size:13.5px;line-height:1.35;color:var(--muted);margin-top:3px}
.goal{position:absolute;left:0;top:50%;width:0;height:0}
.goal::before{content:"";position:absolute;left:0;top:-7px;width:14px;height:14px;border-radius:999px;background:#fff;border:3px solid var(--amber)}
.gf{position:absolute;left:3px;top:-44px;width:24px;height:24px;color:var(--amber)}
.gf svg{width:24px;height:24px}
.gl{position:absolute;left:28px;top:-42px;white-space:nowrap}
.gl b{font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.04em;color:var(--amber)}
</style></head><body><div class="canvas" id="shot">
<div class="top"><p class="spec">Stretch goals</p><p class="st">Unlocked: $15,000, $35,000, $54,000</p></div>
<div class="track"><div class="fill" style="width:84%"></div>
<div class="goal"><span class="gf"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 21V4"/><path d="M5 4.5h11l-2 3.5 2 3.5H5"/></svg></span><div class="gl"><b>Goal $5,000</b></div></div>
<div class="m on" style="left:16%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/></svg></span><div class="lbl"><b>$15,000<span class="ul">Unlocked</span></b><span class="nm">Bonus chapter</span><span class="ds">PDF for every backer whose reward includes the eBook</span></div></div><div class="m on" style="left:50%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/></svg></span><div class="lbl"><b>$35,000<span class="ul">Unlocked</span></b><span class="nm">Audiobook sample</span><span class="ds">One chapter recorded as audio, for every backer</span></div></div><div class="m on" style="left:84%"><span class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 7.7-1.6"/><path d="m9.6 15.4 1.7 1.7 3.2-3.3"/></svg></span><div class="lbl"><b>$54,000<span class="ul">Unlocked</span></b><span class="nm">Live Q&A replay pack</span><span class="ds">Recording of a live Q&A with the authors, for every backer</span></div></div>
</div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">⏳</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Timeline</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,37 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.tl{position:relative;height:396px}
.path{position:absolute;inset:0;width:616px;height:420px}
.path path{fill:none;stroke:var(--pine);stroke-width:3;stroke-dasharray:2 9;stroke-linecap:round;opacity:.7}
.stop{position:absolute;width:0;height:0}
.num{position:absolute;left:-23px;top:-23px;width:46px;height:46px;border-radius:999px;background:var(--pine);color:#fff;font-family:var(--display);font-weight:900;font-size:22px;display:grid;place-items:center;box-shadow:0 0 0 5px var(--paper-2),var(--sh-md)}
.stop.tba .num{background:#fff;color:var(--pine);border:2.5px solid var(--pine)}
.tx{position:absolute;top:34px;left:-92px;width:184px;text-align:center}
.tx b{display:block;font-family:var(--display);font-weight:600;font-size:18px;letter-spacing:-.02em;line-height:1.15;color:var(--ink)}
.tx p{font-size:14px;line-height:1.4;color:var(--muted);margin-top:5px}
.stop.tba .tx b{color:var(--pine)}
</style></head><body><div class="canvas" id="shot"><div class="tl">
<svg class="path" viewBox="0 0 616 420"><path d="M96 30 H484 C644 30 644 250 484 250 H96" /></svg>
<div class="stop" style="left:96px;top:30px"><span class="num">1</span><div class="tx"><b>Fri 2 Oct 2026</b><p>The campaign opens.</p></div></div><div class="stop" style="left:290px;top:30px"><span class="num">2</span><div class="tx"><b>Wed 4 Nov 2026</b><p>The campaign ends. The Burned Out Advisor Retreat runs 4 to 6 November.</p></div></div><div class="stop" style="left:484px;top:30px"><span class="num">3</span><div class="tx"><b>By mid-November 2026</b><p>AiBook.fun access for backers whose reward includes it, and the backer survey.</p></div></div><div class="stop tba" style="left:484px;top:250px"><span class="num">4</span><div class="tx"><b>Dates to be announced</b><p>Academy BootCamp cohorts in Orlando, North Denver, and Villa Park.</p></div></div><div class="stop" style="left:290px;top:250px"><span class="num">5</span><div class="tx"><b>January 2027</b><p>Final layout and eBook files. The eBook is delivered and the paperbacks are signed.</p></div></div><div class="stop" style="left:96px;top:250px"><span class="num">6</span><div class="tx"><b>February 2027</b><p>Signed paperbacks are delivered.</p></div></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">📦</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Shipping and delivery</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,37 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.lane{padding:16px 18px 18px}
.lane+.lane{margin-top:14px}
.lh{display:flex;align-items:center;gap:12px;padding-bottom:12px;border-bottom:1px dashed var(--line-2);margin-bottom:14px}
.ico.lhi{width:40px;height:40px;padding:9px;border-radius:12px;background:var(--pine);color:#fff}
.lh h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.1}
.lh p{font-family:var(--mono);font-size:13px;color:var(--muted);margin-top:3px}
.steps{display:flex;align-items:flex-start}
.stp{flex:1;min-width:0;text-align:center;padding:0 4px}
.ar{flex:none;width:18px;height:18px;color:var(--line-2);margin-top:14px}
.ico.ci{width:46px;height:46px;padding:11px;border-radius:999px;background:var(--pine-wash);border:1px solid var(--pine-tint);display:block;margin:0 auto 8px}
.stp b{display:block;font-size:15.5px;font-weight:700}
.stp p{font-size:13.5px;line-height:1.38;color:var(--muted);margin-top:3px}
</style></head><body><div class="canvas" id="shot"><div class="card lane"><div class="lh"><span class="ico lhi"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5.5" width="18" height="13" rx="2"/><path d="m3.8 7 8.2 6 8.2-6"/></svg></span><div><h3>Digital rewards</h3><p>Arrive by email</p></div></div><div class="steps n3"><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5.5" width="18" height="13" rx="2"/><path d="m3.8 7 8.2 6 8.2-6"/></svg></span><b>Email</b><p>To the address on your Kickstarter account</p></div><span class="ar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="12" rx="1.6"/><path d="M2 19.5h20"/><path d="M8 8.5h8M8 11.5h5"/></svg></span><b>Library access</b><p>Within about 14 days of a successful campaign</p></div><span class="ar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6.5C9.5 5 7 4.6 3.5 5v13c3.5-.4 6 0 8.5 1.5 2.5-1.5 5-1.9 8.5-1.5V5c-3.5-.4-6 0-8.5 1.5z"/><path d="M12 6.5v13"/></svg></span><b>eBook</b><p>January 2027</p></div></div></div><div class="card lane"><div class="lh"><span class="ico lhi"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 7.5 12 3.5l8.5 4v9L12 20.5l-8.5-4z"/><path d="M3.5 7.5 12 11.5l8.5-4M12 11.5v9"/></svg></span><div><h3>Signed paperbacks</h3><p>Ship in February 2027</p></div></div><div class="steps n4"><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="M8.5 9.5h7M8.5 13h7M8.5 16.5h4"/></svg></span><b>Survey</b><p>Kickstarter’s Pledge Manager asks for your address and charges shipping and any taxes</p></div><span class="ar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 9V4h10v5"/><rect x="3.5" y="9" width="17" height="8" rx="2"/><path d="M7 14h10v6H7z"/><path d="M17 12h.01"/></svg></span><b>Printing</b></div><span class="ar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20c2.5 0 3.5-2 5-2s2 1.5 3.5 1.5"/><path d="m14 4.5 5.5 5.5L11 18.5l-6 1.5 1.5-6z"/><path d="m12.5 6 5.5 5.5"/></svg></span><b>Signing</b><p>January 2027</p></div><span class="ar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span><div class="stp"><span class="ico ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5h11v9h-11z"/><path d="M13.5 9.5h4l3 3.5v2.5h-7"/><circle cx="6.5" cy="17" r="1.8"/><circle cx="17" cy="17" r="1.8"/></svg></span><b>Shipping</b><p>February 2027</p></div></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">👀</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Early readers</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,32 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.q{display:flex;gap:18px;align-items:center;padding:18px 20px}
.q+.q{margin-top:12px}
.qp{width:84px;height:84px;border-radius:999px;flex:none}
.qp .ph{border-width:2px;gap:2px}.qp .ph svg{width:20px;height:20px}.qp .ph b{font-size:12px}.qp .ph small{font-size:10px}
.qq{font-family:var(--display);font-style:italic;font-size:21px;letter-spacing:-.01em;color:#8d8579;line-height:1.3}
.who{font-size:14.5px;color:var(--muted);margin-top:6px}
.who b{color:var(--ink)}
</style></head><body><div class="canvas" id="shot"><div class="card q"><div class="slot qp" data-src="../assets/reader-1" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>400 × 400</small></div></div><div class="qt"><p class="qq">“Early reader quote 1 goes here.”</p><p class="who"><b>Name goes here</b> · Role and firm</p></div></div><div class="card q"><div class="slot qp" data-src="../assets/reader-2" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>400 × 400</small></div></div><div class="qt"><p class="qq">“Early reader quote 2 goes here.”</p><p class="who"><b>Name goes here</b> · Role and firm</p></div></div><div class="card q"><div class="slot qp" data-src="../assets/reader-3" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>400 × 400</small></div></div><div class="qt"><p class="qq">“Early reader quote 3 goes here.”</p><p class="who"><b>Name goes here</b> · Role and firm</p></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">✍️</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>The authors and team</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,45 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.ag{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
.au{padding:14px 14px 18px}
.ap{aspect-ratio:1/1;border-radius:10px}
.an{font-family:var(--display);font-weight:600;font-size:22px;letter-spacing:-.02em;margin-top:12px;color:#8d8579}
.ar{font-size:15px;color:var(--muted);margin-top:2px}
.tg{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}
.tm{padding:12px;display:flex;gap:11px;align-items:flex-start}
.tp{width:60px;height:60px;border-radius:999px;flex:none}
.tp .ph{gap:1px}.tp .ph svg{width:16px;height:16px}.tp .ph b{font-size:11px}.tp .ph small{display:none}
.tn{font-weight:700;font-size:14.5px;line-height:1.25}
.th{font-family:var(--mono);font-size:12px;color:var(--pine);margin-top:2px}
.tr{font-size:13px;line-height:1.35;color:var(--muted);margin-top:3px}
</style></head><body><div class="canvas" id="shot">
<p class="spec">The authors</p>
<div class="ag">
<div class="card au"><div class="slot ap" data-src="../assets/author-1" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Author photo goes here</b><small>800 × 800</small></div></div><p class="an">Author name</p><p class="ar">Role in the book</p></div>
<div class="card au"><div class="slot ap" data-src="../assets/author-2" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Author photo goes here</b><small>800 × 800</small></div></div><p class="an">Author name</p><p class="ar">Role in the book</p></div>
</div>
<p class="spec" style="margin-top:22px">The team</p>
<div class="tg"><div class="card tm"><div class="slot tp" data-src="../assets/team-thy" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>800 × 800</small></div></div><div><p class="tn">Name goes here</p><p class="th">@THY</p><p class="tr">Author and Retreat host</p></div></div><div class="card tm"><div class="slot tp" data-src="../assets/team-lfg" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>800 × 800</small></div></div><div><p class="tn">Name goes here</p><p class="th">@LFG</p><p class="tr">Creative lead for design, website, campaign page, and video</p></div></div><div class="card tm"><div class="slot tp" data-src="../assets/team-ceo" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>800 × 800</small></div></div><div><p class="tn">Name goes here</p><p class="th">@CEO</p><p class="tr">Business strategy and partnerships</p></div></div><div class="card tm"><div class="slot tp" data-src="../assets/team-gtm" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>800 × 800</small></div></div><div><p class="tn">Name goes here</p><p class="th">@GTM</p><p class="tr">Payments, email, and technical setup</p></div></div><div class="card tm"><div class="slot tp" data-src="../assets/team-osinachi-patrick" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>800 × 800</small></div></div><div><p class="tn">Osinachi Patrick</p><p class="tr">Product lead, AiBook.fun</p></div></div><div class="card tm"><div class="slot tp" data-src="../assets/team-academy-lead" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Photo</b><small>800 × 800</small></div></div><div><p class="tn">Name goes here</p><p class="tr">Academy BootCamp lead</p></div></div></div>
</div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>.canvas{width:400px;height:400px;padding:0}.hs{position:absolute;inset:0}.hs .ph{border-radius:0;border-width:3px}.hs .ph b{font-size:20px}.hs .ph small{font-size:14px}.hs .ph svg{width:44px;height:44px}</style></head><body><div class="canvas" id="shot"><div class="slot hs" data-src="../assets/headshot-template" style=""><div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8h3l1.6-2.5h6.8L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13.2" r="3.4"/></svg><b>Author photo goes here</b><small>800 × 800 px · square</small></div></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">🙏</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>Special thanks</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style></style></head><body><div class="hdr" id="shot"><div class="tabs"><span></span><span></span><span></span><span></span><span></span></div><div class="mark"><svg viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="17.5" stroke="#efe9db" stroke-width="1" opacity="0.7"/><path d="M20 5 L22.6 20 L20 35 L17.4 20 Z" fill="#efe9db"/><path d="M5 20 L20 17.4 L35 20 L20 22.6 Z" fill="#efe9db" opacity="0.55"/></svg></div><div class="badge"><span class="emoji">📣</span></div><div style="min-width:0;flex:1"><div class="kicker">We Own AI — The New American Dream</div><h1 data-fit>How to help</h1></div><div class="rule"></div></div>
<script>
window.__ready = (async () => {
let empty = 0;
for (const s of document.querySelectorAll('.slot[data-src]')) {
let ok = false;
for (const ext of ['jpg', 'jpeg', 'png', 'webp']) {
const url = s.dataset.src + '.' + ext;
ok = await new Promise(r => { const i = new Image(); i.onload = () => r(true); i.onerror = () => r(false); i.src = url; });
if (ok) { s.style.backgroundImage = 'url("' + url + '")'; s.classList.add('filled'); break; }
}
if (!ok) empty++;
}
await document.fonts.ready;
for (const el of document.querySelectorAll('[data-fit]')) {
let fs = parseFloat(getComputedStyle(el).fontSize);
const max = el.dataset.fit ? parseFloat(el.dataset.fit) : el.parentElement.clientWidth;
while (el.scrollWidth > max && fs > 10) { fs -= 0.5; el.style.fontSize = fs + 'px'; }
}
if (window.afterLayout) await window.afterLayout();
return empty;
})();
</script></body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,156 @@
<!doctype html>
<html lang="en" class="js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Work the listing without leaking the client · AiBook.fun</title>
<!-- Static replica of marketing-site/pages/read/real-estate.jsx (WeOwnAI/aibook-fun, main),
using the repo's own globals.css and the sample chapter data in data/sample-chapter.js.
Used only to render the device screens for the Kickstarter story mockups. -->
<link rel="stylesheet" href="globals.css">
<style>
/* screenshot helpers only */
.reader-nav .nav-in, .wrap.reader-wide { max-width: min(1520px, calc(100vw - 40px)); }
</style>
</head>
<body>
<nav class="nav reader-nav scrolled">
<div class="nav-in">
<a class="logo" href="#">AiBook<b>.</b><span class="dot">fun</span></a>
<div class="nav-links reader-links"><a href="#" class="nav-title">Library</a></div>
<a href="#" class="btn btn-primary nav-cta">Get the preview</a>
</div>
</nav>
<main>
<article class="chapter-page">
<div class="wrap reader-wide">
<nav class="crumbs" aria-label="Breadcrumb"><ol>
<li><a href="#">Library</a></li>
<li><span class="crumbs-sep">/</span><span aria-current="page">Real Estate</span></li>
</ol></nav>
</div>
<div class="wrap reader-wide chapter-shell">
<nav class="chapter-outline" aria-label="In this chapter">
<p class="outline-progress">0/2 Try-its complete</p>
<p class="outline-heading">In this chapter</p>
<div class="outline-group"><p class="outline-group-label spec">Start</p><ul><li><a href="#need"><span>What you need</span></a></li></ul></div>
<div class="outline-group"><p class="outline-group-label spec">Essay</p><ul>
<li><a href="#stakes"><span>The stakes</span></a></li>
<li><a href="#constraints"><span>Constraints you cannot ignore</span></a></li>
<li><a id="ol-practice" href="#practice"><span>How we work here</span></a></li>
<li><a href="#between"><span>The brief is half the meeting</span></a></li>
</ul></div>
<div class="outline-group"><p class="outline-group-label spec">Workflows</p><ul>
<li><a href="#re-01"><span>RE-01 · Market brief</span></a></li>
<li><a href="#re-02"><span>RE-02 · Listing copy</span></a></li>
</ul></div>
<div class="outline-group"><p class="outline-group-label spec">End</p><ul>
<li><a href="#further"><span>Further workflows</span></a></li>
<li><a href="#sources"><span>Sources</span></a></li>
</ul></div>
</nav>
<div class="chapter-col">
<p class="spec chapter-kicker">Real Estate · 2026 edition</p>
<h1 class="chapter-title">Work the listing without leaking the client</h1>
<p class="chapter-by">Founding library · sample</p>
<details class="chapter-outline-mobile"><summary>In this chapter</summary></details>
<p class="chapter-promise">In this chapter you run two jobs a realtor actually has this week, on sample data, without putting a client file in a public model.</p>
<section class="chapter-kit" id="need">
<h2 class="chapter-kit-title">What you need</h2>
<p class="chapter-kit-lede">2 Try-its on sample data. Nothing from a live client file.</p>
<dl class="kit-grid">
<div><dt>Tools</dt><dd><ul><li>brief CLI (local redaction)</li><li>Claude</li><li>listing-copy CLI</li></ul></dd></div>
<div><dt>Files</dt><dd><ul><li>sample_listings.csv</li><li>market-1pager template</li><li>sample_comps.json</li><li>listing-voice template</li></ul></dd></div>
<div><dt>Model</dt><dd><ul><li>Claude</li></ul></dd></div>
<div><dt>Try-its</dt><dd><ul><li>RE-01 · ~15 min · ≈ $0.03</li><li>RE-02 · ~12 min · ≈ $0.02</li></ul></dd></div>
</dl>
</section>
<section class="essay" id="stakes"><h2>The stakes</h2>
<p>A listing that stalls because the market brief is late is a lost weekend. A listing that leaks because you pasted the client sheet into a public model is a lost license. Real estate work runs on both clocks: the showing at 4pm, and the file that must never leave the office.</p>
<p>The failure is not that AI sits unused. It is a one-pager that is late, generic, or built from a client name that should not have been in the prompt. Buyers notice fluff. Brokers notice a privacy incident. Neither is recoverable in the same week.</p>
</section>
<section class="essay" id="constraints"><h2>Constraints you cannot ignore</h2>
<p>You already work in MLS exports, a CRM, and a phone that rings during the write-up. Done is a brief the client can read in a meeting, with numbers you can stand behind, reviewed by a human. Public models are useful for draft and classify. They are not a filing cabinet.</p>
<p>Owner names, phones, exact addresses, and showing lists stay on your machine. Comps, medians, and days on market can travel, once identifiers are stripped. If a field could pick one household out of the block, it does not go in the prompt.</p>
</section>
<section class="essay" id="practice"><h2>How we work here</h2>
<p>Redact locally. Prompt with a sample or a stripped file. Review against a checklist. Share the artifact, not the workflow text. The first job this week is the neighborhood market brief. Run it on sample comps before you touch a live listing.</p>
</section>
<p class="spec workflow-kicker">Workflow 1 of 2 in this chapter</p>
<article id="re-01" class="recipe recipe-nested">
<div class="recipe-top">
<span class="recipe-id">Workflow 1 of 2 · <b>RE-01</b> · v1.2 · ~15 min · ≈ $0.03</span>
<span class="recipe-vert">Real Estate</span>
</div>
<div class="recipe-body">
<h3 class="recipe-title">Draft a property-market brief with an AI agent without leaking client data.</h3>
<dl class="workflow-kit">
<div><dt>You run</dt><dd>Listing agent</dd></div>
<div><dt>Tools</dt><dd>brief CLI (local redaction) · Claude</dd></div>
<div><dt>Files</dt><dd>sample_listings.csv · market-1pager template</dd></div>
<div><dt>Model</dt><dd>Claude</dd></div>
<div><dt>Time</dt><dd>~15 min</dd></div>
<div><dt>Cost</dt><dd>≈ $0.03</dd></div>
<div><dt>You produce</dt><dd>One-pager market brief</dd></div>
</dl>
<nav class="recipe-jump"><a>Kit</a><a>Problem</a><a>Actors</a><a>Setup</a><a>Safeguards</a><a>Method</a><a>Evidence</a><a>Try-it</a></nav>
<div class="row" id="re-01-problem"><h4 class="k">Problem</h4><div class="v">You need a neighborhood market brief for a client meeting this week. The listing sheet and client notes cannot go into a public model. <span class="sub">Job: turn messy local data into a credible one-pager in minutes, safely.</span></div></div>
<div class="row"><h4 class="k">Actors</h4><div class="v"><ul class="plain-list"><li>Listing agent runs it.</li><li>Client receives the brief.</li><li>Model may draft and classify. A human must review before send.</li></ul></div></div>
<div class="row"><h4 class="k">Setup</h4><div class="v"><ul class="plain-list"><li>Tools: local redaction script (brief CLI), Claude or your usual model.</li><li>Sample files: sample_listings.csv, market-1pager template.</li><li>Cost: about $0.03 on the sample run (2,140 tokens).</li></ul></div></div>
<div class="row"><h4 class="k">Safeguards</h4><div class="v">
<div class="guard"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z" stroke="#1C5D4C" stroke-width="1.3" stroke-linejoin="round"/><path d="m5.6 8 1.7 1.7L10.6 6" stroke="#1C5D4C" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg><span>Redaction runs on your machine. Raw client data never reaches the model.</span></div>
<div class="guard"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z" stroke="#1C5D4C" stroke-width="1.3" stroke-linejoin="round"/><path d="m5.6 8 1.7 1.7L10.6 6" stroke="#1C5D4C" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg><span>A human checklist is required before the brief is shared. The checklist is part of the recipe.</span></div>
<div class="guard"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z" stroke="#1C5D4C" stroke-width="1.3" stroke-linejoin="round"/><path d="m5.6 8 1.7 1.7L10.6 6" stroke="#1C5D4C" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg><span>Stop if identifiers remain after the strip report. Do not prompt until owner_name, phone, and exact_address are gone.</span></div>
</div></div>
<div class="row"><h4 class="k">Method</h4><div class="v"><ol class="steps">
<li><div class="step-copy">Strip identifiers from the source data locally (a provided script does this).</div></li>
<li><div class="step-copy">Prompt the agent with the anonymized sample and a fixed brief template.
<div class="term"><div class="term-bar"><i></i><i></i><i></i><span>agent — property-brief · sample dataset (non-confidential)</span></div><pre><span class="c-dim">$ </span><span class="c-user">brief</span> --input <span class="c-key">sample_listings.csv</span> --template market-1pager
<span class="c-dim">✓ redacted 3 fields (owner_name, phone, exact_address)
✓ 41 comps in radius · median $/sqft computed
</span>
<span class="c-ok">MARKET BRIEF — Maple Heights (sample)
</span> · Median list: $420/sqft (▲ 4.1% QoQ)
· Days on market: 22 (was 31)
· Inventory: 1.8 mo — seller-leaning
· Read: price to comp, expect &lt;2 wks at ask
<span class="c-dim"># cost this run: $0.03 · model: claude · 2,140 tokens</span></pre></div></div></li>
<li><div class="step-copy">Review the draft against the checklist before anything leaves your machine.</div></li>
</ol></div></div>
<div class="row"><h4 class="k">Evidence</h4><div class="v"><p class="evidence-p">A one-pager brief on Maple Heights (sample), ready for a meeting once a human signs the checklist.</p><p class="evidence-p">Sample metrics from that run: median $420/sqft, 22 days on market, 1.8 months of inventory.</p></div></div>
<div class="row" id="re-01-tryit"><h4 class="k">Try-it</h4><div class="v">
<p>About 15 minutes. Use the included sample_listings.csv (no live client file). Produce the market-1pager brief and walk the human checklist.</p>
<p class="sub-line">Mark complete when you have a brief you would send after review. <a class="inline-sample">sample_listings.csv</a></p>
<p><span class="chip">◷ cost to try ≈ $0.03</span></p>
<div class="tryit-actions"><button type="button" class="btn btn-ghost">Mark complete</button></div>
</div></div>
<div class="row" id="re-01-share"><h4 class="k">Share</h4><div class="v"><p>Copy a short result line. Share the outcome, not the recipe text.</p><div class="tryit-actions"><button type="button" class="btn btn-ghost">Copy result line</button></div></div></div>
<div class="row" id="re-01-record"><h4 class="k">Record</h4><div class="v"><div class="recipe-record">
<span>Author: Founding library · sample</span>
<span>Sources: Sample MLS packet (non-confidential); market-1pager template shipped with the recipe.</span>
<span id="re-01-updated">Last updated: 31 Aug 2026</span>
<span><a>Open on paper</a></span>
</div></div></div>
</div>
</article>
</div>
<aside class="chapter-rail">
<p class="spec">Workflows in this chapter</p>
<ol class="rail-list">
<li><a class="rail-card"><span class="rail-kicker">1 of 2 · RE-01</span><span class="rail-title">Market brief</span><span class="rail-meta">~15 min · ≈ $0.03</span><span class="rail-kit">brief CLI (local redaction) · Claude</span><span class="rail-kit rail-out">One-pager market brief</span></a></li>
<li><a class="rail-card"><span class="rail-kicker">2 of 2 · RE-02</span><span class="rail-title">Listing copy</span><span class="rail-meta">~12 min · ≈ $0.02</span><span class="rail-kit">listing-copy CLI · Claude</span><span class="rail-kit rail-out">180-word listing draft</span></a></li>
</ol>
<a class="rail-skip">Skip to the first Try-it</a>
</aside>
</div>
</article>
</main>
</body>
</html>
@@ -0,0 +1,886 @@
/* Copied from WeOwnAI/aibook-fun marketing-site/styles/globals.css (main).
Changes for static rendering: Tailwind import replaced by a minimal preflight; @theme block -> :root. */
*,::before,::after{box-sizing:border-box;border:0 solid;margin:0;padding:0}
html{line-height:1.5}
h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}
ol,ul,menu{list-style:none}
img,svg{display:block;vertical-align:middle}
button{font:inherit;color:inherit;background:transparent}
/* ============================================================
AiBook.fun — "field guide" design system
Tokens live in @theme so they're available BOTH as CSS vars
(var(--color-pine)) and as Tailwind utilities (bg-pine, font-display).
============================================================ */
:root {
--color-paper: #fcfbf8; /* warm near-white ground (chosen, not cream cliché) */
--color-paper-2: #f6f4ee; /* recessed band */
--color-card: #ffffff; /* raised surfaces */
--color-ink: #1a1613; /* warm near-black text */
--color-muted: #6e675e; /* warm stone secondary */
--color-line: #e7e1d7; /* warm hairline */
--color-line-2: #d8d1c4; /* stronger hairline */
--color-pine: #1c5d4c; /* THE accent — field-guide green */
--color-pine-deep: #12463a;
--color-pine-tint: rgba(28, 93, 76, 0.1);
--color-pine-wash: rgba(28, 93, 76, 0.06);
--color-amber: #b4791f;
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
--font-body: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
}
:root {
--sh-sm: 0 1px 2px rgba(26, 22, 19, 0.05), 0 1px 3px rgba(26, 22, 19, 0.04);
--sh-md: 0 4px 10px rgba(26, 22, 19, 0.06), 0 2px 4px rgba(26, 22, 19, 0.04);
--sh-lg: 0 18px 40px -12px rgba(26, 22, 19, 0.12), 0 6px 14px rgba(26, 22, 19, 0.05);
--ease: cubic-bezier(0.16, 1, 0.3, 1);
--wrap: 1080px;
}
/* ============ BASE ============ */
html {
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
}
body {
background: var(--color-paper);
color: var(--color-ink);
font-family: var(--font-body);
font-size: 17px;
line-height: 1.65;
letter-spacing: -0.005em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
::selection {
background: var(--color-pine-tint);
color: var(--color-pine-deep);
}
a { color: inherit; text-decoration: none; }
h1, h2, h3 { text-wrap: balance; margin: 0; }
@media (max-width: 720px) { body { font-size: 16px; } }
/* ============ LAYOUT PRIMITIVES ============ */
.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }
.section { padding: 104px 0; }
@media (max-width: 900px) { .section { padding: 84px 0; } }
@media (max-width: 600px) {
.wrap { padding: 0 20px; }
.section { padding: 64px 0; }
}
.spec {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--color-pine);
}
.serif-it { font-family: var(--font-display); font-style: italic; font-weight: 500; }
.hl {
background: linear-gradient(transparent 62%, var(--color-pine-tint) 62%);
padding: 0 0.06em;
}
/* ============ BUTTONS ============ */
.btn {
display: inline-flex; align-items: center; gap: 0.5em;
font-family: var(--font-body); font-weight: 700; font-size: 15px; letter-spacing: -0.01em;
border-radius: 999px; padding: 15px 26px; cursor: pointer; border: 1px solid transparent;
transition: transform 0.14s var(--ease), box-shadow 0.14s var(--ease), background 0.18s var(--ease);
}
.btn-primary { background: var(--color-pine); color: #f7fbf9; box-shadow: 0 6px 18px -6px rgba(28, 93, 76, 0.5); }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 12px 26px -8px rgba(28, 93, 76, 0.55); }
.btn-primary:active { transform: translateY(0) scale(0.99); }
.btn-ghost { background: var(--color-card); color: var(--color-ink); border-color: var(--color-line-2); }
.btn-ghost:hover { transform: translateY(-1px); box-shadow: var(--sh-md); }
.btn:focus-visible { outline: 2px solid var(--color-pine); outline-offset: 3px; }
/* ============ REVEAL (only hides when JS can un-hide) ============ */
.js .reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.js .reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
.js .reveal { opacity: 1; transform: none; }
html { scroll-behavior: auto; }
* { animation: none !important; transition: none !important; }
}
/* ============ NAV ============ */
.nav {
position: sticky; top: 0; z-index: 50;
background: var(--color-paper);
border-bottom: 1px solid transparent; transition: border-color 0.3s, box-shadow 0.3s;
}
.nav.scrolled { border-bottom-color: var(--color-line); box-shadow: var(--sh-sm); }
.nav-in {
position: relative;
max-width: var(--wrap); margin: 0 auto; padding: 0 28px; height: 68px;
display: flex; align-items: center; justify-content: space-between; gap: 20px;
}
.logo {
display: flex; align-items: baseline; gap: 2px;
font-family: var(--font-display); font-weight: 900; font-size: 21px; letter-spacing: -0.02em;
flex: none; position: relative; z-index: 1;
}
.logo b { color: var(--color-pine); }
.logo .dot { color: var(--color-muted); font-weight: 500; }
.nav-links {
display: flex; align-items: center; gap: 30px;
font-size: 14.5px; font-weight: 600; color: var(--color-muted);
position: absolute; left: 50%; transform: translateX(-50%);
}
.nav-links a { transition: color 0.18s; }
.nav-links a:hover { color: var(--color-ink); }
.nav-actions {
display: flex; align-items: center; gap: 10px;
flex: none; margin-left: auto; position: relative; z-index: 1;
}
.nav-cta { font-size: 14px; padding: 10px 18px; }
/* mobile hamburger toggle (hidden on desktop) */
.nav-toggle {
display: none; width: 44px; height: 44px; align-items: center; justify-content: center;
padding: 0; border: 1px solid var(--color-line-2); border-radius: 12px;
background: var(--color-card); cursor: pointer; flex: none;
}
.nav-toggle:focus-visible { outline: 2px solid var(--color-pine); outline-offset: 3px; }
.nav-toggle .bars, .nav-toggle .bars::before, .nav-toggle .bars::after {
content: ""; display: block; width: 18px; height: 2px; border-radius: 2px;
background: var(--color-ink); transition: transform 0.25s var(--ease), opacity 0.2s;
}
.nav-toggle .bars { position: relative; }
.nav-toggle .bars::before { position: absolute; left: 0; top: -6px; }
.nav-toggle .bars::after { position: absolute; left: 0; top: 6px; }
.nav.open .nav-toggle .bars { background: transparent; }
.nav.open .nav-toggle .bars::before { transform: translateY(6px) rotate(45deg); }
.nav.open .nav-toggle .bars::after { transform: translateY(-6px) rotate(-45deg); }
/* mobile dropdown menu (rendered on desktop as display:none) */
.nav-menu { display: none; }
@media (max-width: 860px) {
.nav-links { display: none; }
.nav-actions { display: none; }
.nav-cta { display: none; } /* CTA moves into the menu */
.nav-toggle { display: inline-flex; }
.nav-menu {
display: flex; flex-direction: column; gap: 2px;
position: absolute; top: 100%; left: 0; right: 0;
background: var(--color-paper); border-bottom: 1px solid var(--color-line);
box-shadow: var(--sh-lg); padding: 10px 20px 20px;
opacity: 0; visibility: hidden; transform: translateY(-8px); pointer-events: none;
transition: opacity 0.2s var(--ease), transform 0.25s var(--ease), visibility 0.2s;
}
.nav.open .nav-menu { opacity: 1; visibility: visible; transform: none; pointer-events: auto; }
.nav-menu a:not(.btn) {
font-family: var(--font-body); font-weight: 600; font-size: 1.05rem; color: var(--color-ink);
padding: 15px 6px; border-bottom: 1px solid var(--color-line);
}
.nav-menu a:not(.btn):hover { color: var(--color-pine); }
.nav-menu .btn { margin-top: 14px; justify-content: center; width: 100%; }
}
@media (max-width: 600px) { .nav-in { padding: 0 20px; } }
/* ============ HERO ============ */
.hero { padding: 78px 0 72px; position: relative; overflow: hidden; }
.hero-tabs { position: absolute; top: 0; right: max(28px, calc(50% - var(--wrap) / 2 + 28px)); display: flex; gap: 6px; opacity: 0.5; }
.hero-tabs span { width: 34px; height: 8px; border-radius: 0 0 4px 4px; background: var(--color-line-2); }
.hero-tabs span:first-child { background: var(--color-pine); opacity: 0.85; }
.eyebrow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 26px; }
.eyebrow .tick { width: 7px; height: 7px; border-radius: 999px; background: var(--color-pine); flex: none; }
.hero h1 { font-family: var(--font-display); font-weight: 900; font-size: clamp(2.7rem, 6.4vw, 5.1rem); line-height: 0.98; letter-spacing: -0.028em; max-width: 15ch; margin-bottom: 26px; }
.hero-sub { font-size: clamp(1.1rem, 1.7vw, 1.35rem); color: var(--color-muted); line-height: 1.62; max-width: 33rem; margin-bottom: 16px; }
.hero-form { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 30px; max-width: 34rem; }
.field {
flex: 1; min-width: 220px; font-family: var(--font-body); font-size: 15.5px; color: var(--color-ink);
background: var(--color-card); border: 1px solid var(--color-line-2); border-radius: 999px; padding: 15px 22px;
transition: border-color 0.18s, box-shadow 0.18s;
}
.field::placeholder { color: #a49c90; }
.field:focus { outline: none; border-color: var(--color-pine); box-shadow: 0 0 0 4px var(--color-pine-wash); }
.hero-note { font-family: var(--font-mono); font-size: 12px; color: var(--color-muted); margin-top: 16px; }
.hero-note b { color: var(--color-pine); font-weight: 600; }
@media (max-width: 720px) { .hero-tabs { display: none; } .hero { padding: 56px 0 56px; } }
@media (max-width: 600px) { .spec { letter-spacing: 0.1em; font-size: 11px; } }
/* ============ HERO TWO-COLUMN + 3D BOOK ============ */
.hero-grid { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 72px; align-items: center; }
.hero-visual { display: flex; justify-content: center; order: 2; } /* book on the right */
.hero-copy { min-width: 0; order: 1; } /* text on the left */
.book3d { --bw: 300px; --bh: 420px; --bt: 54px; --band-h: 58px; position: relative; perspective: 2000px; flex: none; padding: 6px 0 40px; }
.book3d__inner {
position: relative; width: var(--bw); height: var(--bh); margin: 0 auto;
transform-style: preserve-3d; transform: rotateX(7deg) rotateY(-34deg);
animation: book-float 8s ease-in-out infinite; will-change: transform;
}
.book3d:hover .book3d__inner { transform: rotateX(5deg) rotateY(-22deg); transition: transform 0.7s var(--ease); animation-play-state: paused; }
@keyframes book-float {
0%, 100% { transform: rotateX(7deg) rotateY(-34deg) translateY(0); }
50% { transform: rotateX(5deg) rotateY(-30deg) translateY(-12px); }
}
.book3d__face { position: absolute; top: 0; left: 0; }
.book3d__cover, .book3d__back { width: var(--bw); height: var(--bh); }
.book3d__cover {
transform: translateZ(calc(var(--bt) / 2)); border-radius: 3px 8px 8px 3px; overflow: hidden;
background: linear-gradient(125deg, #23745f, #1a5748 48%, #123f34);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07), inset 8px 0 16px -8px rgba(0, 0, 0, 0.5), 0 32px 46px -22px rgba(18, 63, 52, 0.55);
padding: 32px 27px calc(var(--band-h) * 1.48); display: flex; flex-direction: column;
}
.book3d__back { transform: rotateY(180deg) translateZ(calc(var(--bt) / 2)); border-radius: 8px 3px 3px 8px; background: #0f3b30; }
.book3d__pages {
width: var(--bt); height: calc(var(--bh) - 6px); top: 3px; left: calc((var(--bw) - var(--bt)) / 2);
transform: rotateY(90deg) translateZ(calc(var(--bw) / 2));
background: repeating-linear-gradient(to left, #efe9db 0, #efe9db 1px, #d6cdba 1px, #d6cdba 3px);
}
.book3d__spine {
width: var(--bt); height: var(--bh); left: calc((var(--bw) - var(--bt)) / 2);
transform: rotateY(-90deg) translateZ(calc(var(--bw) / 2));
background: linear-gradient(#174f41, #0f3b30); display: flex; align-items: center; justify-content: center;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.book3d__spine span {
writing-mode: vertical-rl; transform: rotate(180deg); white-space: nowrap;
font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(239, 233, 219, 0.72);
}
.book3d__top {
width: var(--bw); height: var(--bt); top: calc((var(--bh) - var(--bt)) / 2);
transform: rotateX(90deg) translateZ(calc(var(--bh) / 2));
background: repeating-linear-gradient(to right, #efe9db 0, #efe9db 1px, #d6cdba 1px, #d6cdba 3px);
}
.book3d__kicker { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(239, 233, 219, 0.62); }
.book3d__title { font-family: var(--font-display); font-weight: 600; font-size: 2.25rem; line-height: 1.02; letter-spacing: -0.02em; color: #f3eee2; margin-top: 16px; }
.book3d__line { width: 34px; height: 2px; background: rgba(239, 233, 219, 0.5); margin: 15px 0; }
.book3d__sub { font-family: var(--font-body); font-size: 13px; color: rgba(239, 233, 219, 0.68); line-height: 1.45; max-width: 16ch; }
.book3d__foot { display: flex; align-items: center; gap: 10px; margin-top: auto; }
.book3d__mark { width: 30px; height: 30px; flex: none; }
/* Belly band: a promotional strip wrapped across the lower cover, publisher-
style, rather than redesigning the whole face around the source graphic.
The <img> is width-locked to the band and shifted up so the crop stays
full-width (legible "AMERICAN" wordmark) and only trims top/bottom. */
.book3d__band {
position: absolute; left: 0; right: 0; bottom: calc(var(--band-h) * 0.31);
height: var(--band-h); overflow: hidden;
box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.1) inset, 0 3px 10px rgba(0, 0, 0, 0.3);
}
.book3d__band img {
position: absolute; left: 0; top: -78%; width: 100%; height: auto; display: block;
}
.book3d__brand { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em; color: rgba(239, 233, 219, 0.85); }
.book3d::after {
content: ""; position: absolute; left: 50%; bottom: 6px; width: 60%; height: 24px; transform: translateX(-50%);
background: radial-gradient(ellipse, rgba(18, 60, 50, 0.3), transparent 70%); filter: blur(7px); z-index: -1;
}
@media (max-width: 900px) {
.hero-grid { grid-template-columns: 1fr; gap: 44px; }
.hero-visual { order: 2; }
.hero-copy { order: 1; }
.book3d { --bw: 256px; --bh: 358px; --bt: 48px; --band-h: 50px; }
}
@media (max-width: 420px) { .book3d { --bw: 210px; --bh: 294px; --bt: 40px; --band-h: 40px; } }
@media (prefers-reduced-motion: reduce) { .book3d__inner { animation: none; } }
/* ============ FIELD NOTES (even gutters, aligned to container edges) ============ */
.notes { background: var(--color-paper-2); border-top: 1px solid var(--color-line); border-bottom: 1px solid var(--color-line); }
.notes-grid { display: grid; grid-template-columns: repeat(3, 1fr); }
.note { padding: 48px 36px; border-left: 1px solid var(--color-line); }
.note:first-child { border-left: none; padding-left: 0; }
.note:last-child { padding-right: 0; }
.note .n { font-family: var(--font-mono); font-size: 12px; color: var(--color-pine); letter-spacing: 0.12em; margin-bottom: 16px; }
.note h3 { font-family: var(--font-display); font-weight: 600; font-size: 1.35rem; letter-spacing: -0.02em; margin-bottom: 12px; line-height: 1.15; }
.note p { font-size: 15px; color: var(--color-muted); line-height: 1.6; }
@media (max-width: 760px) {
.notes-grid { grid-template-columns: 1fr; }
.note { padding: 32px 0; border-left: none; border-top: 1px solid var(--color-line); }
.note:first-child { border-top: none; padding-top: 0; }
.note:last-child { padding-bottom: 0; }
}
/* ============ RECIPE (value centerpiece) ============ */
.recipe-head { max-width: 40rem; margin-bottom: 44px; }
.recipe-head h2 { font-family: var(--font-display); font-weight: 900; font-size: clamp(2rem, 4.3vw, 3.3rem); letter-spacing: -0.025em; line-height: 1.15; margin: 16px 0 18px; }
.recipe-head p { color: var(--color-muted); font-size: 1.08rem; }
.recipe { background: var(--color-card); border: 1px solid var(--color-line); border-radius: 20px; box-shadow: var(--sh-lg); overflow: hidden; }
.recipe-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 20px 28px; border-bottom: 1px solid var(--color-line); background: linear-gradient(var(--color-card), var(--color-paper)); }
.recipe-id { font-family: var(--font-mono); font-size: 12.5px; color: var(--color-muted); letter-spacing: 0.04em; }
.recipe-id b { color: var(--color-pine); }
.recipe-vert { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--color-pine); background: var(--color-pine-wash); border: 1px solid var(--color-pine-tint); padding: 5px 11px; border-radius: 999px; white-space: nowrap; }
.recipe-body { padding: 34px 28px; }
.recipe-title { font-family: var(--font-display); font-weight: 600; font-size: 1.55rem; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 26px; max-width: 34rem; }
.row { display: grid; grid-template-columns: 118px 1fr; gap: 20px; padding: 18px 0; border-top: 1px dashed var(--color-line-2); }
.row:first-of-type { border-top: none; padding-top: 0; }
.row .k { font-family: var(--font-mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-pine); padding-top: 3px; }
.row .v { font-size: 15.5px; color: var(--color-ink); line-height: 1.6; }
.row .v .sub { color: var(--color-muted); font-size: 14.5px; }
/* Let grid items shrink below their content width so the terminal's own
overflow-x:auto engages (scrolls) instead of the row expanding + being clipped. */
.row > * { min-width: 0; }
.term { max-width: 100%; }
.steps { counter-reset: s; display: flex; flex-direction: column; gap: 11px; margin: 0; padding: 0; }
.steps li { list-style: none; display: flex; gap: 12px; font-size: 15px; color: var(--color-ink); }
.steps li::before {
counter-increment: s; content: counter(s);
font-family: var(--font-mono); font-size: 11px; font-weight: 600; color: var(--color-pine);
background: var(--color-pine-wash); width: 22px; height: 22px; border-radius: 6px;
display: grid; place-items: center; flex: none; margin-top: 1px;
}
.term { margin-top: 14px; background: #14110e; border-radius: 12px; overflow: hidden; border: 1px solid #2a241d; }
.term-bar { display: flex; align-items: center; gap: 7px; padding: 11px 14px; border-bottom: 1px solid #2a241d; }
.term-bar i { width: 11px; height: 11px; border-radius: 999px; background: #3a342b; display: block; }
.term-bar span { font-family: var(--font-mono); font-size: 11px; color: #8a8175; margin-left: 8px; letter-spacing: 0.03em; }
.term pre { margin: 0; padding: 16px 18px; overflow-x: auto; font-family: var(--font-mono); font-size: 12.5px; line-height: 1.7; color: #d8d0c4; }
.term .c-user { color: #7fc8a9; }
.term .c-key { color: #e0b96b; }
.term .c-dim { color: #8a8175; }
.term .c-ok { color: #7fc8a9; }
.chip { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: 12px; color: var(--color-amber); background: rgba(180, 121, 31, 0.08); border: 1px solid rgba(180, 121, 31, 0.2); padding: 4px 10px; border-radius: 999px; }
.guard { display: flex; gap: 10px; align-items: flex-start; font-size: 14.5px; color: var(--color-muted); padding: 8px 0; }
.guard svg { flex: none; margin-top: 2px; }
@media (max-width: 640px) {
.row { grid-template-columns: 1fr; gap: 8px; }
.recipe-body { padding: 26px 20px; }
.recipe-top { padding: 16px 20px; }
}
/* ============ CONTENTS (table-of-contents) ============ */
.contents { background: var(--color-paper-2); border-top: 1px solid var(--color-line); border-bottom: 1px solid var(--color-line); }
.contents h2 { font-family: var(--font-display); font-weight: 900; font-size: clamp(1.9rem, 4vw, 3rem); letter-spacing: -0.025em; line-height: 1.2; margin: 14px 0 34px; max-width: 20ch; }
.toc { border-top: 1px solid var(--color-line-2); }
.toc a { display: grid; grid-template-columns: 52px 1fr auto auto; align-items: center; gap: 20px; padding: 22px 8px; border-bottom: 1px solid var(--color-line); transition: background 0.2s, padding 0.2s; }
.toc a:hover { background: var(--color-pine-wash); padding-left: 16px; }
.toc .num { font-family: var(--font-mono); font-size: 13px; color: var(--color-muted); }
.toc .name { font-family: var(--font-display); font-weight: 600; font-size: 1.32rem; letter-spacing: -0.02em; }
.toc .desc { font-size: 14px; color: var(--color-muted); justify-self: end; text-align: right; max-width: 24rem; }
.toc .arrow { color: var(--color-pine); opacity: 0; transition: opacity 0.2s, transform 0.2s; font-family: var(--font-mono); }
.toc a:hover .arrow { opacity: 1; transform: translateX(3px); }
@media (max-width: 720px) { .toc .desc { display: none; } .toc a { grid-template-columns: 40px 1fr auto; } }
/* ============ LETTER (human) ============ */
.letter { background: var(--color-pine-deep); color: #eaf1ec; position: relative; overflow: hidden; }
.letter::before { content: ""; position: absolute; inset: 0; background: radial-gradient(120% 90% at 85% 0%, rgba(255, 255, 255, 0.06), transparent 60%); pointer-events: none; }
.letter-in { max-width: 720px; margin: 0 auto; padding: 0 28px; position: relative; }
.letter .spec { color: #8fbeae; }
.letter-body { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.4rem, 2.6vw, 1.95rem); line-height: 1.42; letter-spacing: -0.01em; margin: 22px 0 30px; }
.letter-body em { font-style: italic; color: #fff; }
.letter-body .mark { background: rgba(143, 190, 174, 0.18); padding: 0 0.08em; border-radius: 2px; }
.sign { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.sign .scrawl { font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: 1.5rem; color: #fff; }
.sign .who { font-family: var(--font-mono); font-size: 12px; color: #8fbeae; letter-spacing: 0.04em; line-height: 1.5; }
@media (max-width: 600px) { .letter-in { padding: 0 20px; } }
/* ============ AUTHORS ============ */
.authors h2 { font-family: var(--font-display); font-weight: 900; font-size: clamp(1.9rem, 3.6vw, 2.7rem); letter-spacing: -0.025em; line-height: 1.2; margin: 14px 0 10px; }
.authors .lede { color: var(--color-muted); max-width: 38rem; margin-bottom: 38px; }
.auth-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.auth { background: var(--color-card); border: 1px solid var(--color-line); border-radius: 16px; padding: 24px; box-shadow: var(--sh-sm); transition: box-shadow 0.2s, transform 0.2s; }
.auth:hover { box-shadow: var(--sh-md); transform: translateY(-2px); }
.auth .mono-tile { width: 46px; height: 46px; border-radius: 11px; display: grid; place-items: center; font-family: var(--font-mono); font-weight: 600; font-size: 16px; color: var(--color-pine); background: var(--color-pine-wash); border: 1px solid var(--color-pine-tint); margin-bottom: 16px; }
.auth h3 { font-family: var(--font-display); font-weight: 600; font-size: 1.15rem; letter-spacing: -0.02em; margin-bottom: 4px; }
.auth .role { font-size: 13.5px; color: var(--color-muted); margin-bottom: 12px; }
.auth .status { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; display: inline-flex; align-items: center; gap: 6px; }
.auth .status.confirmed { color: var(--color-pine); }
.auth .status.tba { color: var(--color-muted); }
.auth .status i { width: 6px; height: 6px; border-radius: 999px; background: currentColor; display: block; }
.auth-join { display: grid; place-content: center; text-align: center; background: var(--color-pine-wash); border-style: dashed; border-color: var(--color-pine-tint); }
.auth-join p { font-size: 14px; color: var(--color-muted); max-width: 15rem; }
@media (max-width: 900px) { .auth-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .auth-grid { grid-template-columns: 1fr; } }
/* ============ EMAIL CAPTURE ============ */
.capture { background: var(--color-paper-2); border-top: 1px solid var(--color-line); border-bottom: 1px solid var(--color-line); }
.capture-in { max-width: 640px; margin: 0 auto; text-align: center; padding: 0 28px; }
.capture h2 { font-family: var(--font-display); font-weight: 900; font-size: clamp(2rem, 4vw, 3rem); letter-spacing: -0.025em; line-height: 1.15; margin: 16px 0 14px; }
.capture p { color: var(--color-muted); font-size: 1.06rem; margin-bottom: 28px; }
.capture form { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.capture .fine { font-family: var(--font-mono); font-size: 11.5px; color: var(--color-muted); margin-top: 18px; }
.ok-msg { font-family: var(--font-mono); font-size: 14px; color: var(--color-pine); }
@media (max-width: 600px) { .capture-in { padding: 0 20px; } }
/* ============ TIERS PREVIEW ============ */
.tiers-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-bottom: 34px; }
.tiers-head h2 { font-family: var(--font-display); font-weight: 900; font-size: clamp(1.9rem, 3.6vw, 2.7rem); letter-spacing: -0.025em; line-height: 1.2; margin: 12px 0 0; }
.tiers-note { font-family: var(--font-mono); font-size: 12px; color: var(--color-muted); max-width: 22rem; text-align: right; line-height: 1.6; }
.tier-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.tier { background: var(--color-card); border: 1px solid var(--color-line); border-radius: 18px; padding: 30px 26px; display: flex; flex-direction: column; transition: box-shadow 0.2s, transform 0.2s; }
.tier:hover { box-shadow: var(--sh-md); transform: translateY(-2px); }
.tier.feature { background: var(--color-pine); color: #eaf1ec; border-color: var(--color-pine); }
.tier .t-name { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 16px; }
.tier .t-price { font-family: var(--font-display); font-weight: 900; font-size: 2.4rem; letter-spacing: -0.03em; line-height: 1; margin-bottom: 6px; }
.tier .t-price small { font-family: var(--font-body); font-size: 1rem; font-weight: 600; color: var(--color-muted); }
.tier.feature .t-price small { color: #9dc4b5; }
.tier .t-unit { font-size: 13px; color: var(--color-muted); margin-bottom: 18px; }
.tier.feature .t-unit { color: #9dc4b5; }
.tier .t-desc { font-size: 14.5px; color: var(--color-muted); line-height: 1.6; }
.tier.feature .t-desc { color: #cfe0d6; }
.tier .t-tag { align-self: flex-start; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; background: #eaf1ec; color: var(--color-pine-deep); padding: 4px 10px; border-radius: 999px; margin-bottom: 16px; }
@media (max-width: 820px) { .tier-grid { grid-template-columns: 1fr; } .tiers-note { text-align: left; } }
/* ============ FAQ ============ */
.faq h2 { font-family: var(--font-display); font-weight: 900; font-size: clamp(1.9rem, 3.6vw, 2.7rem); letter-spacing: -0.025em; line-height: 1.2; margin-bottom: 34px; }
.qa { border-top: 1px solid var(--color-line); }
.qa details { border-bottom: 1px solid var(--color-line); padding: 6px 0; }
.qa summary { cursor: pointer; list-style: none; padding: 20px 0; display: flex; align-items: center; justify-content: space-between; gap: 20px; font-family: var(--font-display); font-weight: 600; font-size: 1.2rem; letter-spacing: -0.02em; }
.qa summary::-webkit-details-marker { display: none; }
.qa summary .pl { font-family: var(--font-mono); color: var(--color-pine); font-size: 1.1rem; transition: transform 0.2s var(--ease); flex: none; }
.qa details[open] summary .pl { transform: rotate(45deg); }
.qa .ans { padding: 0 40px 22px 0; color: var(--color-muted); font-size: 16px; line-height: 1.65; }
@media (max-width: 600px) { .qa .ans { padding-right: 0; } }
/* ============ FOOTER ============ */
.footer { background: var(--color-ink); color: #d8d0c4; }
.foot-in { max-width: var(--wrap); margin: 0 auto; padding: 64px 28px 48px; }
.foot-top { display: flex; justify-content: space-between; gap: 40px; flex-wrap: wrap; padding-bottom: 40px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
.foot-top .logo { color: #f7f5f0; font-size: 24px; }
.foot-top .logo b { color: #7fc8a9; }
.foot-top .logo .dot { color: #8a8175; }
.foot-blurb { max-width: 24rem; font-size: 14.5px; color: #9a9184; margin-top: 14px; line-height: 1.6; }
.foot-links { display: flex; gap: 28px; font-family: var(--font-mono); font-size: 12.5px; letter-spacing: 0.03em; color: #9a9184; flex-wrap: wrap; }
.foot-links a:hover { color: #f7f5f0; }
.colophon { padding-top: 26px; font-family: var(--font-mono); font-size: 11.5px; color: #7a7266; letter-spacing: 0.02em; line-height: 1.8; }
@media (max-width: 600px) { .foot-in { padding: 48px 20px 40px; } }
/* ============ READER DEMO ============ */
/* Layout the landing page does not already have. Reader pages use a wider wrap.
Same tokens, same ~20px cards and 999px pills. Do not restyle the homepage recipe. */
.demo-chip {
display: inline-flex; align-items: center; gap: 8px;
font-family: var(--font-mono); font-size: 11px; font-weight: 500;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--color-amber);
background: rgba(180, 121, 31, 0.08);
border: 1px solid rgba(180, 121, 31, 0.22);
padding: 5px 12px; border-radius: 999px;
margin-bottom: 22px;
}
.reader-nav .nav-in,
.reader-footer .foot-in,
.wrap.reader-wide {
max-width: min(1520px, calc(100vw - 40px));
}
.crumbs {
padding: 4px 0 18px;
font-size: 13.5px;
}
.crumbs ol {
display: flex; flex-wrap: wrap; align-items: center;
list-style: none; margin: 0; padding: 0;
}
.crumbs li { display: flex; align-items: center; }
.crumbs-sep {
margin: 0 10px; color: var(--color-muted); font-weight: 400;
}
.crumbs a { color: var(--color-pine); font-weight: 600; }
.crumbs a:hover { color: var(--color-pine-deep); }
.crumbs [aria-current="page"] { color: var(--color-ink); font-weight: 600; }
.reader-nav .nav-title {
font-size: 14.5px; font-weight: 600; color: var(--color-ink);
}
.reader-nav .nav-title:hover { color: var(--color-pine); }
.reader-nav .reader-links { gap: 22px; }
.reader-nav .nav-current {
font-size: 14.5px; font-weight: 600; color: var(--color-ink);
max-width: 22ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 860px) {
.reader-nav .nav-links {
display: flex; align-items: center; gap: 16px; font-size: 13.5px;
}
.reader-nav .nav-cta {
display: inline-flex; font-size: 13px; padding: 8px 14px;
}
.reader-nav .nav-current { display: none; }
.reader-nav .nav-toggle { display: none; }
}
.chapter-page { padding: 48px 0 96px; }
.chapter-shell {
display: grid;
grid-template-columns: 240px minmax(0, 1fr);
gap: 32px 40px;
align-items: start;
}
@media (min-width: 1080px) {
.chapter-shell {
grid-template-columns: 240px minmax(0, 1fr) 280px;
}
}
.chapter-col { min-width: 0; }
.chapter-kicker { margin: 8px 0 14px; }
.chapter-title {
font-family: var(--font-display); font-weight: 900;
font-size: clamp(2.1rem, 4.6vw, 3.4rem);
letter-spacing: -0.028em; line-height: 1.15; margin: 0 0 12px;
max-width: 22ch;
}
.chapter-by {
font-family: var(--font-mono); font-size: 12.5px; color: var(--color-muted);
letter-spacing: 0.04em; margin-bottom: 20px;
}
.chapter-promise {
font-size: 1.12rem; line-height: 1.62; color: var(--color-ink); margin: 0 0 28px;
max-width: 46rem;
}
.chapter-outline {
position: sticky;
top: 68px;
z-index: 30;
align-self: start;
background: var(--color-paper);
border-right: 1px solid var(--color-line);
padding: 16px 20px 28px 0;
max-height: calc(100vh - 68px);
overflow-y: auto;
}
.outline-progress {
font-family: var(--font-mono); font-size: 11.5px; color: var(--color-muted);
letter-spacing: 0.04em; margin: 0 0 14px;
}
.outline-heading {
font-family: var(--font-display); font-weight: 600;
font-size: 1.08rem; letter-spacing: -0.02em; margin: 0 0 16px;
}
.outline-group { margin: 0 0 18px; }
.outline-group:last-child { margin-bottom: 0; }
.outline-group-label { margin: 0 0 8px; }
.outline-group ul { list-style: none; margin: 0; padding: 0; }
.outline-group li { margin: 0; }
.outline-group a {
display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
padding: 5px 0 5px 10px;
border-left: 2px solid transparent;
font-size: 13.5px; line-height: 1.35; color: var(--color-muted);
}
.outline-group a:hover { color: var(--color-ink); }
.outline-group a.is-active {
color: var(--color-pine); border-left-color: var(--color-pine); font-weight: 600;
}
.outline-done {
font-family: var(--font-mono); font-size: 12px; color: var(--color-pine); flex: none;
}
.chapter-rail {
position: sticky;
top: 84px;
align-self: start;
display: none;
}
@media (min-width: 1080px) {
.chapter-rail { display: block; }
}
.chapter-rail .spec { margin: 0 0 14px; }
.rail-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.rail-card {
display: flex; flex-direction: column; gap: 6px;
padding: 16px 18px;
background: var(--color-card);
border: 1px solid var(--color-line);
border-radius: 16px;
box-shadow: var(--sh-sm);
transition: border-color 0.18s, transform 0.14s var(--ease), box-shadow 0.18s;
}
.rail-card:hover {
border-color: var(--color-pine-tint);
transform: translateY(-1px);
box-shadow: var(--sh-md);
}
.rail-card.is-done { border-color: var(--color-pine-tint); background: var(--color-pine-wash); }
.rail-kicker {
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--color-pine);
}
.rail-title {
font-family: var(--font-display); font-weight: 600;
font-size: 1.15rem; letter-spacing: -0.02em; line-height: 1.2;
color: var(--color-ink);
}
.rail-meta {
font-family: var(--font-mono); font-size: 12px; color: var(--color-muted);
letter-spacing: 0.02em;
}
.rail-skip {
display: inline-block; margin-top: 16px;
font-family: var(--font-mono); font-size: 12.5px; font-weight: 600;
color: var(--color-pine);
}
.rail-skip:hover { color: var(--color-pine-deep); }
.chapter-outline-mobile {
margin: 0 0 24px;
padding: 10px 16px 12px;
border: 1px solid var(--color-line);
border-radius: 12px;
background: var(--color-card);
}
.chapter-outline-mobile summary {
cursor: pointer; list-style: none;
font-family: var(--font-display); font-weight: 600;
font-size: 1.05rem; letter-spacing: -0.02em;
padding: 6px 0;
}
.chapter-outline-mobile summary::-webkit-details-marker { display: none; }
.chapter-outline-mobile[open] summary { margin-bottom: 10px; }
.chapter-outline-mobile .outline-progress { margin-top: 4px; }
.chapter-kit {
margin: 0 0 28px;
padding: 18px 20px 16px;
background: var(--color-pine-wash);
border: 1px solid var(--color-pine-tint);
border-radius: 16px;
}
.chapter-kit-title {
font-family: var(--font-display); font-weight: 600;
font-size: 1.2rem; letter-spacing: -0.02em;
margin: 0 0 6px;
}
.chapter-kit-lede {
font-size: 14.5px; color: var(--color-muted);
margin: 0 0 16px;
}
.kit-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px 28px;
margin: 0;
}
@media (min-width: 900px) {
.kit-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
.kit-grid { grid-template-columns: 1fr; }
}
.kit-grid dt {
font-family: var(--font-mono); font-size: 11px; font-weight: 500;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--color-pine); margin: 0 0 6px;
}
.kit-grid dd { margin: 0; font-size: 14.5px; line-height: 1.4; color: var(--color-ink); }
.kit-grid ul { list-style: none; margin: 0; padding: 0; }
.kit-grid li { margin: 0 0 4px; }
.kit-grid li:last-child { margin-bottom: 0; }
.workflow-kit {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
gap: 12px 16px;
margin: 4px 0 8px;
padding: 14px 0 16px;
border-bottom: 1px dashed var(--color-line-2);
}
.workflow-kit dt {
font-family: var(--font-mono); font-size: 11px; font-weight: 500;
letter-spacing: 0.1em; text-transform: uppercase;
color: var(--color-pine); margin: 0 0 4px;
}
.workflow-kit dd {
margin: 0; font-size: 14px; line-height: 1.4; color: var(--color-ink);
}
.rail-kit {
font-size: 12.5px; line-height: 1.35; color: var(--color-muted);
}
.rail-out { color: var(--color-ink); }
.chapter-map {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 40px;
margin: 0 0 8px;
padding: 18px 0 6px;
border-top: 1px solid var(--color-line);
}
.chapter-map-title { grid-column: 1 / -1; }
@media (max-width: 720px) {
.chapter-map { grid-template-columns: 1fr; }
}
.chapter-map-title {
font-family: var(--font-display); font-weight: 600;
font-size: 1.2rem; letter-spacing: -0.02em;
margin: 0 0 16px;
}
.chapter-map-block { margin: 0 0 16px; }
.chapter-map-block .spec { margin: 0 0 8px; }
.chapter-map-block ul { margin: 0; padding: 0 0 0 1.1em; }
.chapter-map-block li { margin: 0 0 6px; }
.chapter-map-block a { color: var(--color-pine); font-weight: 600; }
.chapter-map-block a:hover { color: var(--color-pine-deep); }
.skip-row {
display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
margin: 0 0 48px; padding: 16px 0;
border-top: 1px dashed var(--color-line-2);
border-bottom: 1px dashed var(--color-line-2);
}
.skip-label {
font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-pine);
margin-right: 4px;
}
.skip-chip {
display: inline-flex; align-items: baseline; gap: 8px;
font-family: var(--font-mono); font-size: 12.5px; color: var(--color-pine);
background: var(--color-pine-wash); border: 1px solid var(--color-pine-tint);
padding: 7px 13px; border-radius: 999px;
transition: background 0.18s, transform 0.14s var(--ease);
}
.skip-chip:hover { background: var(--color-pine-tint); transform: translateY(-1px); }
.skip-chip:focus-visible { outline: 2px solid var(--color-pine); outline-offset: 3px; }
.skip-chip-job {
font-family: var(--font-body); font-size: 13px; font-weight: 600;
color: var(--color-ink); letter-spacing: 0; text-transform: none;
}
.essay,
.recipe-nested,
.end-matter,
.end-sources,
.recipe-nested .row {
scroll-margin-top: 88px;
}
.essay { margin: 0 0 36px; max-width: 46rem; }
.essay h2 {
font-family: var(--font-display); font-weight: 600;
font-size: 1.45rem; letter-spacing: -0.02em; line-height: 1.2;
margin: 0 0 12px;
}
.essay p { color: var(--color-ink); margin: 0 0 14px; }
.essay p:last-child { margin-bottom: 0; }
.workflow-kicker { margin: 8px 0 10px; }
.recipe-nested {
margin: 0 0 52px;
}
.recipe-nested .step-copy { min-width: 0; flex: 1; }
.recipe-nested .step-copy .term { margin-top: 12px; }
.recipe-nested h4.k { margin: 0; font-weight: 500; }
.recipe-nested .term {
overflow-x: auto;
overflow-y: hidden;
}
.recipe-nested .term pre { overflow-x: auto; }
.recipe-jump {
display: flex; flex-wrap: wrap; gap: 8px 16px;
margin: 0 0 8px; padding: 0 0 16px;
border-bottom: 1px dashed var(--color-line-2);
font-size: 13.5px;
}
.recipe-jump a { color: var(--color-pine); font-weight: 600; }
.recipe-jump a:hover { color: var(--color-pine-deep); }
.recipe-jump a:focus-visible { outline: 2px solid var(--color-pine); outline-offset: 2px; }
.plain-list { margin: 0; padding: 0 0 0 1.1em; }
.plain-list li { margin: 0 0 6px; }
.plain-list li:last-child { margin-bottom: 0; }
.evidence-p { margin: 0 0 10px; }
.evidence-p:last-child { margin-bottom: 0; }
.sub-line { color: var(--color-muted); font-size: 14.5px; margin: 8px 0 12px; }
.inline-sample {
font-family: var(--font-mono); font-size: 13px; color: var(--color-pine);
border-bottom: 1px solid var(--color-pine-tint);
}
.inline-sample:hover { color: var(--color-pine-deep); }
.tryit-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.tryit-actions .btn { font-size: 14px; padding: 11px 18px; }
.tryit-done { gap: 8px; }
.recipe-record {
display: flex; flex-direction: column; gap: 6px;
font-size: 14.5px; color: var(--color-muted);
}
.recipe-record a { color: var(--color-pine); font-weight: 600; }
.recipe-record a:hover { color: var(--color-pine-deep); }
.end-matter {
margin-top: 56px; padding-top: 28px;
border-top: 1px solid var(--color-line);
}
.end-sources { margin-top: 32px; }
.end-matter h2,
.end-sources h2 {
font-family: var(--font-display); font-weight: 600;
font-size: 1.35rem; letter-spacing: -0.02em; margin: 0 0 12px;
}
.end-matter h2 + .further-list { margin-bottom: 8px; }
.further-list { margin: 0 0 8px; padding: 0 0 0 1.1em; color: var(--color-ink); }
.further-list li { margin: 0 0 8px; }
.further-list a { color: var(--color-pine); font-weight: 600; }
.chapter-updated {
font-family: var(--font-mono); font-size: 12px; color: var(--color-muted);
letter-spacing: 0.04em; margin-top: 18px;
}
.library-title {
font-family: var(--font-display); font-weight: 900;
font-size: clamp(1.9rem, 4vw, 3rem); letter-spacing: -0.025em;
line-height: 1.15; margin: 14px 0 16px;
white-space: nowrap;
}
.library-lede { color: var(--color-muted); max-width: 38rem; margin-bottom: 34px; }
.toc .toc-soon {
display: grid; grid-template-columns: 52px 1fr auto auto;
align-items: center; gap: 20px; padding: 22px 8px;
border-bottom: 1px solid var(--color-line);
color: var(--color-muted);
}
.toc .soon {
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--color-muted);
background: var(--color-card); border: 1px solid var(--color-line);
padding: 4px 10px; border-radius: 999px; white-space: nowrap;
}
.reader-library .toc a {
grid-template-columns: 52px 1fr auto auto auto;
}
.toc-count {
font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
letter-spacing: 0.08em; text-transform: uppercase;
color: var(--color-pine); white-space: nowrap;
}
@media (max-width: 720px) {
.toc .toc-soon .desc { display: none; }
.toc .toc-soon { grid-template-columns: 40px 1fr auto; }
.reader-library .toc a { grid-template-columns: 40px 1fr auto auto; }
}
.recipe-focus-page { padding: 40px 0 96px; }
.recipe-focus-page .demo-chip { margin-bottom: 12px; }
.recipe-focus-page .recipe { max-width: none; }
.focus-back {
font-family: var(--font-mono); font-size: 13px; color: var(--color-muted);
margin: 0 0 28px;
}
.focus-back a { color: var(--color-pine); font-weight: 600; }
@media (max-width: 959px) {
.chapter-shell { grid-template-columns: 1fr; gap: 0; }
.chapter-outline { display: none; }
.chapter-rail { display: none; }
}
@media (min-width: 960px) {
.chapter-outline-mobile { display: none; }
}
@media print {
.reader-nav, .chapter-outline, .chapter-outline-mobile, .chapter-rail, .reader-footer, .skip-row, .tryit-actions, .demo-chip, .recipe-jump {
display: none !important;
}
.recipe-nested { break-inside: avoid; box-shadow: none; }
}
@@ -0,0 +1,32 @@
"""Render the AiBook.fun reader replica (reader/chapter.html) to device-screen PNGs for the mockups.
Writes assets-gen/screen-laptop.png, assets-gen/screen-phone.png and assets-gen/screens.json (target boxes)."""
import json, pathlib, asyncio
from playwright.async_api import async_playwright
HERE = pathlib.Path(__file__).parent
URL = (HERE / 'reader' / 'chapter.html').as_uri()
# Box of the element's text (Range), so callout dots can sit just outside the words.
BOX = """(sel) => { const el = document.querySelector(sel); const rg = document.createRange(); rg.selectNodeContents(el);
const r = rg.getBoundingClientRect(); return [r.left, r.top, r.width, r.height]; }"""
async def main():
out = {}
async with async_playwright() as p:
b = await p.chromium.launch(executable_path='/usr/bin/google-chrome')
# Laptop: desktop layout, scrolled to the last essay + first workflow
pg = await b.new_page(viewport={'width': 1120, 'height': 700}, device_scale_factor=2)
await pg.goto(URL); await pg.evaluate('document.fonts.ready')
await pg.evaluate("document.getElementById('ol-practice').classList.add('is-active')")
await pg.evaluate("window.scrollTo(0, document.getElementById('practice').getBoundingClientRect().top + scrollY - 96)")
await pg.wait_for_timeout(200)
out['laptop'] = {'vw': 1120, 'vh': 700, 'essay': await pg.evaluate(BOX, '#practice h2'), 'workflow': await pg.evaluate(BOX, '#re-01 .recipe-id')}
await pg.screenshot(path=str(HERE / 'assets-gen' / 'screen-laptop.png'))
# Phone: mobile layout, scrolled to the Try-it row
pg = await b.new_page(viewport={'width': 390, 'height': 844}, device_scale_factor=3)
await pg.goto(URL); await pg.evaluate('document.fonts.ready')
await pg.evaluate("window.scrollTo(0, document.getElementById('re-01-tryit').getBoundingClientRect().top + scrollY - 92)")
await pg.wait_for_timeout(200)
out['phone'] = {'vw': 390, 'vh': 844, 'tryit': await pg.evaluate(BOX, '#re-01-tryit .k'), 'updated': await pg.evaluate(BOX, '#re-01-updated')}
await pg.screenshot(path=str(HERE / 'assets-gen' / 'screen-phone.png'))
await b.close()
json.dump(out, open(HERE / 'assets-gen' / 'screens.json', 'w'), indent=1)
print(json.dumps(out))
asyncio.run(main())
+53
View File
@@ -0,0 +1,53 @@
/* We Own AI: Kickstarter story images. Shared styles.
Tokens copied from WeOwnAI/aibook-fun marketing-site/styles/globals.css (@theme). */
:root {
--paper: #fcfbf8; --paper-2: #f6f4ee; --card: #ffffff; --ink: #1a1613; --muted: #6e675e;
--line: #e7e1d7; --line-2: #d8d1c4; --pine: #1c5d4c; --pine-deep: #12463a;
--pine-tint: rgba(28,93,76,.1); --pine-wash: rgba(28,93,76,.06); --amber: #b4791f;
--mint: #8fbeae; --cream: #f3eee2;
--display: "Fraunces", Georgia, serif; --body: "Hanken Grotesk", system-ui, sans-serif;
--mono: "JetBrains Mono", ui-monospace, monospace; --emoji: "Noto Color Emoji";
--sh-sm: 0 1px 2px rgba(26,22,19,.05), 0 1px 3px rgba(26,22,19,.04);
--sh-md: 0 4px 10px rgba(26,22,19,.06), 0 2px 4px rgba(26,22,19,.04);
--sh-lg: 0 18px 40px -12px rgba(26,22,19,.14), 0 6px 14px rgba(26,22,19,.05);
}
*, ::before, ::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: #fff; }
body { font-family: var(--body); color: var(--ink); -webkit-font-smoothing: antialiased; letter-spacing: -0.005em; }
ul, ol { list-style: none; }
svg { display: block; }
.emoji { font-family: var(--emoji); font-style: normal; }
/* ---------- canvas (CSS px; rendered at 2x) ---------- */
.canvas { width: 680px; background: var(--paper-2); position: relative; overflow: hidden; padding: 32px; }
.canvas.fixed { overflow: hidden; }
.spec { font-family: var(--mono); font-size: 13px; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--pine); }
.lead { font-family: var(--display); font-weight: 600; font-size: 25px; letter-spacing: -0.02em; line-height: 1.15; margin: 8px 0 20px; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 16px; box-shadow: var(--sh-sm); }
.foot-note { font-family: var(--mono); font-size: 13px; color: var(--muted); letter-spacing: .02em; margin-top: 18px; }
.ico { width: 24px; height: 24px; color: var(--pine); flex: none; }
.ico svg { width: 100%; height: 100%; }
/* ---------- section header 680 x 150 ---------- */
.hdr { width: 680px; height: 150px; padding: 0 36px; display: flex; align-items: center; gap: 22px; position: relative; overflow: hidden;
background: radial-gradient(120% 140% at 88% 0%, rgba(255,255,255,.08), transparent 55%), linear-gradient(120deg, #1f6653, var(--pine-deep) 55%, #0f3b30); }
.hdr .tabs { position: absolute; top: 0; right: 36px; display: flex; gap: 6px; }
.hdr .tabs span { width: 34px; height: 8px; border-radius: 0 0 4px 4px; background: rgba(243,238,226,.18); }
.hdr .tabs span:first-child { background: var(--amber); opacity: .9; }
.hdr .mark { position: absolute; right: -34px; bottom: -58px; width: 230px; height: 230px; opacity: .10; }
.hdr .badge { width: 78px; height: 78px; border-radius: 999px; background: var(--paper); display: grid; place-items: center; flex: none;
box-shadow: 0 10px 24px -10px rgba(0,0,0,.45), inset 0 0 0 1px rgba(26,22,19,.06); }
.hdr .badge .emoji { font-size: 40px; line-height: 1; }
.hdr .kicker { font-family: var(--mono); font-size: 13px; letter-spacing: .14em; text-transform: uppercase; color: var(--mint); margin-bottom: 8px; }
.hdr h1 { font-family: var(--display); font-weight: 900; font-size: 46px; line-height: 1; letter-spacing: -0.028em; color: var(--cream); white-space: nowrap; }
.hdr .rule { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: linear-gradient(90deg, var(--amber) 0 64px, transparent 64px); }
/* ---------- photo slots (owner-supplied images) ---------- */
.slot { position: relative; overflow: hidden; background: var(--paper-2); }
.slot.filled { background-size: cover; background-position: center; }
.slot .ph { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; text-align: center;
background: repeating-linear-gradient(135deg, #efebe2 0 10px, #f4f1ea 10px 20px); border: 2px dashed #c9c0b0; border-radius: inherit; color: var(--muted); padding: 10px; }
.slot.filled .ph { display: none; }
.slot .ph b { font-family: var(--body); font-weight: 700; font-size: 16px; color: #5b554d; }
.slot .ph small { font-family: var(--mono); font-size: 12px; letter-spacing: .04em; }
.slot .ph svg { width: 30px; height: 30px; color: #8d8579; }