Files

252 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 your main roster and, when needed, its nursery-only roster. Confirm the month,
then download one formatted image—the PDFs are identified automatically.
</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" multiple>
<span class="upload-mark" aria-hidden="true">↑</span>
<span class="drop-title" id="drop-title">Choose your roster PDFs</span>
<span class="drop-copy" id="drop-copy">or drag them here · one main roster, optional nursery roster</span>
<span class="drop-requirements">One-page landscape Letter PDFs · up to __MAX_UPLOAD_MB__ MB each</span>
<span class="selected-files" id="selected-files" aria-live="polite" hidden></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>Positions use PDF points; scales are multipliers. 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>Single-PDF roster</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>
<p class="combined-settings-note">
The section controls below apply when two PDFs are combined. Each roster can be moved or scaled independently.
</p>
<fieldset>
<legend>Main roster</legend>
<div class="setting-grid setting-grid--three">
<label>
X position
<input name="main_x" type="number" value="__MAIN_X__" data-default="__MAIN_X__" data-factory="__FACTORY_MAIN_X__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Y position
<input name="main_y" type="number" value="__MAIN_Y__" data-default="__MAIN_Y__" data-factory="__FACTORY_MAIN_Y__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Scale
<input name="main_scale" type="number" value="__MAIN_SCALE__" data-default="__MAIN_SCALE__" data-factory="__FACTORY_MAIN_SCALE__" data-layout-input step="0.01" min="0.1" max="2" required>
</label>
</div>
</fieldset>
<fieldset>
<legend>Nursery-only roster</legend>
<div class="setting-grid setting-grid--three">
<label>
X position
<input name="nursery_x" type="number" value="__NURSERY_X__" data-default="__NURSERY_X__" data-factory="__FACTORY_NURSERY_X__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Y position
<input name="nursery_y" type="number" value="__NURSERY_Y__" data-default="__NURSERY_Y__" data-factory="__FACTORY_NURSERY_Y__" data-layout-input step="1" min="-1000" max="2000" required>
</label>
<label>
Scale
<input name="nursery_scale" type="number" value="__NURSERY_SCALE__" data-default="__NURSERY_SCALE__" data-factory="__FACTORY_NURSERY_SCALE__" data-layout-input step="0.01" min="0.1" max="2" 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>