V2: Added Webserver for faster formatting

This commit is contained in:
Media
2026-07-25 15:13:23 -05:00
parent eed911cffa
commit 3eab672c6c
22 changed files with 3737 additions and 128 deletions
+209
View File
@@ -0,0 +1,209 @@
<!doctype html>
<html lang="en" data-theme="system">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#102d3d">
<title>Roster Formatter</title>
<script src="/static/theme.js"></script>
<link rel="stylesheet" href="/static/styles.css">
<script src="/static/app.js" defer></script>
</head>
<body
data-default-month="__DEFAULT_MONTH__"
data-retention-months="__RETENTION_MONTHS__"
data-max-upload-mb="__MAX_UPLOAD_MB__"
>
<header class="site-header">
<a class="brand" href="/" aria-label="Roster Formatter home">
<img src="/assets/logo.png" alt="River of Glory Church">
</a>
<div class="header-actions">
<div class="theme-switch" role="group" aria-label="Color theme">
<button class="theme-option" type="button" data-theme-choice="system" aria-pressed="true">System</button>
<button class="theme-option" type="button" data-theme-choice="light" aria-pressed="false">Light</button>
<button class="theme-option" type="button" data-theme-choice="dark" aria-pressed="false">Dark</button>
</div>
<span class="local-badge"><span aria-hidden="true"></span> Local &amp; private</span>
</div>
</header>
<main>
<section class="intro" aria-labelledby="page-title">
<p class="eyebrow">Roster formatter</p>
<h1 id="page-title">From current roster to ready-to-share PNG.</h1>
<p class="lede">
Upload the one-page PDF, confirm its month, and download the formatted image.
The current layout is preserved unless you intentionally change an advanced value.
</p>
<div class="format-facts" aria-label="Output details">
<span>PNG</span>
<span>1650 × 1275 px</span>
<span>150 DPI</span>
</div>
</section>
<div class="workspace">
<section class="card form-card" aria-labelledby="format-heading">
<div class="card-heading">
<div>
<p class="step-label">New export</p>
<h2 id="format-heading">Format a roster</h2>
</div>
<span class="defaults-status" id="defaults-status">Saved defaults</span>
</div>
<form id="roster-form" action="/format" method="post" enctype="multipart/form-data">
<label class="drop-zone" id="drop-zone" for="roster-file">
<input id="roster-file" name="roster" type="file" accept="application/pdf,.pdf">
<span class="upload-mark" aria-hidden="true">↑</span>
<span class="drop-title" id="drop-title">Choose your roster PDF</span>
<span class="drop-copy" id="drop-copy">or drag it here · one-page landscape Letter · up to __MAX_UPLOAD_MB__ MB</span>
</label>
<div class="field-block">
<label for="month">Roster month</label>
<div class="month-row">
<input id="month" name="month" type="month" value="__DEFAULT_MONTH__" required>
<button class="text-button" id="suggested-month" type="button">Use suggested</button>
</div>
<p class="field-help">This controls the label and downloaded filename.</p>
</div>
<details class="advanced" id="advanced-settings">
<summary>
<span>
<strong>Advanced adjustments</strong>
<small>Use only when this month needs a nudge.</small>
</span>
<span class="chevron" aria-hidden="true">⌄</span>
</summary>
<div class="advanced-body">
<div class="advanced-heading">
<p>Values use PDF points. Saved defaults prefill every new roster.</p>
<div class="layout-actions">
<button class="text-button" id="reset-layout" type="button">Reset changes</button>
<button class="text-button" id="factory-layout" type="button">Use original values</button>
</div>
</div>
<fieldset>
<legend>Roster content</legend>
<div class="setting-grid">
<label>
Vertical shift
<input name="content_shift" type="number" value="__CONTENT_SHIFT__" data-default="__CONTENT_SHIFT__" data-factory="__FACTORY_CONTENT_SHIFT__" data-layout-input step="1" min="-300" max="500" required>
</label>
<label>
Bottom trim
<input name="bottom_margin" type="number" value="__BOTTOM_MARGIN__" data-default="__BOTTOM_MARGIN__" data-factory="__FACTORY_BOTTOM_MARGIN__" data-layout-input step="1" min="0" max="500" required>
</label>
</div>
</fieldset>
<fieldset>
<legend>Logo</legend>
<div class="setting-grid setting-grid--three">
<label>
X position
<input name="logo_x" type="number" value="__LOGO_X__" data-default="__LOGO_X__" data-factory="__FACTORY_LOGO_X__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Y position
<input name="logo_y" type="number" value="__LOGO_Y__" data-default="__LOGO_Y__" data-factory="__FACTORY_LOGO_Y__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Scale
<input name="logo_scale" type="number" value="__LOGO_SCALE__" data-default="__LOGO_SCALE__" data-factory="__FACTORY_LOGO_SCALE__" data-layout-input step="0.01" min="0.01" max="2" required>
</label>
</div>
</fieldset>
<fieldset>
<legend>Date label</legend>
<div class="setting-grid setting-grid--three">
<label>
X position
<input name="date_x" type="number" value="__DATE_X__" data-default="__DATE_X__" data-factory="__FACTORY_DATE_X__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Y position
<input name="date_y" type="number" value="__DATE_Y__" data-default="__DATE_Y__" data-factory="__FACTORY_DATE_Y__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Font size
<input name="date_font_size" type="number" value="__DATE_FONT_SIZE__" data-default="__DATE_FONT_SIZE__" data-factory="__FACTORY_DATE_FONT_SIZE__" data-layout-input step="1" min="8" max="200" required>
</label>
</div>
</fieldset>
<div class="save-defaults-row">
<p id="layout-message" role="status" aria-live="polite" hidden></p>
<button class="secondary-button save-defaults-button" id="save-layout-defaults" type="button">Save as defaults</button>
</div>
<p class="canvas-lock">
The page canvas and 150-DPI export stay fixed, even when these values change.
</p>
</div>
</details>
<div class="form-message" id="form-message" role="alert" hidden></div>
<button class="primary-button" id="format-button" type="submit">
<span class="button-label">Format roster</span>
<span class="button-arrow" aria-hidden="true">→</span>
</button>
</form>
</section>
<aside class="side-column">
<section class="card result-card" id="result-card" aria-labelledby="result-heading" aria-live="polite" tabindex="-1" hidden>
<div class="result-heading">
<div>
<p class="step-label">Ready</p>
<h2 id="result-heading">Your formatted roster</h2>
</div>
<span class="success-mark" aria-hidden="true">✓</span>
</div>
<div class="preview-shell">
<img id="result-preview" alt="Preview of the formatted roster">
</div>
<p class="result-name" id="result-name"></p>
<div class="result-actions">
<a class="primary-button" id="download-result" href="#" download>Download PNG</a>
<button class="secondary-button" id="format-another" type="button">Format another</button>
</div>
</section>
<section class="card history-card" aria-labelledby="history-heading">
<div class="card-heading compact-heading">
<div>
<p class="step-label">Saved locally</p>
<h2 id="history-heading">Recent rosters</h2>
</div>
<button class="icon-button" id="refresh-history" type="button" aria-label="Refresh recent rosters">↻</button>
</div>
<div id="history-list" class="history-list" aria-live="polite">
<p class="empty-state">Loading saved rosters…</p>
</div>
<p class="retention-note" id="retention-note">
Saved roster PNGs outside the __RETENTION_MONTHS__-month window are removed automatically.
</p>
</section>
<section class="privacy-note" aria-label="Privacy information">
<span aria-hidden="true">⌂</span>
<p>
Processing happens on this machine. Uploads and working PDFs are deleted as soon as the PNG is made.
</p>
</section>
</aside>
</div>
</main>
<footer>
<span>River of Glory · Roster tools</span>
<span>Fixed 11 × 8.5 in canvas</span>
</footer>
</body>
</html>