BrewPage report playground

HTML

<!-- This demo example is MIT licensed. The BrewPage service remains proprietary. -->
<main>
<a class="brand" href="https://brewpage.app" target="_blank" rel="noopener noreferrer"><span class="mark" aria-hidden="true">B</span> BrewPage</a>
<h1>Turn a report into a page.</h1>
<p class="intro">Edit a little HTML. Preview it here. Download your report, then publish it from your terminal for a link you can share.</p>
<div class="workspace">
<section class="panel" aria-labelledby="source-label"><div class="panel-head"><label id="source-label" for="source">01 / Your HTML</label><span class="hint">Changes preview as you type</span></div><textarea id="source" spellcheck="false" aria-describedby="preview-note"></textarea></section>
<section class="panel" aria-labelledby="preview-label"><div class="panel-head"><h2 id="preview-label">02 / Live preview</h2><span class="hint">Sandboxed · local</span></div><iframe id="preview" title="Report preview" sandbox="" referrerpolicy="no-referrer"></iframe></section>
</div>
<p class="preview-note" id="preview-note">Preview scripts and external assets are blocked. The download contains your original HTML. Your edits stay in this page and are lost on reload.</p>
<section class="panel share" aria-labelledby="share-label"><h2 id="share-label">03 / Ready to share?</h2><p>Download <strong>report.html</strong>, open a terminal in its folder, then run this command. It publishes to BrewPage’s public namespace. Anyone can read it, and it appears in the public gallery. No signup or API key is needed.</p><div class="actions"><button class="primary" id="download" type="button">Download report.html</button><button id="copy" type="button">Copy curl command</button></div><pre id="command" tabindex="0" aria-label="Terminal command"></pre><p id="status" role="status" aria-live="polite"></p><p class="hint">The API returns a share link and owner credentials. Keep those credentials private to update or delete your page. Review the <a...

CSS

:root{color-scheme:light dark;--bg:#f7f7f5;--panel:#fff;--ink:#202323;--muted:#626866;--line:#d9deda;--accent:#ebbb40}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 system-ui,sans-serif}main{max-width:1320px;margin:auto;padding:40px 28px}a{color:inherit;text-underline-offset:3px}a:hover{text-decoration-thickness:2px}.brand{display:inline-flex;align-items:center;gap:9px;font-weight:750;text-decoration:none}.mark{display:grid;place-items:center;width:30px;height:30px;background:var(--accent);color:#202323;border-radius:8px}h1{margin:22px 0 8px;font-size:clamp(30px,4vw,46px);line-height:1.12;letter-spacing:-1.5px}p{margin:0 0 16px}.intro{max-width:700px;color:var(--muted);font-size:18px}.workspace{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:28px}.panel{min-width:0;background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--line)}.panel-head label,h2{font-size:15px;font-weight:700;margin:0}.hint{color:var(--muted);font-size:13px}textarea{display:block;width:100%;height:430px;padding:18px;resize:vertical;border:0;background:var(--panel);color:var(--ink);font:13px/1.65 ui-monospace,monospace;tab-size:2}textarea:focus{outline:3px solid var(--accent);outline-offset:-3px}iframe{display:block;width:100%;height:430px;border:0;background:#fff}.preview-note{margin:12px 0 0;color:var(--muted);font-size:13px}.share{margin-top:28px;padding:22px}.share h2{font-size:21px;margin-bottom:8px}.share p{color:var(--muted);max-width:850px}.actions{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}button{min-height:44px;border:1px solid var(--line);border-radius:8px;padding:10px 16px;background:var(--panel);color:var(--ink);font:600 14px...

JavaScript

(() => {
const source = document.getElementById('source');
const preview = document.getElementById('preview');
const status = document.getElementById('status');
const command = "curl --fail-with-body -X POST 'https://brewpage.app/api/html?ns=public&format=html' \\\n  -H 'User-Agent: ReportPlayground/1.0' \\\n  -H 'Content-Type: text/html; charset=utf-8' \\\n  --data-binary @report.html";
const policy = '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; style-src \'unsafe-inline\'; img-src data:; form-action \'none\'; base-uri \'none\'">';
source.value = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Weekly project report</title>
<style>
body { margin:0; background:#f4f6f4; color:#243a31; font:16px/1.6 system-ui,sans-serif; }
main { max-width:620px; margin:auto; padding:36px 24px; }
.tag { color:#4d6559; font-size:13px; text-transform:uppercase; letter-spacing:2px; }
h1 { margin:8px 0; font-size:36px; line-height:1.15; }
.summary { background:white; border:1px solid #dbe3dd; border-radius:12px; padding:20px; margin:24px 0; }
h2 { margin:0 0 8px; font-size:20px; }
ul { margin:0; padding-left:20px; }
footer { color:#596c61; font-size:13px; }
</style>
</head>
<body>
<main>
<p class="tag">Project journal / Week 12</p>
<h1>Small steps.<br>Useful progress.</h1>
<p>A simple weekly update for your team. Replace this sample with your own findings.</p>
<section class="summary">
<h2>What shipped</h2>
<ul><li>A clearer onboarding checklist</li><li>Three customer interviews, summarized</li><li>A working prototype for the next milestone</li></ul>
</section>
<h2>Up next</h2>
<p>Test the prototype with the team and choose one improvement to ship next week.</p>
<footer>Sample content · Edit freely</footer>
</main>
</body>
</html>`;
const updatePreview = () => { preview.srcdoc = policy + source.value;...