sabaridsnfuji's picture
download
raw
15.5 kB
<!DOCTYPE html>
<!--
============================================================
posterly — Hello World example
PURPOSE: smallest poster that passes all four gates
(preflight, measure, polish --strict, verify-final).
CANVAS: A2 portrait (420 × 594 mm), single card, no MathJax,
no external images — renders fully offline, single
page, fills the canvas.
The example is a single card FILLED with real content -- not
stretched with `flex: 1` + blank space to fake a full page (see
SKILL.md "Gate C -- the same trap, one card": a half-empty
stretched card passes every gate yet reads as a failed poster).
It stays single-card so it's robust to font / Chromium revision
drift; multi-card alignment is the templates/ gallery's job, not
this install-verification fixture's.
Use:
cd examples/hello_world
python ../../tools/poster_check.py preflight poster.html
python ../../tools/poster_check.py measure poster.html
python ../../tools/poster_check.py polish poster.html --strict
python ../../tools/render_preview.py poster.html
python ../../tools/poster_check.py verify-final poster_preview.pdf --from-html poster.html
Expect five lines confirming success.
============================================================
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>posterly — Hello World</title>
<style>
@page { size: A2 portrait; margin: 0; }
:root {
--accent: #2D5F8B;
--accent-deep: #1F4566;
--accent-light: #E8F1F8;
--gold: #C9A24A;
--text-primary: #1A1A1A;
--text-muted: #777;
--bg-page: #F6F3F0;
--bg-card: #FFFFFF;
--border-soft: #D8D8D8;
--u: 1.6px;
--font-sans: "Inter","Helvetica Neue",sans-serif;
--font-serif: "Charter","Source Serif Pro","Georgia",serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
/* BASE DEFENSES (see templates/*_neutral.html for the annotated
version): orphan/widow wrap protection on prose, balance on the
centered title. Keep this block in any skeleton, incl. custom. */
p, li, dd, figcaption,
.body-text, .caption, .callout, .section-title { text-wrap: pretty; }
.title { text-wrap: balance; }
html, body { background: #2b2b2b; font-family: var(--font-serif); color: var(--text-primary); }
.poster {
width: calc(420 * var(--u));
height: calc(594 * var(--u));
background: var(--bg-page);
margin: 20px auto;
padding: calc(8 * var(--u)) calc(12 * var(--u));
display: grid;
grid-template-rows: auto 1fr auto auto;
/* 10 mm row-gap → 38 px in print mode (--u: 1mm), comfortably inside
the [30, 50] px target for measure's gap-to-footer-strip gate. */
gap: calc(10 * var(--u));
box-shadow: 0 0 40px rgba(0,0,0,0.4);
position: relative;
overflow: hidden;
}
.poster::before {
content: "";
position: absolute; top: 0; left: 0; right: 0;
height: calc(4 * var(--u));
background: linear-gradient(90deg, var(--accent-deep), var(--accent), var(--gold));
}
/* ---- header ---- */
.header {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: calc(8 * var(--u));
padding: calc(2 * var(--u)) calc(2 * var(--u)) calc(4 * var(--u));
border-bottom: calc(1 * var(--u)) solid var(--accent);
}
.title-block { min-width: 0; }
.title {
font-family: var(--font-sans);
font-weight: 800;
font-size: calc(20 * var(--u));
line-height: 1.08;
color: var(--accent-deep);
letter-spacing: -0.4px;
}
.title .accent { color: var(--gold); }
.subtitle {
font-family: var(--font-sans);
font-size: calc(9 * var(--u));
color: var(--text-muted);
margin-top: calc(1 * var(--u));
}
.authors {
font-family: var(--font-sans);
font-size: calc(8 * var(--u));
color: var(--accent);
font-weight: 600;
margin-top: calc(2 * var(--u));
}
.qr-block { display: flex; flex-direction: column; align-items: center; gap: calc(1 * var(--u)); }
.qr-block img {
width: calc(28 * var(--u));
height: calc(28 * var(--u));
border: calc(0.5 * var(--u)) solid var(--accent);
border-radius: calc(1.5 * var(--u));
background: white;
padding: calc(0.5 * var(--u));
}
.qr-label { font-family: var(--font-sans); font-size: calc(6 * var(--u)); color: var(--accent); font-weight: 600; }
/* ---- body (single column, single card filling the row) ---- */
.body-grid {
display: grid;
grid-template-columns: 1fr;
min-height: 0;
}
.column {
display: flex;
flex-direction: column;
min-height: 0;
}
.card {
background: var(--bg-card);
border-radius: calc(2 * var(--u));
padding: calc(6 * var(--u)) calc(8 * var(--u));
border: calc(0.5 * var(--u)) solid var(--border-soft);
box-shadow: 0 calc(1 * var(--u)) calc(2 * var(--u)) rgba(0,0,0,0.04);
border-left: calc(3 * var(--u)) solid var(--accent);
flex: 1;
display: flex;
flex-direction: column;
}
.section-title {
font-family: var(--font-sans);
font-weight: 700;
font-size: calc(11 * var(--u));
color: var(--accent-deep);
margin-bottom: calc(2 * var(--u));
}
.body-text {
font-family: var(--font-serif);
font-size: calc(7 * var(--u));
line-height: 1.35;
color: var(--text-primary);
}
.body-text + .body-text { margin-top: calc(1.5 * var(--u)); }
.keyword { color: var(--accent); font-weight: 700; }
.card ul { padding-left: calc(11 * var(--u)); margin-top: calc(1 * var(--u)); }
.card li { font-family: var(--font-serif); font-size: calc(7 * var(--u)); line-height: 1.35; margin-top: calc(0.5 * var(--u)); }
.figure { margin-top: calc(3 * var(--u)); display: flex; flex-direction: column; align-items: center; }
/* 80% width on an AR=2 figure → tall figures would be too big, but
AR=2 wide fits comfortably AND satisfies polish Gate A's wide-min
ratio (must be >= 65% of card width). */
/* Natural aspect ratio (AR=2), no max-height cap: the figure carries
real vertical space instead of leaving the card half-blank, and
stays >= 65% width so polish Gate A doesn't flag it as too small. */
.figure img { width: 70%; display: block; }
.figure .caption { font-family: var(--font-sans); font-size: calc(6 * var(--u)); color: var(--text-muted); margin-top: calc(1 * var(--u)); text-align: center; line-height: 1.2; }
/* ---- footer-strip ---- */
.footer-strip {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: calc(4 * var(--u));
background: var(--accent-deep);
color: white;
border-radius: calc(2 * var(--u));
padding: calc(3 * var(--u)) calc(5 * var(--u));
}
.hs-stat { display: flex; flex-direction: column; align-items: center; gap: calc(0.5 * var(--u)); }
.hs-stat .num {
font-family: var(--font-sans);
font-weight: 800;
font-size: calc(12 * var(--u));
color: var(--gold);
white-space: nowrap;
}
.hs-stat .lbl {
font-family: var(--font-sans);
font-size: calc(6 * var(--u));
color: var(--accent-light);
text-align: center;
}
/* ---- footer ---- */
.footer {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: calc(8 * var(--u));
font-family: var(--font-sans);
font-size: calc(6 * var(--u));
color: var(--text-muted);
padding: calc(1 * var(--u)) calc(2 * var(--u));
}
.footer .right { color: var(--accent); font-weight: 600; }
/* =========================================================
PRINT OVERRIDE — KEEP LAST so it wins source-order ties.
Without `:root { --u: 1mm }` the poster keeps the 1.6 px /
unit screen scale and renders into ~42 % of A2 — the column
spread gate would still pass on the shrunken poster, but
the printed PDF would show a giant gray border. measure
hard-fails this via `--min-canvas-fill`. The explicit
`html, body { height: 100% }` block dodges Chromium's
well-known "round content height to next page" quirk that
otherwise emits a phantom second page.
========================================================= */
@media print {
:root { --u: 1mm; }
html, body {
background: white;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.poster { margin: 0; box-shadow: none; page-break-after: avoid; }
}
</style>
</head>
<body>
<div class="poster" data-measure-role="poster">
<header class="header" data-measure-role="header">
<div class="title-block">
<h1 class="title">Hello, <span class="accent">posterly</span></h1>
<div class="subtitle">A minimal poster that passes every gate.</div>
<div class="authors">A. Researcher &middot; B. Collaborator &middot; C. Author<sup>&#9993;</sup></div>
</div>
<div class="qr-block">
<!-- Inline SVG QR placeholder so the template renders offline.
Replace with a real QR before printing:
$ qrencode -o qr.png -s 12 "<your-url>"
$ python -c "import qrcode; qrcode.make('<your-url>').save('qr.png')" -->
<img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' fill='%23fff'/><g fill='%23222'><rect x='4' y='4' width='26' height='26'/><rect x='70' y='4' width='26' height='26'/><rect x='4' y='70' width='26' height='26'/></g><g fill='%23fff'><rect x='10' y='10' width='14' height='14'/><rect x='76' y='10' width='14' height='14'/><rect x='10' y='76' width='14' height='14'/></g><g fill='%23222'><rect x='14' y='14' width='6' height='6'/><rect x='80' y='14' width='6' height='6'/><rect x='14' y='80' width='6' height='6'/><rect x='38' y='10' width='4' height='4'/><rect x='48' y='10' width='4' height='4'/><rect x='58' y='10' width='4' height='4'/><rect x='34' y='34' width='4' height='4'/><rect x='44' y='38' width='4' height='4'/><rect x='54' y='34' width='4' height='4'/><rect x='62' y='40' width='4' height='4'/><rect x='38' y='46' width='4' height='4'/><rect x='50' y='52' width='4' height='4'/><rect x='62' y='56' width='4' height='4'/><rect x='34' y='60' width='4' height='4'/><rect x='44' y='66' width='4' height='4'/><rect x='56' y='62' width='4' height='4'/><rect x='80' y='80' width='4' height='4'/><rect x='88' y='86' width='4' height='4'/></g><text x='50' y='54' text-anchor='middle' font-family='sans-serif' font-size='6' font-weight='bold' fill='%23888'>QR</text></svg>" alt="QR placeholder">
<div class="qr-label">Paper</div>
</div>
</header>
<div class="body-grid" data-measure-role="body">
<div class="column" data-measure-role="column">
<div class="card" data-measure-role="card">
<div class="section-title">What this is</div>
<p class="body-text">
<span class="keyword">posterly</span> is a Claude Code skill that builds academic conference posters as a single HTML/CSS file rendered to print-ready PDF via headless Chromium. You iterate by <em>measuring</em>, not eyeballing &mdash; the screen preview lies; only <code>emulate_media("print")</code> at the exact viewport tells the truth.
</p>
<div class="figure">
<img alt="A bar chart placeholder" src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 120'><rect width='240' height='120' fill='%23F6F3F0'/><g fill='%232D5F8B'><rect x='15' y='80' width='28' height='30'/><rect x='55' y='55' width='28' height='55'/><rect x='95' y='35' width='28' height='75'/><rect x='135' y='25' width='28' height='85'/></g><g fill='%23C9A24A'><rect x='175' y='12' width='28' height='98'/></g><line x1='10' y1='110' x2='220' y2='110' stroke='%23555' stroke-width='1'/><text x='189' y='8' font-family='sans-serif' font-size='8' fill='%23C9A24A' font-weight='bold' text-anchor='middle'>ours</text></svg>">
<div class="caption">Hypothetical headline result. Last bar is &ldquo;ours&rdquo; in gold.</div>
</div>
<div class="section-title" style="margin-top: calc(4 * var(--u))">How the gates work</div>
<ul>
<li><b>preflight</b> &mdash; static lint: LaTeX residue, raw <code>&lt;</code> inside math, missing local images, role validation.</li>
<li><b>measure</b> &mdash; print-emulated geometry: column-bottom spread (&lt; 5 px), gap to next strip ([30, 50] px), poster bbox aligns to page within &plusmn;2 px.</li>
<li><b>polish</b> &mdash; soft visual checks: figure-AR sizing, broken images, typography orphans, space-between fill.</li>
<li><b>verify-final</b> &mdash; <code>pdfinfo</code>-based PDF sanity: page count, dimensions, file size.</li>
</ul>
<div class="section-title" style="margin-top: calc(4 * var(--u))">Why measure, not eyeball</div>
<p class="body-text">
The screen preview <em>lies</em>: <code>@media print</code> uses a different unit scale, web fonts substitute, and a column that looks aligned on screen can sit 20&nbsp;px off in the printed PDF. <span class="keyword">posterly</span> renders at the exact print viewport and reports numbers &mdash; so &ldquo;looks aligned&rdquo; becomes <code>spread = 0.0 px</code>, or it doesn't.
</p>
<ul>
<li><b>Fill, don't stretch.</b> A card padded out with <code>flex: 1</code> clears the bottom-edge gate while half of it is blank &mdash; balance it with real content, never whitespace.</li>
<li><b>Autocrop figures</b> so the image fills its card instead of floating in a gray margin.</li>
<li><b>Guard trailing glyphs</b> (<code>&times;</code>, <code>&uarr;</code>, <code>%</code>) with <code>white-space: nowrap</code> so a lone symbol never orphans onto its own line.</li>
</ul>
<div class="section-title" style="margin-top: calc(4 * var(--u))">What's in the box</div>
<ul>
<li><b>templates/</b> &mdash; portrait 2-column, landscape 4-column, and hero gallery, each pre-wired with the measurement roles.</li>
<li><b>tools/</b> &mdash; the four-gate <code>poster_check.py</code> CLI plus a standalone <code>render_preview.py</code>.</li>
<li><b>examples/</b> &mdash; this file: the smallest poster that clears every gate, your install check.</li>
</ul>
<p class="body-text" style="margin-top: calc(3 * var(--u))">
This example demonstrates every measurement role the gates look for: <span class="keyword">poster, header, body, column, card, footer-strip, footer</span>. For real multi-column posters, copy a template from <code>templates/</code> instead.
</p>
</div>
</div>
</div>
<div class="footer-strip" data-measure-role="footer-strip">
<div class="hs-stat">
<div class="num">4 gates</div>
<div class="lbl">preflight, measure, polish, verify-final</div>
</div>
<div class="hs-stat">
<div class="num">&lt; 5 px</div>
<div class="lbl">column-bottom spread target</div>
</div>
<div class="hs-stat">
<div class="num">A0&ndash;A4</div>
<div class="lbl">named canvas sizes supported</div>
</div>
</div>
<footer class="footer" data-measure-role="footer">
<div>posterly &mdash; build academic posters as HTML, render to print-ready PDF.</div>
<div class="right">github.com/Chenruishuo/posterly</div>
</footer>
</div>
</body>
</html>

Xet Storage Details

Size:
15.5 kB
·
Xet hash:
ef1e4c2be4186209e373ba80192074bb4d6af53ac89763499945d4bd43a3d9b6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.