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, "vh": 700,
"essay": [ "essay": [
328, 328,
95.21875, 95.40625,
201.921875, 198.921875,
29 29
], ],
"workflow": [ "workflow": [
357, 357,
364.09375, 364.28125,
200.046875, 200.109375,
37.625 37.625
] ]
}, },
@@ -20,14 +20,14 @@
"vh": 844, "vh": 844,
"tryit": [ "tryit": [
61, 61,
114.8125, 115.28125,
48.3125, 48.3125,
15 15
], ],
"updated": [ "updated": [
61, 61,
769.78125, 794.046875,
170.75, 170.765625,
19 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 | | 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 | | retreat-venue | 07b Retreat | 1360 × 765, landscape |
| site-orlando, site-north-denver, site-villa-park | 08c Academy sites | 800 × 600 each, 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 | | 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 | | 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"]}' 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>') 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 pages = [] # dicts: name, body, css, sel, w, h(optional), dpr
def page(name, body, css='', w=680, h=None, extra_css='', px_h=None): 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)) 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''' page('01b-welcome-cover-icons', f'''
<div class="canvas" id="shot"> <div class="canvas" id="shot">
<div class="wel"> <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"> <div class="wtxt">
<h2>Welcome</h2> <h2>Welcome</h2>
<p class="t">{TITLE}</p> <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>{icon("updated", "ico big")}<p>Library updated often</p></div>
</div> </div>
</div>''', css=''' </div>''', css='''
.wel{display:flex;gap:34px;align-items:center;padding:6px 6px 26px} .wel{display:flex;gap:30px;align-items:center;padding:0 6px 22px}
.bookwrap{flex:none;padding:6px 14px 14px 4px} .bookwrap{flex:none;margin:0;width:196px;text-align:center}
.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)} .bookwrap img{width:196px;display:block}
.book .slot{position:absolute;inset:0;border-radius:3px 8px 8px 3px} .bookwrap figcaption{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:-14px}
.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 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 .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 .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 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} .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'] # Published on the landing page: marketing-site/components/Contents.jsx ("The founding library")
page('04c-chapter-tiles-DRAFT', '<div class="canvas" id="shot"><p class="spec">Chapters</p><div class="tg">' + ''.join( CHAPTERS = [('Real Estate', 'Market briefs & listings, without leaking client data'),
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=''' ('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} .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{padding:16px 16px 18px;display:flex;flex-direction:column}
.t .n{font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.08em} .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:22px;letter-spacing:-.02em;line-height:1.12;margin-top:auto;padding-top:22px} .t h3{font-family:var(--display);font-weight:600;font-size:21px;letter-spacing:-.02em;line-height:1.12;margin:10px 0 6px}
.t .ico{position:absolute;right:16px;top:16px;width:22px;height:22px;opacity:.8} .t p{font-size:14px;line-height:1.4;color:var(--muted)}
''') ''')
# ---------------------------------------------------------------- 05 try one workflow # ---------------------------------------------------------------- 05 try one workflow
add_header('05') add_header('05')
CHECK = ['Use the included sample_listings.csv (no live client file).', page('05b-sample-workflow-try-it', '''<div class="canvas" id="shot">
'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="win">
<div class="bar"><i></i><i></i><i></i><span class="url">aibook.fun</span></div> <div class="bar"><i></i><i></i><i></i><span class="url">aibook.fun/read/recipe/RE-01</span></div>
<div class="scr"> <img src="../assets-gen/re01-top.png" alt="">
<article class="recipe"> <div class="gap"><span>Problem · Actors · Setup · Safeguards · Method · Evidence</span></div>
<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> <img src="../assets-gen/re01-tryit.png" alt="">
<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> </div>
</article> <p class="foot-note">Screenshots of sample workflow RE-01 at aibook.fun/read/recipe/RE-01</p>
</div> </div>''', css='''
</div> .win{background:var(--paper);border:1px solid var(--line-2);border-radius:14px;overflow:hidden;box-shadow:var(--sh-lg)}
<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{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 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)} .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)} .win img{display:block;width:614px}
.recipe{box-shadow:var(--sh-md);border-radius:16px} .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))}
.recipe-top{padding:14px 18px} .gap span{font-family:var(--mono);font-size:12.5px;color:var(--muted);background:var(--paper);padding:2px 10px;letter-spacing:.02em}
.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} .foot-note{text-align:center}
''') ''')
page('05c-button-try-the-workflow', f'''<div class="canvas" id="shot"> 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>' 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) for p, t, s, f in cloud_items)
if paperback: 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>' 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 '')) + ('<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>' 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 class="pr"><p class="price">Price on the reward</p></div>
</div> </div>
<div class="col card cloud"> <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 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="sec"><p class="lb">When</p><ul class="dl">{cloud_deliv}</ul></div>
<div class="pr"></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{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 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{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 small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec} .prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff} .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> <span class="co" data-c="updated" style="right:0;bottom:0">Last updated</span>
<svg class="lines"></svg> <svg class="lines"></svg>
</div> </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> </div>
<script> <script>
window.afterLayout = async () => {{ window.afterLayout = async () => {{
@@ -527,20 +503,24 @@ def lane(title, sub, ic, steps):
st = '' st = ''
for j, (i2, t, d) in enumerate(steps): for j, (i2, t, d) in enumerate(steps):
if j: st += f'<span class="ar">{ICONS["arrow"]}</span>' 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>' vis = f'<img class="cv" src="{i2[4:]}" alt="">' if i2.startswith('img:') else icon(i2, "ico ci")
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>' 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">' + page('12b-shipping-and-delivery-lanes', '<div class="canvas" id="shot">' +
lane('Digital rewards', 'Arrive by email', 'mail', [ lane('Digital rewards', 'Arrive by email', 'mail', [
('mail', 'Email', 'To the address on your Kickstarter account'), ('mail', 'Email', 'To the address on your Kickstarter account'),
('library', 'Library access', 'Within about 14 days of a successful campaign'), ('library', 'Library access', 'Within about 14 days of a successful campaign'),
('book', 'eBook', 'January 2027')]) + ('img:' + COVER_FLAT, 'eBook', 'January 2027')]) +
lane('Signed paperbacks', 'Ship in February 2027', 'box', [ 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'), ('survey', 'Survey', 'Kickstarter’s Pledge Manager asks for your address and charges shipping and any taxes'),
('printer', 'Printing', ''), ('printer', 'Printing', ''),
('pen', 'Signing', 'January 2027'), ('pen', 'Signing', 'January 2027'),
('truck', 'Shipping', 'February 2027')]) + ('truck', 'Shipping', 'February 2027')]) +
'</div>', css=''' '</div>', css='''
.lane{padding:16px 18px 18px} .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} .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} .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} .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( 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 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) 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"> page('14b-authors-and-team', f'''<div class="canvas" id="shot">
<p class="spec">The authors</p> <p class="spec">The authors</p>
<div class="ag"> <div class="ag">{AUTHOR_HTML}</div>
<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> <p class="spec" style="margin-top:22px">The team</p>
<div class="tg">{team_html}</div> <div class="tg">{team_html}</div>
</div>''', css=''' </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} .au{padding:14px 14px 18px}
.ap{aspect-ratio:1/1;border-radius:10px} .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} .ap .ph b{font-size:13px}
.ar{font-size:15px;color:var(--muted);margin-top:2px} .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} .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} .tm{padding:12px;display:flex;gap:11px;align-items:flex-start}
.tp{width:60px;height:60px;border-radius:999px;flex:none} .tp{width:60px;height:60px;border-radius:999px;flex:none}
@@ -1,10 +1,9 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"> <!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style> <link rel="stylesheet" href="../styles.css"><style>
.wel{display:flex;gap:34px;align-items:center;padding:6px 6px 26px} .wel{display:flex;gap:30px;align-items:center;padding:0 6px 22px}
.bookwrap{flex:none;padding:6px 14px 14px 4px} .bookwrap{flex:none;margin:0;width:196px;text-align:center}
.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)} .bookwrap img{width:196px;display:block}
.book .slot{position:absolute;inset:0;border-radius:3px 8px 8px 3px} .bookwrap figcaption{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:-14px}
.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 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 .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 .s{font-size:16px;line-height:1.5;color:var(--muted);max-width:25rem}
@@ -16,7 +15,7 @@
</style></head><body> </style></head><body>
<div class="canvas" id="shot"> <div class="canvas" id="shot">
<div class="wel"> <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"> <div class="wtxt">
<h2>Welcome</h2> <h2>Welcome</h2>
<p class="t">We Own AI — The New American Dream</p> <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"> <!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../reader/globals.css"><link rel="stylesheet" href="../styles.css"><style> <link rel="stylesheet" href="../styles.css"><style>
body{background:#fff} .win{background:var(--paper);border:1px solid var(--line-2);border-radius:14px;overflow:hidden;box-shadow:var(--sh-lg)}
.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{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 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)} .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)} .win img{display:block;width:614px}
.recipe{box-shadow:var(--sh-md);border-radius:16px} .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))}
.recipe-top{padding:14px 18px} .gap span{font-family:var(--mono);font-size:12.5px;color:var(--muted);background:var(--paper);padding:2px 10px;letter-spacing:.02em}
.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} .foot-note{text-align:center}
</style></head><body><div class="canvas" id="shot"> </style></head><body><div class="canvas" id="shot">
<div class="win"> <div class="win">
<div class="bar"><i></i><i></i><i></i><span class="url">aibook.fun</span></div> <div class="bar"><i></i><i></i><i></i><span class="url">aibook.fun/read/recipe/RE-01</span></div>
<div class="scr"> <img src="../assets-gen/re01-top.png" alt="">
<article class="recipe"> <div class="gap"><span>Problem · Actors · Setup · Safeguards · Method · Evidence</span></div>
<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> <img src="../assets-gen/re01-tryit.png" alt="">
<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> </div>
</article> <p class="foot-note">Screenshots of sample workflow RE-01 at aibook.fun/read/recipe/RE-01</p>
</div>
</div>
<p class="foot-note">Sample workflow RE-01, shown in the AiBook.fun reader.</p>
</div> </div>
<script> <script>
window.__ready = (async () => { 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{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 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{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 small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec} .prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff} .prices li.feat b{color:#fff}
@@ -42,8 +44,8 @@
<div class="pr"><p class="price">Price on the reward</p></div> <div class="pr"><p class="price">Price on the reward</p></div>
</div> </div>
<div class="col card cloud"> <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><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 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="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 class="pr"></div>
</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{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 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{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 small{display:block;font-size:13px;color:var(--muted)}
.prices li.feat{background:var(--pine);color:#eaf1ec} .prices li.feat{background:var(--pine);color:#eaf1ec}
.prices li.feat b{color:#fff} .prices li.feat b{color:#fff}
@@ -42,7 +44,7 @@
<div class="pr"><p class="price">Price on the reward</p></div> <div class="pr"><p class="price">Price on the reward</p></div>
</div> </div>
<div class="col card cloud"> <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 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="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 class="pr"></div>
@@ -20,16 +20,16 @@
</style></head><body><div class="canvas" id="shot"> </style></head><body><div class="canvas" id="shot">
<div class="stage"> <div class="stage">
<div class="laptop"><div class="lid"><div class="scr"><img src="../assets-gen/screen-laptop.png"> <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"> <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="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="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="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> <span class="co" data-c="updated" style="right:0;bottom:0">Last updated</span>
<svg class="lines"></svg> <svg class="lines"></svg>
</div> </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> </div>
<script> <script>
window.afterLayout = async () => { window.afterLayout = async () => {
@@ -1,6 +1,8 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"> <!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style> <link rel="stylesheet" href="../styles.css"><style>
.lane{padding:16px 18px 18px} .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} .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} .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} .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} .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 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} .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> <script>
window.__ready = (async () => { window.__ready = (async () => {
let empty = 0; let empty = 0;
@@ -1,10 +1,11 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"> <!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="../styles.css"><style> <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} .au{padding:14px 14px 18px}
.ap{aspect-ratio:1/1;border-radius:10px} .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} .ap .ph b{font-size:13px}
.ar{font-size:15px;color:var(--muted);margin-top:2px} .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} .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} .tm{padding:12px;display:flex;gap:11px;align-items:flex-start}
.tp{width:60px;height:60px;border-radius:999px;flex:none} .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} .tr{font-size:13px;line-height:1.35;color:var(--muted);margin-top:3px}
</style></head><body><div class="canvas" id="shot"> </style></head><body><div class="canvas" id="shot">
<p class="spec">The authors</p> <p class="spec">The authors</p>
<div class="ag"> <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>
<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> <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 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> </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. """Real screenshots of the live AiBook.fun reader (https://aibook.fun, deployed from WeOwnAI/aibook-fun main).
Writes assets-gen/screen-laptop.png, assets-gen/screen-phone.png and assets-gen/screens.json (target boxes)."""
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 import json, pathlib, asyncio
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
HERE = pathlib.Path(__file__).parent HERE = pathlib.Path(__file__).parent
URL = (HERE / 'reader' / 'chapter.html').as_uri() OUT = HERE / 'assets-gen'
# Box of the element's text (Range), so callout dots can sit just outside the words. CHAPTER = 'https://aibook.fun/read/real-estate'
BOX = """(sel) => { const el = document.querySelector(sel); const rg = document.createRange(); rg.selectNodeContents(el); RECIPE = 'https://aibook.fun/read/recipe/RE-01'
const r = rg.getBoundingClientRect(); return [r.left, r.top, r.width, r.height]; }""" # 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(): async def main():
out = {} out = {}
async with async_playwright() as p: async with async_playwright() as p:
b = await p.chromium.launch(executable_path='/usr/bin/google-chrome') b = await p.chromium.launch(executable_path='/usr/bin/google-chrome')
# Laptop: desktop layout, scrolled to the last essay + first workflow # Laptop: desktop layout at the last essay + first workflow
pg = await b.new_page(viewport={'width': 1120, 'height': 700}, device_scale_factor=2) pg = await open_(b, CHAPTER, 1120, 700, 2)
await pg.goto(URL); await pg.evaluate('document.fonts.ready') await scroll_to(pg, '#practice', 96)
await pg.evaluate("document.getElementById('ol-practice').classList.add('is-active')") out['laptop'] = {'vw': 1120, 'vh': 700,
await pg.evaluate("window.scrollTo(0, document.getElementById('practice').getBoundingClientRect().top + scrollY - 96)") 'essay': await (await pg.query_selector('#practice h2')).evaluate(BOX),
await pg.wait_for_timeout(200) 'workflow': await (await pg.query_selector('#re-01 .recipe-id')).evaluate(BOX)}
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(OUT / 'screen-laptop.png'))
await pg.screenshot(path=str(HERE / 'assets-gen' / 'screen-laptop.png')) # Phone: mobile layout at the first Try-it
# Phone: mobile layout, scrolled to the Try-it row pg = await open_(b, CHAPTER, 390, 844, 3)
pg = await b.new_page(viewport={'width': 390, 'height': 844}, device_scale_factor=3) await scroll_to(pg, '#re-01-tryit', 92)
await pg.goto(URL); await pg.evaluate('document.fonts.ready') upd = await pg.evaluate_handle("[...document.querySelectorAll('#re-01-record .recipe-record span')].find(s => s.textContent.startsWith('Last updated'))")
await pg.evaluate("window.scrollTo(0, document.getElementById('re-01-tryit').getBoundingClientRect().top + scrollY - 92)") out['phone'] = {'vw': 390, 'vh': 844,
await pg.wait_for_timeout(200) 'tryit': await (await pg.query_selector('#re-01-tryit .k')).evaluate(BOX),
out['phone'] = {'vw': 390, 'vh': 844, 'tryit': await pg.evaluate(BOX, '#re-01-tryit .k'), 'updated': await pg.evaluate(BOX, '#re-01-updated')} 'updated': await upd.evaluate(BOX)}
await pg.screenshot(path=str(HERE / 'assets-gen' / 'screen-phone.png')) 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() await b.close()
json.dump(out, open(HERE / 'assets-gen' / 'screens.json', 'w'), indent=1)
print(json.dumps(out)) print(json.dumps(out))
asyncio.run(main()) asyncio.run(main())