Story images: sources for landing-page cover and live screenshots

This commit is contained in:
pat
2026-09-25 17:58:24 +00:00
parent 1685918c11
commit a69711227e
22 changed files with 223 additions and 195 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

@@ -0,0 +1,15 @@
{
"url": "https://aibook.fun/read/recipe/RE-01",
"top": {
"x": 0,
"y": 223.203125,
"width": 616,
"height": 386.96875
},
"tryit": {
"x": 0,
"y": 2002.140625,
"width": 616,
"height": 287.125
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 238 KiB

@@ -4,14 +4,14 @@
"vh": 700,
"essay": [
328,
95.21875,
201.921875,
95.40625,
198.921875,
29
],
"workflow": [
357,
364.09375,
200.046875,
364.28125,
200.109375,
37.625
]
},
@@ -20,14 +20,14 @@
"vh": 844,
"tryit": [
61,
114.8125,
115.28125,
48.3125,
15
],
"updated": [
61,
769.78125,
170.75,
794.046875,
170.765625,
19
]
}
@@ -5,9 +5,10 @@ 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 |
| author-1, author-2, author-3 | 14b authors (Real Estate & Finance, Fractional C-Suite, Technology & RAAI) | 800 × 800, square |
| team-thy, team-lfg, team-ceo, team-gtm, team-osinachi-patrick, team-academy-lead | 14b team | 800 × 800, square |
The book cover is not a photo slot: it is rendered from the landing page's `Book3D.jsx` by `render_cover.py` into `src/assets-gen/`.
+55 -72
View File
@@ -45,6 +45,8 @@ 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>')
COVER_3D = '../assets-gen/cover-3d.png' # rendered from the landing page (render_cover.py)
COVER_FLAT = '../assets-gen/cover-flat.png'
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))
@@ -74,7 +76,7 @@ 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>
<figure class="bookwrap"><img src="{COVER_3D}" alt=""><figcaption>Cover mockup</figcaption></figure>
<div class="wtxt">
<h2>Welcome</h2>
<p class="t">{TITLE}</p>
@@ -88,11 +90,10 @@ page('01b-welcome-cover-icons', f'''
<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}
.wel{display:flex;gap:30px;align-items:center;padding:0 6px 22px}
.bookwrap{flex:none;margin:0;width:196px;text-align:center}
.bookwrap img{width:196px;display:block}
.bookwrap figcaption{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:-14px}
.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}
@@ -183,67 +184,40 @@ page('04b-workflow-recipe-diagram', f'''<div class="canvas" id="shot">
.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='''
# Published on the landing page: marketing-site/components/Contents.jsx ("The founding library")
CHAPTERS = [('Real Estate', 'Market briefs & listings, without leaking client data'),
('Financial Advisors', 'Portfolio summaries that stay compliant & reviewed'),
('Fractional <span style="white-space:nowrap">C-Suite</span>', 'Board updates & plans with real escalation paths'),
('Higher Education', 'Course & research workflows with academic integrity'),
('Communities', 'Moderation & programming that respects members'),
('Technology', 'Agent patterns, evals & the responsible defaults')]
page('04c-chapter-tiles', '<div class="canvas" id="shot"><p class="spec">The founding library</p><div class="tg">' + ''.join(
f'<div class="card t"><span class="n">{i:02d}</span><h3>{c}</h3><p>{d}</p></div>' for i, (c, d) 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}
.t{padding:16px 16px 18px;display:flex;flex-direction:column}
.t .n{font-family:var(--mono);font-size:13px;color:var(--pine);letter-spacing:.08em}
.t h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.12;margin:10px 0 6px}
.t p{font-size:14px;line-height:1.4;color:var(--muted)}
''')
# ---------------------------------------------------------------- 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">
page('05b-sample-workflow-try-it', '''<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 class="bar"><i></i><i></i><i></i><span class="url">aibook.fun/read/recipe/RE-01</span></div>
<img src="../assets-gen/re01-top.png" alt="">
<div class="gap"><span>Problem · Actors · Setup · Safeguards · Method · Evidence</span></div>
<img src="../assets-gen/re01-tryit.png" alt="">
</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)}
<p class="foot-note">Screenshots of sample workflow RE-01 at aibook.fun/read/recipe/RE-01</p>
</div>''', css='''
.win{background:var(--paper);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}
.win img{display:block;width:614px}
.gap{padding:10px 0;text-align:center;background:linear-gradient(transparent calc(50% - 1px),var(--line-2) calc(50% - 1px) calc(50% + 1px),transparent calc(50% + 1px))}
.gap span{font-family:var(--mono);font-size:12.5px;color:var(--muted);background:var(--paper);padding:2px 10px;letter-spacing:.02em}
.foot-note{text-align:center}
''')
page('05c-button-try-the-workflow', f'''<div class="canvas" id="shot">
@@ -265,7 +239,7 @@ def three_ways(paperback=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>'
rows += f'<li><b class="np"><img src="{COVER_3D}" alt=""></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>'
@@ -287,7 +261,7 @@ def three_ways(paperback=True):
<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>
<p class="nm">Cloud</p><div class="ch"><h3>Do it yourself, online</h3><img src="{COVER_FLAT}" alt=""></div>
<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>
@@ -312,7 +286,9 @@ TW_CSS = '''
.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 b.np{align-self:start;width:30px;margin:0 0 0 6px}.prices b.np img{width:30px;display:block}
.ch{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.ch img{width:46px;border-radius:2px 4px 4px 2px;box-shadow:0 6px 12px -6px rgba(18,63,52,.5);margin:4px 0 8px}
.prices small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff}
@@ -405,7 +381,7 @@ page('09b-aibook-fun-device-mockup', f'''<div class="canvas" id="shot">
<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>
<p class="foot-note">Screenshots of the AiBook.fun reader (sample Real Estate chapter) in device frames.</p>
</div>
<script>
window.afterLayout = async () => {{
@@ -527,20 +503,24 @@ 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>'
vis = f'<img class="cv" src="{i2[4:]}" alt="">' if i2.startswith('img:') else icon(i2, "ico ci")
st += f'<div class="stp">{vis}<b>{t}</b>' + (f'<p>{d}</p>' if d else '') + '</div>'
head = f'<img class="lhc" src="{ic[4:]}" alt="">' if ic.startswith('img:') else icon(ic, "ico lhi")
return f'<div class="card lane"><div class="lh">{head}<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', [
('img:' + COVER_FLAT, 'eBook', 'January 2027')]) +
lane('Signed paperbacks', 'Ship in February 2027', 'img:' + COVER_3D, [
('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}
.cv{display:block;height:46px;margin:0 auto 8px;border-radius:1px 3px 3px 1px;box-shadow:0 4px 8px -4px rgba(18,63,52,.5)}
.lhc{height:52px;display:block;margin:-6px 2px -4px 0}
.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}
@@ -585,20 +565,23 @@ TEAM = [('@THY', 'Name goes here', 'Author and Retreat host'),
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)
# Landing page (Authors.jsx) lists three co-authors as confirmed, by chapter; names are not published yet.
AUTHORS = [('Name goes here', 'Co-author, Real Estate & Finance'), ('Name goes here', 'Co-author, <span style="white-space:nowrap">Fractional C-Suite</span>'),
('Name goes here', 'Co-author, Technology & RAAI')]
AUTHOR_HTML = ''.join(f'<div class="card au">{slot(f"author-{i}", "Author photo goes here", "800 × 800", "ap")}<p class="an">{n}</p><p class="ar">{r}</p></div>'
for i, (n, r) in enumerate(AUTHORS, 1))
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>
<div class="ag">{AUTHOR_HTML}</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}
.ag{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;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}
.ap .ph b{font-size:13px}
.an{font-family:var(--display);font-weight:600;font-size:19px;letter-spacing:-.02em;margin-top:12px;color:#8d8579}
.ar{font-size:14px;line-height:1.35;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}
@@ -1,10 +1,9 @@
<!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}
.wel{display:flex;gap:30px;align-items:center;padding:0 6px 22px}
.bookwrap{flex:none;margin:0;width:196px;text-align:center}
.bookwrap img{width:196px;display:block}
.bookwrap figcaption{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:-14px}
.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}
@@ -16,7 +15,7 @@
</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>
<figure class="bookwrap"><img src="../assets-gen/cover-3d.png" alt=""><figcaption>Cover mockup</figcaption></figure>
<div class="wtxt">
<h2>Welcome</h2>
<p class="t">We Own AI — The New American Dream</p>
@@ -1,30 +0,0 @@
<!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,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:16px 16px 18px;display:flex;flex-direction:column}
.t .n{font-family:var(--mono);font-size:13px;color:var(--pine);letter-spacing:.08em}
.t h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.12;margin:10px 0 6px}
.t p{font-size:14px;line-height:1.4;color:var(--muted)}
</style></head><body><div class="canvas" id="shot"><p class="spec">The founding library</p><div class="tg"><div class="card t"><span class="n">01</span><h3>Real Estate</h3><p>Market briefs & listings, without leaking client data</p></div><div class="card t"><span class="n">02</span><h3>Financial Advisors</h3><p>Portfolio summaries that stay compliant & reviewed</p></div><div class="card t"><span class="n">03</span><h3>Fractional <span style="white-space:nowrap">C-Suite</span></h3><p>Board updates & plans with real escalation paths</p></div><div class="card t"><span class="n">04</span><h3>Higher Education</h3><p>Course & research workflows with academic integrity</p></div><div class="card t"><span class="n">05</span><h3>Communities</h3><p>Moderation & programming that respects members</p></div><div class="card t"><span class="n">06</span><h3>Technology</h3><p>Agent patterns, evals & the responsible defaults</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>
@@ -1,50 +1,21 @@
<!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)}
<link rel="stylesheet" href="../styles.css"><style>
.win{background:var(--paper);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}
.win img{display:block;width:614px}
.gap{padding:10px 0;text-align:center;background:linear-gradient(transparent calc(50% - 1px),var(--line-2) calc(50% - 1px) calc(50% + 1px),transparent calc(50% + 1px))}
.gap span{font-family:var(--mono);font-size:12.5px;color:var(--muted);background:var(--paper);padding:2px 10px;letter-spacing:.02em}
.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 class="bar"><i></i><i></i><i></i><span class="url">aibook.fun/read/recipe/RE-01</span></div>
<img src="../assets-gen/re01-top.png" alt="">
<div class="gap"><span>Problem · Actors · Setup · Safeguards · Method · Evidence</span></div>
<img src="../assets-gen/re01-tryit.png" alt="">
</div>
<p class="foot-note">Sample workflow RE-01, shown in the AiBook.fun reader.</p>
<p class="foot-note">Screenshots of sample workflow RE-01 at aibook.fun/read/recipe/RE-01</p>
</div>
<script>
window.__ready = (async () => {
@@ -18,7 +18,9 @@
.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 b.np{align-self:start;width:30px;margin:0 0 0 6px}.prices b.np img{width:30px;display:block}
.ch{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.ch img{width:46px;border-radius:2px 4px 4px 2px;box-shadow:0 6px 12px -6px rgba(18,63,52,.5);margin:4px 0 8px}
.prices small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff}
@@ -42,8 +44,8 @@
<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>
<p class="nm">Cloud</p><div class="ch"><h3>Do it yourself, online</h3><img src="../assets-gen/cover-flat.png" alt=""></div>
<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"><img src="../assets-gen/cover-3d.png" alt=""></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>
@@ -18,7 +18,9 @@
.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 b.np{align-self:start;width:30px;margin:0 0 0 6px}.prices b.np img{width:30px;display:block}
.ch{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.ch img{width:46px;border-radius:2px 4px 4px 2px;box-shadow:0 6px 12px -6px rgba(18,63,52,.5);margin:4px 0 8px}
.prices small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff}
@@ -42,7 +44,7 @@
<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>
<p class="nm">Cloud</p><div class="ch"><h3>Do it yourself, online</h3><img src="../assets-gen/cover-flat.png" alt=""></div>
<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>
@@ -20,16 +20,16 @@
</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>
<i class="tg" data-t="essay" style="left:27.32%;top:15.70%"></i><i class="tg" data-t="workflow" style="left:29.91%;top:54.73%"></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>
<i class="tg" data-t="tryit" style="left:33.67%;top:14.55%"></i><i class="tg" data-t="updated" style="left:65.07%;top:95.21%"></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>
<p class="foot-note">Screenshots of the AiBook.fun reader (sample Real Estate chapter) in device frames.</p>
</div>
<script>
window.afterLayout = async () => {
@@ -1,6 +1,8 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style>
.lane{padding:16px 18px 18px}
.cv{display:block;height:46px;margin:0 auto 8px;border-radius:1px 3px 3px 1px;box-shadow:0 4px 8px -4px rgba(18,63,52,.5)}
.lhc{height:52px;display:block;margin:-6px 2px -4px 0}
.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}
@@ -12,7 +14,7 @@
.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>
</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"><img class="cv" src="../assets-gen/cover-flat.png" alt=""><b>eBook</b><p>January 2027</p></div></div></div><div class="card lane"><div class="lh"><img class="lhc" src="../assets-gen/cover-3d.png" alt=""><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;
@@ -1,10 +1,11 @@
<!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}
.ag{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;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}
.ap .ph b{font-size:13px}
.an{font-family:var(--display);font-weight:600;font-size:19px;letter-spacing:-.02em;margin-top:12px;color:#8d8579}
.ar{font-size:14px;line-height:1.35;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}
@@ -14,10 +15,7 @@
.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>
<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">Name goes here</p><p class="ar">Co-author, Real Estate & Finance</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">Name goes here</p><p class="ar">Co-author, <span style="white-space:nowrap">Fractional C-Suite</span></p></div><div class="card au"><div class="slot ap" data-src="../assets/author-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>Author photo goes here</b><small>800 × 800</small></div></div><p class="an">Name goes here</p><p class="ar">Co-author, Technology & RAAI</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>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
"""Render the book cover exactly as designed for the landing page.
Source of the design: WeOwnAI/aibook-fun marketing-site/components/Book3D.jsx + styles/globals.css (.book3d*)
+ public/american-dream-banner.webp, rendered from the deployed landing page https://aibook.fun/ (built from main).
Outputs (transparent PNG):
assets-gen/cover-3d.png the 3D book in its landing-page pose (animation paused via reduced motion)
assets-gen/cover-flat.png the front cover face only, flat (3D transform switched off, nothing else changed)
"""
import asyncio, pathlib
from playwright.async_api import async_playwright
HERE = pathlib.Path(__file__).parent
URL = 'https://aibook.fun/'
HIDE = "html,body,main,.hero,.hero-visual{background:transparent!important;overflow:visible!important} .nav,.hero-copy,.hero-tabs,.notes,.section,.footer{visibility:hidden!important}"
FLAT = ".book3d{perspective:none!important}.book3d__inner{transform:none!important;animation:none!important}.book3d::after{display:none!important}"
# SwiftShader via ANGLE gives correct 3D depth sorting in headless Chrome (the default path lets the spine bleed through).
ARGS = ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader']
async def main():
async with async_playwright() as p:
b = await p.chromium.launch(executable_path='/usr/bin/google-chrome', args=ARGS)
for name, extra, sel, pad in [('cover-3d', '', '.book3d', 70), ('cover-flat', FLAT, '.book3d__cover', 0)]:
pg = await b.new_page(viewport={'width': 1280, 'height': 900}, device_scale_factor=5, reduced_motion='reduce')
await pg.goto(URL, wait_until='networkidle'); await pg.evaluate('document.fonts.ready')
await pg.add_style_tag(content=HIDE + extra); await pg.wait_for_timeout(400)
r = await pg.evaluate(f"(() => {{ const r = document.querySelector('{sel}').getBoundingClientRect(); return [r.left, r.top, r.width, r.height]; }})()")
clip = {'x': r[0] - pad, 'y': r[1] - pad, 'width': r[2] + 2 * pad, 'height': r[3] + 2 * pad}
await pg.screenshot(path=str(HERE / 'assets-gen' / f'{name}.png'), clip=clip, omit_background=True)
await pg.close()
await b.close()
from PIL import Image
for n in ('cover-3d', 'cover-flat'):
im = Image.open(HERE / 'assets-gen' / f'{n}.png'); bb = im.getbbox(); im.crop(bb).save(HERE / 'assets-gen' / f'{n}.png'); print(n, im.crop(bb).size)
asyncio.run(main())
+45 -22
View File
@@ -1,32 +1,55 @@
"""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)."""
"""Real screenshots of the live AiBook.fun reader (https://aibook.fun, deployed from WeOwnAI/aibook-fun main).
Writes to assets-gen/:
screen-laptop.png / screen-phone.png + screens.json chapter page https://aibook.fun/read/real-estate (09b device mockup)
re01-top.png / re01-tryit.png + re01.json sample workflow https://aibook.fun/read/recipe/RE-01 (05b)
Nothing on the pages is edited except the outline highlight, which the page sets itself on scroll.
"""
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]; }"""
OUT = HERE / 'assets-gen'
CHAPTER = 'https://aibook.fun/read/real-estate'
RECIPE = 'https://aibook.fun/read/recipe/RE-01'
# Box of an element's text (Range), so callout dots can sit just outside the words.
BOX = """(el) => { const rg = document.createRange(); rg.selectNodeContents(el); const r = rg.getBoundingClientRect(); return [r.left, r.top, r.width, r.height]; }"""
RECT = """(el) => { const r = el.getBoundingClientRect(); return [r.left, r.top + scrollY, r.width, r.height]; }"""
async def open_(b, url, w, h, dpr):
pg = await b.new_page(viewport={'width': w, 'height': h}, device_scale_factor=dpr)
await pg.goto(url, wait_until='networkidle'); await pg.evaluate('document.fonts.ready'); return pg
async def scroll_to(pg, sel, offset):
await pg.evaluate(f"window.scrollTo(0, document.querySelector('{sel}').getBoundingClientRect().top + scrollY - {offset})")
await pg.wait_for_timeout(700)
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'))
# Laptop: desktop layout at the last essay + first workflow
pg = await open_(b, CHAPTER, 1120, 700, 2)
await scroll_to(pg, '#practice', 96)
out['laptop'] = {'vw': 1120, 'vh': 700,
'essay': await (await pg.query_selector('#practice h2')).evaluate(BOX),
'workflow': await (await pg.query_selector('#re-01 .recipe-id')).evaluate(BOX)}
await pg.screenshot(path=str(OUT / 'screen-laptop.png'))
# Phone: mobile layout at the first Try-it
pg = await open_(b, CHAPTER, 390, 844, 3)
await scroll_to(pg, '#re-01-tryit', 92)
upd = await pg.evaluate_handle("[...document.querySelectorAll('#re-01-record .recipe-record span')].find(s => s.textContent.startsWith('Last updated'))")
out['phone'] = {'vw': 390, 'vh': 844,
'tryit': await (await pg.query_selector('#re-01-tryit .k')).evaluate(BOX),
'updated': await upd.evaluate(BOX)}
await pg.screenshot(path=str(OUT / 'screen-phone.png'))
json.dump(out, open(OUT / 'screens.json', 'w'), indent=1)
# Sample workflow page, 616 px wide (fits the 05b browser frame 1:1)
pg = await open_(b, RECIPE, 616, 1000, 2)
card = await (await pg.query_selector('#re-01')).evaluate(RECT)
kit = await (await pg.query_selector('#re-01-kit')).evaluate(RECT)
tri = await (await pg.query_selector('#re-01-tryit')).evaluate(RECT)
top = {'x': 0, 'y': card[1] - 14, 'width': 616, 'height': kit[1] + kit[3] - card[1] + 14}
low = {'x': 0, 'y': tri[1] - 2, 'width': 616, 'height': tri[3] + 14}
await pg.screenshot(path=str(OUT / 're01-top.png'), clip=top, full_page=True)
await pg.screenshot(path=str(OUT / 're01-tryit.png'), clip=low, full_page=True)
json.dump({'url': RECIPE, 'top': top, 'tryit': low}, open(OUT / 're01.json', 'w'), indent=1)
await b.close()
json.dump(out, open(HERE / 'assets-gen' / 'screens.json', 'w'), indent=1)
print(json.dumps(out))
asyncio.run(main())