Files
Waters ChurchandClaude Opus 5 b03fb252d8
Build apps / build (push) Failing after 35s
Deploy web to NAS / deploy (push) Successful in 1m21s
Note in index.html how a change here reaches the web and the apps
This directory feeds both targets by different routes, and the difference is the
thing people get wrong: the website serves it directly, while the apps carry a
copy frozen at build time. Someone editing here reasonably assumes a deploy is
enough, and for the website it is.

Written at the top of the file rather than in a README because this is where the
assumption gets made.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 15:50:35 -04:00

807 lines
52 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>
<!-- This directory is the source of truth for both the website and the apps. The website
serves it directly; mobile/scripts/sync-web.js copies it into the iOS and Android
bundles at build time, so the apps carry a FROZEN copy taken when they were last built.
A change here therefore reaches the two in different ways: pushing to main redeploys
the website within a minute, and separately rebuilds both apps on the ingest Mac and
emails that they are behind — but nothing reaches a phone until someone runs
./release.sh --ios --android and the stores approve it. -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<!-- Stop iOS Safari's data detectors from auto-linking IPs/ports/credentials in
task descriptions as "phone numbers" — that drew a stray underline and hijacked
taps into the call/share sheet. Nothing in this app is ever a phone number. -->
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<title>Recurring Checklist</title>
<!-- Inter — modern productivity typeface (falls back to the system stack offline) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="styles.css">
<!-- PWA / installable web app -->
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#1d2026">
<meta name="color-scheme" content="dark">
<!-- iOS Safari support -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Checklist">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png?v=2">
<!-- Favicons (v2 = new checkmark logo; query busts the browser's favicon cache) -->
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg?v=2">
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png?v=2">
</head>
<body>
<div id="loadingOverlay" class="loading-overlay">
<div class="loading-spinner"></div>
<div class="loading-text">Loading checklist...</div>
</div>
<!-- Shown when offline or while queued check-offs are syncing. -->
<div id="offlineBadge" class="offline-badge" style="display:none;"></div>
<!-- First-launch setup, native apps only. The store build ships to anyone, so the
server is a per-install choice rather than something baked into the binary. -->
<div id="setupOverlay" class="setup-overlay" style="display:none;">
<div class="setup-card">
<div class="setup-logo" aria-hidden="true">
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<defs><linearGradient id="sg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#39a8ff"/><stop offset="1" stop-color="#2ecc71"/>
</linearGradient></defs>
<rect width="512" height="512" rx="112" fill="url(#sg)"/>
<path d="M150 262 L224 338 L366 178" fill="none" stroke="#fff" stroke-width="46"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<h2>Set up Checklist</h2>
<p class="setup-sub">How do you want to use it? You can change this later.</p>
<div id="setupChoices">
<button type="button" class="setup-choice" data-setup="local">
<span class="setup-choice-title">Just on this device</span>
<span class="setup-choice-desc">Everything stays on your phone. No server,
no account, works offline &mdash; but only you see it.</span>
</button>
<button type="button" class="setup-choice" data-setup="join">
<span class="setup-choice-title">Join a server</span>
<span class="setup-choice-desc">Your church already runs one. Everyone on your
team shares the same lists.</span>
</button>
<button type="button" class="setup-choice" data-setup="host">
<span class="setup-choice-title">Host it yourself</span>
<span class="setup-choice-desc">Run it on your own computer or NAS. Your data
stays on your hardware.</span>
</button>
</div>
<!-- Address entry, shared by both choices above. -->
<div id="setupJoin" style="display:none;">
<label for="setupAddress">Server address</label>
<input id="setupAddress" type="url" inputmode="url" autocapitalize="off"
autocorrect="off" spellcheck="false" placeholder="checklist.yourchurch.org" />
<div id="setupError" class="setup-error" style="display:none;"></div>
<div class="setup-actions">
<button type="button" id="setupBack" class="btn btn-secondary btn-small">Back</button>
<button type="button" id="setupConnect" class="btn btn-primary btn-small">Connect</button>
</div>
</div>
<div id="setupHostInfo" style="display:none;">
<p class="setup-sub" style="text-align:left;">
Checklist runs as a small Docker service on any always-on machine — a NAS, a
spare computer, or a cheap VPS. Once it is running, come back and enter its
address.
</p>
<div class="setup-actions">
<button type="button" id="setupHostBack" class="btn btn-secondary btn-small">Back</button>
<button type="button" id="setupHostNext" class="btn btn-primary btn-small">I have it running</button>
</div>
</div>
</div>
</div>
<div id="loginModal" class="modal-overlay" style="display: none;">
<div class="modal">
<h3>Sign in</h3>
<div class="form-group">
<label for="loginLocation">Location</label>
<select id="loginLocation"></select>
</div>
<div class="form-group">
<label for="loginTeam">Team</label>
<select id="loginTeam"></select>
</div>
<div class="form-group" id="loginExecNameGroup" style="display:none;">
<label for="loginExecName">Who are you?</label>
<input id="loginExecName" type="text" autocomplete="off" autocapitalize="words" placeholder="Your name" />
</div>
<div class="form-group">
<label for="loginPassword">Password</label>
<div class="password-field">
<input id="loginPassword" type="password" autocomplete="current-password" />
<button id="loginPasswordToggle" class="password-toggle" type="button" aria-label="Show password" title="Show password">🐵</button>
</div>
</div>
<div id="loginError" class="modal-message danger" style="display:none;"></div>
<p style="font-size:11px;color:#888;margin:0;">Your location, team, and password. Editors: choose <code>Executive</code>. <a href="/privacy" target="_blank" rel="noopener noreferrer" style="color:#7c9cc4;">Privacy</a><span id="changeServerWrap" style="display:none;"> · <a href="#" id="changeServerLink" style="color:#7c9cc4;">Change server</a></span></p>
<div class="modal-actions">
<button id="loginSubmit" class="btn btn-primary btn-small" type="button">Sign in</button>
</div>
</div>
</div>
<div id="userMgmtModal" class="modal-overlay" style="display: none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>Manage Access</h3>
<button id="userMgmtClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<div id="userMgmtError" class="modal-message danger" style="display:none;"></div>
<div class="mgmt-grid">
<section class="mgmt-section">
<h4>Team logins</h4>
<input id="userMgmtSearch" class="mgmt-search" type="search" placeholder="🔍 Filter by location, team, or email…" />
<div id="userMgmtList" class="user-mgmt-list"></div>
<div class="mgmt-add">
<h5>Add a team login</h5>
<div class="user-mgmt-grid">
<select id="newUserLocation" aria-label="Location"></select>
<select id="newUserTeam" aria-label="Team"></select>
<input id="newUserPassword" type="password" placeholder="Password (4+ chars)" />
<input id="newUserEmails" type="text" placeholder="Report email(s), comma-separated — optional" />
<button id="newUserCreate" class="btn btn-primary btn-small" type="button">Create login</button>
</div>
<p class="mgmt-tip">One shared login per location + team. Report emails get the weekly digest.</p>
</div>
</section>
<section class="mgmt-section">
<h4>Executives</h4>
<input id="execSearch" class="mgmt-search" type="search" placeholder="🔍 Filter by name…" />
<div id="execList" class="user-mgmt-list"></div>
<div class="mgmt-add">
<h5>Add an executive</h5>
<div class="user-mgmt-grid">
<input id="newExecName" type="text" placeholder="Name (e.g. Jordan)" autocapitalize="words" />
<input id="newExecPassword" type="password" placeholder="Password (4+ chars)" />
<button id="newExecCreate" class="btn btn-primary btn-small" type="button">Create executive</button>
</div>
<p class="mgmt-tip">Sign in under &ldquo;Executive&rdquo; with their name. &ldquo;Edit access&rdquo; sets the locations/teams they can edit.</p>
</div>
</section>
<section class="mgmt-section mgmt-wide">
<h4>Locations &amp; teams</h4>
<div id="directoryEditor" class="directory-editor"></div>
</section>
</div>
</div>
</div>
<div id="listModal" class="modal-overlay" style="display: none;">
<div class="modal">
<h3 id="listModalTitle"></h3>
<div id="listModalMessage" class="modal-message" style="display:none;"></div>
<input id="listModalInput" type="text" placeholder="List name" />
<div id="listModalTemplateGroup" class="form-group" style="margin-top:8px; display:none;">
<label for="listModalTemplate" style="font-size:12px;color:#b0b0b0;">Start from</label>
<select id="listModalTemplate"></select>
</div>
<div id="listModalLocationGroup" class="form-group" style="margin-top:8px;">
<label for="listModalLocation" style="font-size:12px;color:#b0b0b0;">Assign to location</label>
<select id="listModalLocation"></select>
</div>
<div id="listModalTeamGroup" class="form-group" style="margin-top:8px;">
<label for="listModalTeam" style="font-size:12px;color:#b0b0b0;">Assign to team</label>
<select id="listModalTeam"></select>
</div>
<div id="listModalColorGroup" class="form-group" style="margin-top:8px;">
<label style="font-size:12px;color:#b0b0b0;">Color</label>
<div id="listModalColors" class="color-swatches">
<button type="button" class="swatch swatch-none" data-color="" title="No color"></button>
<button type="button" class="swatch" data-color="blue" title="Blue"></button>
<button type="button" class="swatch" data-color="green" title="Green"></button>
<button type="button" class="swatch" data-color="amber" title="Amber"></button>
<button type="button" class="swatch" data-color="red" title="Red"></button>
<button type="button" class="swatch" data-color="purple" title="Purple"></button>
<button type="button" class="swatch" data-color="teal" title="Teal"></button>
<button type="button" class="swatch" data-color="pink" title="Pink"></button>
<button type="button" class="swatch" data-color="slate" title="Slate"></button>
<button type="button" class="swatch" data-color="orange" title="Orange"></button>
<button type="button" class="swatch" data-color="yellow" title="Yellow"></button>
<button type="button" class="swatch" data-color="lime" title="Lime"></button>
<button type="button" class="swatch" data-color="cyan" title="Cyan"></button>
<button type="button" class="swatch" data-color="indigo" title="Indigo"></button>
<button type="button" class="swatch" data-color="brown" title="Brown"></button>
<label class="swatch swatch-custom" title="Custom color — pick your own">
<input type="color" id="listModalCustomColor" value="#3498db" aria-label="Custom color" />
</label>
</div>
</div>
<div id="listModalHiddenGroup" class="form-group" style="margin-top:10px;">
<label class="checkbox-label" style="font-size:13px;"><input type="checkbox" id="listModalHidden" /> Hidden — only editors see it</label>
<label class="checkbox-label" style="font-size:13px;margin-top:6px;"><input type="checkbox" id="listModalShowNotes" /> Show a Notes column — reference info, no checking off</label>
<label class="checkbox-label" style="font-size:13px;margin-top:6px;"><input type="checkbox" id="listModalNotesOnly" /> Notes only — hide the To&nbsp;Do and Done columns</label>
<label class="checkbox-label" style="font-size:13px;margin-top:6px;"><input type="checkbox" id="listModalShowFeedback" /> Show a Feedback column — anyone on the list can add input</label>
<div class="form-group" id="listModalSectionsGroup" style="margin-top:10px;">
<label style="font-size:13px;">Task sections — extra To&nbsp;Do columns (e.g. Monday, Tuesday). The first is the default and can be renamed.</label>
<div id="listModalSections"></div>
<button type="button" class="btn btn-secondary btn-small" id="listModalSectionAdd">+ Add section</button>
</div>
</div>
<div id="listModalRepeatGroup" class="form-group" style="margin-top:8px;">
<label for="listModalResetType" style="font-size:12px;color:#b0b0b0;">🔁 Repeat this list <span style="color:#7c828c;">— every item resets to To&nbsp;Do on this schedule</span></label>
<select id="listModalResetType">
<option value="">Don't repeat — reset manually</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
</select>
<div id="listModalResetEvery" class="reset-row" style="display:none;">
<span>Every</span>
<input type="number" id="listModalResetInterval" min="1" max="99" value="1" />
<span id="listModalResetUnit">weeks</span>
<span id="listModalResetHint" class="reset-hint"></span>
</div>
<div id="listModalResetStartRow" class="reset-row" style="display:none;">
<span>starting on</span>
<select id="listModalResetStartDow">
<option value="0">Sunday</option><option value="1">Monday</option><option value="2">Tuesday</option>
<option value="3">Wednesday</option><option value="4">Thursday</option><option value="5">Friday</option><option value="6">Saturday</option>
</select>
</div>
<div id="listModalResetDowRow" class="reset-row" style="display:none;">
<span>on</span>
<select id="listModalResetDow">
<option value="0">Sunday</option><option value="1">Monday</option><option value="2">Tuesday</option>
<option value="3">Wednesday</option><option value="4">Thursday</option><option value="5">Friday</option><option value="6">Saturday</option>
</select>
</div>
<div id="listModalResetMonthRow" class="reset-row" style="display:none;">
<span>in</span>
<select id="listModalResetMonth">
<option value="0">January</option><option value="1">February</option><option value="2">March</option>
<option value="3">April</option><option value="4">May</option><option value="5">June</option>
<option value="6">July</option><option value="7">August</option><option value="8">September</option>
<option value="9">October</option><option value="10">November</option><option value="11">December</option>
</select>
</div>
<div id="listModalResetDomRow" class="reset-row" style="display:none;">
<span>on day</span>
<input type="number" id="listModalResetDom" min="1" max="31" value="1" />
<span>of the month</span>
</div>
<div id="listModalResetTimesRow" class="reset-row reset-times-row" style="display:none;">
<span>at</span>
<div id="listModalResetTimesList" class="reset-times-list"></div>
<button type="button" id="listModalResetTimesAdd" class="btn btn-secondary btn-small reset-time-add">+ Add time</button>
<span class="reset-hint reset-times-hint">leave empty to reset once a day</span>
</div>
</div>
<div class="modal-actions">
<button id="listModalCancel" class="btn btn-secondary btn-small" type="button">Cancel</button>
<button id="listModalSave" class="btn btn-primary btn-small" type="button">Save</button>
</div>
</div>
</div>
<div id="editTaskModal" class="modal-overlay" style="display: none;">
<div class="modal wide">
<h3>Edit Task</h3>
<div class="modal-body">
<div class="form-group">
<label for="editTitle">Title</label>
<input id="editTitle" type="text" placeholder="Task title..." />
</div>
<div class="form-group" id="editGroupRow" style="display:none;">
<label for="editGroupSelect">Section</label>
<select id="editGroupSelect"></select>
</div>
<div class="form-group">
<label for="editDescription">Description</label>
<div class="md-toolbar" data-md-target="editDescription">
<button type="button" class="md-btn" data-md="bold" title="Bold"><b>B</b></button>
<button type="button" class="md-btn" data-md="italic" title="Italic"><i>I</i></button>
<button type="button" class="md-btn" data-md="h" title="Heading">H</button>
<button type="button" class="md-btn" data-md="ul" title="Bullet list">• List</button>
<button type="button" class="md-btn" data-md="ol" title="Numbered list">1. List</button>
<button type="button" class="md-btn" data-md="task" title="Checkbox — a step users tick off (the task auto-completes when all are checked)">☑ Step</button>
<button type="button" class="md-btn" data-md="link" title="Add a link (web page, Google Doc…)">🔗 Link</button>
<button type="button" class="md-btn" data-md="attach" title="Attach a document (PDF, Word, Excel, PowerPoint…) — uploads it and links it here">📎 File</button>
<button type="button" class="md-btn md-preview-btn" data-md="preview" aria-pressed="false" title="Toggle formatted markdown view">Markdown</button>
</div>
<textarea id="editDescription" placeholder="Description — format with the buttons above." style="display:none;"></textarea>
<div class="md-rich" contenteditable="true" data-md-rich-for="editDescription" data-placeholder="Description — format with the buttons above." role="textbox" aria-multiline="true"></div>
</div>
<div class="form-group">
<label class="checkbox-label"><input type="checkbox" id="editIsRecurring" /> Recurring Task</label>
</div>
<div class="form-group" id="editRecurrenceOptions" style="display: none;">
<div class="rec-editor"></div>
</div>
</div>
<div class="modal-actions">
<button id="editModalCancel" class="btn btn-secondary btn-small" type="button">Cancel</button>
<button id="editModalSave" class="btn btn-primary btn-small" type="button">Save</button>
</div>
</div>
</div>
<div id="moveItemModal" class="modal-overlay" style="display: none;">
<div class="modal">
<h3>Move Task</h3>
<div class="modal-body">
<div class="form-group">
<label for="moveItemSelect">Select destination list</label>
<select id="moveItemSelect"></select>
</div>
</div>
<div class="modal-actions">
<button id="moveItemCancel" class="btn btn-secondary btn-small" type="button">Cancel</button>
<button id="moveItemSave" class="btn btn-primary btn-small" type="button">Move</button>
</div>
</div>
</div>
<!-- Inline "Move to…" dropdown anchored to a task's Move button (replaces the modal flow). -->
<div id="moveMenuBackdrop" class="move-menu-backdrop" style="display:none;"></div>
<div id="moveMenu" class="move-menu" role="menu" aria-label="Move task to list" style="display:none;"></div>
<!-- Cross-department request (editors only): lands in the target team's Requests inbox. -->
<div id="requestModal" class="modal-overlay" style="display:none;">
<div class="modal">
<div class="modal-header"><h3>✋ Request something</h3></div>
<div class="modal-body">
<div id="requestError" class="modal-message danger" style="display:none;"></div>
<div class="form-group">
<label for="requestTarget">From which department?</label>
<select id="requestTarget"></select>
</div>
<div class="form-group">
<label for="requestTitle">What do you need?</label>
<input id="requestTitle" type="text" maxlength="120" placeholder="e.g. Toilet paper restock in the office bathroom" />
</div>
<div class="form-group">
<label for="requestDetails">Details (optional)</label>
<textarea id="requestDetails" rows="3" maxlength="2000" placeholder="Where exactly, how urgent, anything helpful…"></textarea>
</div>
<div id="requestPhotos" class="equip-photos"></div>
<div class="equip-photo-row">
<label class="btn btn-secondary btn-small equip-upload-label">
📷 Add photo
<input id="requestPhotoFile" type="file" accept="image/*" hidden />
</label>
<span id="requestUploadStatus" class="equip-upload-status"></span>
</div>
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:12px;">
<button id="requestCancel" class="btn btn-secondary" type="button">Cancel</button>
<button id="requestSend" class="btn btn-primary" type="button">Send request</button>
</div>
</div>
</div>
</div>
<!-- Equipment photo popup (shown when anyone taps an equipment word).
The image sits in a pan/zoom stage: pinch or scroll to zoom, drag to pan,
double-tap to reset. The note panel below scrolls independently. -->
<div id="equipModal" class="modal-overlay equip-lightbox" style="display: none;">
<div class="modal equip-modal">
<div class="equip-lb-head">
<h3 id="equipModalName"></h3>
<button id="equipReportBtn" class="equip-report-btn" type="button" title="Report a problem with this gear">⚠ Report a problem</button>
<button id="equipModalClose" class="equip-lb-close" type="button" aria-label="Close">✕</button>
</div>
<div id="equipStage" class="equip-stage">
<img id="equipModalImg" class="equip-img" alt="" draggable="false" />
<button id="equipPrev" class="equip-nav equip-nav-prev" type="button" aria-label="Previous photo">‹</button>
<button id="equipNext" class="equip-nav equip-nav-next" type="button" aria-label="Next photo">›</button>
<div id="equipZoomHint" class="equip-zoom-hint"></div>
<div class="equip-zoom-controls">
<button id="equipZoomOut" class="equip-zoom-btn" type="button" aria-label="Zoom out">−</button>
<button id="equipZoomReset" class="equip-zoom-btn" type="button" aria-label="Fit to screen">⤢</button>
<button id="equipZoomIn" class="equip-zoom-btn" type="button" aria-label="Zoom in">+</button>
</div>
</div>
<div id="equipCaption" class="equip-caption"></div>
<div id="equipThumbs" class="equip-thumbs"></div>
<div id="equipModalNote" class="equip-note"></div>
<div id="equipIssues" class="equip-issues"></div>
</div>
</div>
<!-- Equipment library manager (master only) -->
<div id="equipMgmtModal" class="modal-overlay" style="display: none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>Equipment Library</h3>
<button id="equipMgmtClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<div id="equipMgmtError" class="modal-message danger" style="display:none;"></div>
<p class="mgmt-tip">Add a photo for any gear — its name becomes tappable in descriptions for the locations/teams you pick. Each location can keep its own photo.</p>
<input id="equipSearch" class="mgmt-search" type="search" placeholder="🔍 Filter equipment…" />
<div id="equipMgmtList" class="user-mgmt-list"></div>
<div class="mgmt-add" id="equipAddSection">
<button type="button" class="mgmt-add-toggle" id="equipAddToggle" aria-expanded="false" aria-controls="equipAddBody">
<span class="mgmt-add-caret" aria-hidden="true">+</span>
<span id="equipFormTitle">Add equipment</span>
</button>
<div class="mgmt-add-body" id="equipAddBody">
<div class="user-mgmt-grid">
<input id="equipName" type="text" placeholder="Name" />
<input id="equipAliases" type="text" placeholder="Aliases, comma-separated (optional)" />
<textarea id="equipNote" class="equip-note-input" rows="2" placeholder="Note / troubleshooting steps (optional) — Markdown & line breaks supported"></textarea>
</div>
<div id="equipPhotos" class="equip-photos"></div>
<div class="equip-photo-row">
<label class="btn btn-secondary btn-small equip-upload-label">
📁 Add photo / video
<input id="equipFile" type="file" accept="image/*,video/mp4,video/webm,video/quicktime" hidden />
</label>
<button id="equipCamBtn" class="btn btn-secondary btn-small" type="button">📷 Take photo</button>
<span id="equipUploadStatus" class="equip-upload-status"></span>
</div>
<div class="equip-url-row">
<input id="equipImage" type="text" class="equip-url-input" placeholder="…or paste an image / video URL (JPG, PNG, MP4)" />
<button id="equipAddUrl" class="btn btn-secondary btn-small" type="button">Add</button>
</div>
<div id="equipDocs" class="equip-docs-editor"></div>
<div class="equip-photo-row">
<label class="btn btn-secondary btn-small equip-upload-label">
📎 Add document
<input id="equipDocFile" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.csv,.rtf" hidden />
</label>
</div>
<div class="equip-url-row">
<input id="equipDocUrl" type="text" class="equip-url-input" placeholder="…or paste a link (Google Doc, manual, web page)" />
<button id="equipDocAdd" class="btn btn-secondary btn-small" type="button">Add link</button>
</div>
<div class="equip-scope">
<div id="equipScopeHelp" class="exec-access-help">Where it appears — check location(s) and/or team(s).</div>
<div class="dir-head">Locations</div>
<div id="equipLocRow" class="exec-cb-row"></div>
<div class="dir-head" style="margin-top:6px;">Teams</div>
<div id="equipTeamRow" class="exec-cb-row"></div>
</div>
<div class="equip-form-actions">
<button id="equipSave" class="btn btn-primary btn-small" type="button">Add equipment</button>
<button id="equipCancelEdit" class="btn btn-secondary btn-small" type="button" style="display:none;">Cancel edit</button>
</div>
</div><!-- /.mgmt-add-body -->
</div>
</div>
</div>
<!-- In-app camera capture (works on desktop + mobile via getUserMedia) -->
<div id="equipCamModal" class="modal-overlay" style="display:none; z-index:10002;">
<div class="modal equip-cam">
<div class="mgmt-head">
<h3>Take a photo</h3>
<button id="equipCamCancel" class="btn btn-secondary btn-small" type="button">Cancel</button>
</div>
<div class="equip-cam-stage">
<video id="equipCamVideo" playsinline autoplay muted></video>
</div>
<div id="equipCamError" class="modal-message danger" style="display:none;"></div>
<div class="equip-cam-actions">
<button id="equipCamCapture" class="btn btn-primary" type="button">📸 Capture</button>
<button id="equipCamSwitch" class="btn btn-secondary btn-small" type="button">🔄 Switch camera</button>
</div>
</div>
</div>
<!-- Overview: each team's progress today + recent activity (master + execs) -->
<div id="overviewModal" class="modal-overlay" style="display:none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>Today at a glance</h3>
<button id="overviewClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<div id="overviewBody" class="overview-body"></div>
</div>
</div>
<div id="activityModal" class="modal-overlay" style="display:none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>🛡 Activity Log</h3>
<button id="activityClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<input id="activitySearch" class="activity-search" type="search" placeholder="Filter by person, action, or list…" autocomplete="off" />
<div id="activityBody" class="activity-body"></div>
</div>
</div>
<!-- "Who's checking?" name picker (shared team logins only) -->
<div id="checkerModal" class="modal-overlay" style="display:none;">
<div class="modal">
<h3>Who's checking?</h3>
<p style="font-size:12px;color:#888;margin:0;">Pick your name so your check-offs are recorded as you — no password needed.</p>
<select id="checkerSelect" style="margin-top:8px;"></select>
<div id="checkerAddRow" style="display:none; gap:8px; margin-top:8px;">
<input id="checkerNewName" type="text" placeholder="Type your name" autocapitalize="words" autocomplete="off" style="flex:1;" />
<button id="checkerAddBtn" class="btn btn-primary btn-small" type="button">Add</button>
</div>
<div class="modal-actions">
<button id="checkerClose" class="btn btn-secondary btn-small" type="button">Done</button>
</div>
</div>
</div>
<!-- Team name rosters — manage who's on each team (master + execs) -->
<div id="rosterModal" class="modal-overlay" style="display:none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>Team names</h3>
<button id="rosterClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<p class="mgmt-tip">Names checkers can tap when signing in. Add or remove names for any team you manage.</p>
<input id="rosterSearch" class="mgmt-search" type="search" placeholder="🔍 Filter by location or team…" />
<div id="rosterBody" class="roster-body user-mgmt-list"></div>
</div>
</div>
<div class="app-container">
<header>
<div class="header-left">
<svg class="app-logo" viewBox="0 0 44 44" role="img" aria-label="Checklist" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="appLogoGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#39a8ff"/>
<stop offset="100%" stop-color="#2ecc71"/>
</linearGradient>
</defs>
<rect x="3" y="3" width="38" height="38" rx="12" fill="url(#appLogoGrad)"/>
<path class="app-logo-check" d="M13.5 22.5 L19.5 28.5 L30.5 15.5" fill="none" stroke="#fff" stroke-width="3.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="header-controls admin-only">
<div class="list-tabs-controls">
<button id="listViewToggle" class="lists-toggle" type="button" title="Switch between the tab strip and a Location → Team → List tree">🌳 Tree view</button>
<button id="listTabsToggle" class="lists-toggle" type="button" aria-expanded="true" title="Show/hide the lists">Hide lists</button>
<div class="list-filter master-only">
<select id="adminFilterLocation" aria-label="Filter by location"></select>
<select id="adminFilterTeam" aria-label="Filter by team"></select>
</div>
</div>
<div class="undo-redo admin-only" role="group" aria-label="Undo and redo">
<button id="undoBtn" class="icon-btn" type="button" title="Undo (Ctrl/⌘+Z)" aria-label="Undo" disabled>↶</button>
<button id="redoBtn" class="icon-btn" type="button" title="Redo (Ctrl/⌘+Shift+Z)" aria-label="Redo" disabled>↷</button>
</div>
<div id="listMenu" class="list-menu">
<button id="listMenuHandle" class="list-menu-btn" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="listMenuPanel" aria-label="List tools menu"><span class="lmh-icon" aria-hidden="true">☰</span><span class="lmh-label">Menu</span></button>
<div id="listMenuBackdrop" class="list-menu-backdrop"></div>
<div id="listMenuPanel" class="list-menu-panel" role="menu" aria-label="List tools">
<div class="menu-group-label">This list</div>
<button id="addTaskBtn" class="btn btn-primary btn-small">+ Add Task</button>
<button id="addNoteBtn" class="btn btn-secondary btn-small" title="Add a reference note (no checkbox)">+ Add Note</button>
<button id="renameListBtn" class="btn btn-secondary btn-small">✏️ Edit List</button>
<button id="duplicateListBtn" class="btn btn-secondary btn-small" title="Make a copy of this list (items copied, checkmarks fresh)">⧉ Duplicate List</button>
<button id="resetListBtn" class="btn btn-secondary btn-small" title="Move every item back to To Do">↺ Reset List</button>
<button id="deleteListBtn" class="btn btn-danger btn-small">🗑 Delete List</button>
<div class="menu-group-label">Lists</div>
<button id="addListBtn" class="btn btn-primary btn-small">+ Add List</button>
<div class="menu-group-label">Tools</div>
<button id="helpTipsBtn" class="btn btn-secondary btn-small" type="button" title="Quick tips on how this works">❔ How this works</button>
<button id="overviewBtn" class="btn btn-secondary btn-small admin-only" title="See each team's progress today">📊 Overview</button>
<button id="rosterBtn" class="btn btn-secondary btn-small admin-only" title="Manage the names checkers can pick">🧑 Team Names</button>
<button id="manageUsersBtn" class="btn btn-secondary btn-small master-only">👥 Manage Users</button>
<button id="activityBtn" class="btn btn-secondary btn-small master-only" title="See everything done in the app — who checked, created, edited, moved, or deleted">🛡 Activity Log</button>
<button id="incidentsBtn" class="btn btn-secondary btn-small admin-only" title="See and resolve reported gear problems in your area">⚠ Incidents<span id="incidentsBtnCount" class="menu-count" style="display:none;"></span></button>
<button id="reliabilityBtn" class="btn btn-secondary btn-small admin-only" title="Which gear breaks most — ranked by reported issues">📊 Reliability</button>
<button id="equipMgmtBtn" class="btn btn-secondary btn-small admin-only">🔧 Equipment</button>
<button id="sendDigestBtn" class="btn btn-secondary btn-small admin-only" title="Email the checklist report now to the configured recipients for your area">📧 Send Report</button>
<button id="exportJsonBtn" class="btn btn-secondary btn-small admin-only" title="Download a backup of your lists (master = full backup)">⬇ Export JSON</button>
<button id="importJsonBtn" class="btn btn-secondary btn-small admin-only">⬆ Import JSON</button>
<input id="importJsonInput" type="file" accept="application/json,.json" style="display:none;" />
</div>
</div>
</div>
<div class="header-right">
<button id="searchBtn" class="icon-btn search-bar-btn" type="button" title="Search gear, notes & tasks (press /)" aria-label="Search"><span class="sb-icon" aria-hidden="true">🔍</span><span class="sb-label">Search…</span></button>
<button id="reportIssueBtn" class="icon-btn report-issue-btn" type="button" title="Report a gear problem" aria-label="Report a gear problem">⚠</button>
<button id="requestsBadge" class="requests-badge" type="button" style="display:none;" title="Open requests waiting — tap to view" aria-label="Open requests">✋ <span id="requestsBadgeCount">0</span></button>
<button id="requestSendBtn" class="icon-btn request-send-btn admin-only" type="button" title="Request something from another department" aria-label="Request something from another department">✋</button>
<button id="checkerChip" class="checker-chip checker-only" type="button" title="Who's checking? Tap to pick your name">
<span class="checker-chip-icon">👤</span>
<span id="checkerChipName">Who's checking?</span>
</button>
<button id="userBadge" class="user-badge" type="button" title="Click to sign out">
<span class="user-badge-icon">👤</span>
<span id="userBadgeName">Sign in</span>
</button>
</div>
</header>
<div class="main-content">
<div id="tasksView" class="tab-view active">
<div class="list-tabs-row">
<div id="activeListName" class="active-list-name admin-only"></div>
<div id="listTabs" class="list-tabs"></div>
<div id="listTree" class="list-tree admin-only"></div>
</div>
<div class="checklists-section">
<div class="checklist-column" data-col="todo">
<h2><span class="col-title">To Do</span></h2>
<div id="todoList" class="checklist-list"></div>
</div>
<div class="checklist-column" data-col="done">
<h2><span class="col-title">Done</span></h2>
<div id="doneList" class="checklist-list"></div>
</div>
<div class="checklist-column notes-column" data-col="notes">
<h2><span class="col-title">Notes</span></h2>
<div id="notesList" class="checklist-list"></div>
</div>
<div class="checklist-column feedback-column" data-col="feedback">
<h2><span class="col-title">Feedback</span></h2>
<div id="feedbackList" class="checklist-list"></div>
</div>
</div>
</div>
</div>
<div id="searchOverlay" class="search-overlay" style="display:none;">
<div class="search-panel" role="dialog" aria-label="Search">
<div class="search-inputrow">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input id="globalSearchInput" type="search" placeholder="Search gear, notes, tasks & lists…" autocomplete="off" />
<button id="searchClose" type="button" class="icon-btn" aria-label="Close search">✕</button>
</div>
<div id="searchResults" class="search-results"></div>
</div>
</div>
<!-- Report a gear problem (any signed-in user) -->
<div id="incidentModal" class="modal-overlay" style="display: none;">
<div class="modal">
<div class="modal-header">
<h3>⚠ Report a problem</h3>
<button id="incidentClose" class="btn btn-secondary btn-small" type="button">Cancel</button>
</div>
<div class="modal-body">
<div id="incidentGearRow" class="incident-gear-row" style="display:none;">
<span class="incident-gear-label">Gear:</span>
<span id="incidentGearName" class="incident-gear-name"></span>
</div>
<div class="form-group">
<label>How bad is it?</label>
<div class="severity-toggle" id="incidentSeverity">
<button type="button" class="sev-btn sev-down" data-sev="down">
<span class="sev-emoji">🔴</span>
<span class="sev-title">It's down now</span>
<span class="sev-sub">Can't be used — alerts the team right away</span>
</button>
<button type="button" class="sev-btn sev-minor is-selected" data-sev="minor">
<span class="sev-emoji">🟡</span>
<span class="sev-title">Minor issue</span>
<span class="sev-sub">Still works — goes in the report</span>
</button>
</div>
</div>
<div class="form-group">
<label for="incidentWhat">What's wrong?</label>
<input type="text" id="incidentWhat" maxlength="120" placeholder="e.g. HDMI port loose, won't hold signal" />
</div>
<div class="form-group">
<label for="incidentDetails">Details <span class="label-hint">(optional)</span></label>
<textarea id="incidentDetails" rows="3" maxlength="2000" placeholder="Anything that helps whoever fixes it…"></textarea>
</div>
<div id="incidentReportingAs" class="incident-reporting-as" style="display:none;"></div>
<div class="modal-footer">
<button id="incidentSubmit" class="btn btn-primary" type="button">Send report</button>
</div>
</div>
</div>
</div>
<!-- Reported gear problems (editors) -->
<div id="incidentsModal" class="modal-overlay" style="display: none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>⚠ Gear Issues</h3>
<button id="incidentsClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<div class="incidents-controls">
<label class="incidents-openonly"><input type="checkbox" id="incidentsOpenOnly" checked /> Open only</label>
<input type="search" id="incidentsSearch" class="incidents-search" placeholder="Filter by gear, place, or text…" autocomplete="off" />
</div>
<div id="incidentsList" class="incidents-list"></div>
</div>
</div>
<!-- Reliability dashboard (editors) -->
<div id="reliabilityModal" class="modal-overlay" style="display: none;">
<div class="modal mgmt">
<div class="mgmt-head">
<h3>📊 Gear Reliability</h3>
<button id="reliabilityClose" class="btn btn-secondary btn-small" type="button">Close</button>
</div>
<div class="reliability-controls">
<label class="incidents-openonly"><input type="checkbox" id="reliabilityOpenFirst" checked /> Sort by open issues</label>
<span id="reliabilitySummary" class="reliability-summary"></span>
</div>
<div id="reliabilityList" class="reliability-list"></div>
</div>
</div>
<div id="addTaskModal" class="modal-overlay" style="display: none;">
<div class="modal wide">
<div class="add-header">
<h3 id="addModalTitle">Add Task</h3>
<div class="add-list-target">
<label for="addListSelect">Add to list:</label>
<select id="addListSelect"></select>
</div>
</div>
<div class="modal-body">
<div class="form-group">
<input type="text" id="itemTitle" placeholder="Task title..." />
</div>
<div class="form-group" id="itemGroupRow" style="display:none;">
<label for="itemGroupSelect">Section</label>
<select id="itemGroupSelect"></select>
</div>
<div class="form-group">
<div class="md-toolbar" data-md-target="itemDescription">
<button type="button" class="md-btn" data-md="bold" title="Bold"><b>B</b></button>
<button type="button" class="md-btn" data-md="italic" title="Italic"><i>I</i></button>
<button type="button" class="md-btn" data-md="h" title="Heading">H</button>
<button type="button" class="md-btn" data-md="ul" title="Bullet list">• List</button>
<button type="button" class="md-btn" data-md="ol" title="Numbered list">1. List</button>
<button type="button" class="md-btn" data-md="task" title="Checkbox — a step users tick off (the task auto-completes when all are checked)">☑ Step</button>
<button type="button" class="md-btn" data-md="link" title="Add a link (web page, Google Doc…)">🔗 Link</button>
<button type="button" class="md-btn" data-md="attach" title="Attach a document (PDF, Word, Excel, PowerPoint…) — uploads it and links it here">📎 File</button>
<button type="button" class="md-btn md-preview-btn" data-md="preview" aria-pressed="false" title="Toggle formatted markdown view">Markdown</button>
</div>
<textarea id="itemDescription" placeholder="Description (optional)" style="display:none;"></textarea>
<div class="md-rich" contenteditable="true" data-md-rich-for="itemDescription" data-placeholder="Description (optional)" role="textbox" aria-multiline="true"></div>
</div>
<div class="form-group" id="recurringFieldGroup">
<label class="checkbox-label">
<input type="checkbox" id="isRecurring" />
Recurring Task
</label>
</div>
<div class="form-group" id="recurrenceOptions" style="display: none;">
<div class="rec-editor"></div>
</div>
</div>
<div class="modal-actions">
<button id="addTaskCancel" class="btn btn-secondary btn-small" type="button">Close</button>
<button id="addItemBtn" class="btn btn-primary btn-small">Add Task</button>
</div>
</div>
</div>
</div>
<div id="toast" class="toast"></div>
<!-- Motion One (vendored, self-contained ~15KB): exposes window.Motion synchronously
before app.js so item FLIP, spring modals and the completion burst can use it.
If it ever fails to load, app.js falls back to the pure-CSS animations. -->
<script src="vendor/motion.global.js"></script>
<!-- Native shim: a no-op on the web; inside the iOS/Android app bundles it points
server-absolute URLs (/api/…, /equip-images/…) at the real server. The mobile
build injects native-config.js + native-shell.js ahead of it. -->
<script src="native-bridge.js"></script>
<script src="md.js"></script>
<script src="app.js"></script>
</body>
</html>