It was mine, not the user's: added unprompted alongside the Walks panel because sleepTargetFor existed and it seemed to fit. The number came from the "five-minute rule" — five minutes per month of age, twice a day — written from general knowledge with no source consulted and none cited in the code. That rule is widely repeated but is folk guidance, not a veterinary standard. Its origin is unclear, it is contested by what evidence exists on early exercise and joint development, and it ignores breed, terrain, lead or off-lead and the individual dog. Drawing it as a line labelled "Goal", with a ✓ for clearing it, stated it far more confidently than a rule of thumb earns — particularly in an app whose entire job is otherwise to record what happened rather than to advise. So the target line goes, along with its legend chip, the ✓, and walkTargetFor itself, which had no other caller. The trend keeps today against yesterday and the average: a record, with the judgement left to whoever knows the dog.
571 lines
30 KiB
HTML
571 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||
<meta name="theme-color" content="#7c5cff" />
|
||
<title>Puppy Tracker</title>
|
||
<link rel="manifest" href="manifest.json" />
|
||
<link rel="icon" href="icon.svg" type="image/svg+xml" />
|
||
<link rel="apple-touch-icon" href="icon-180.png" />
|
||
<link rel="stylesheet" href="style.css" />
|
||
<script>
|
||
// Apply a saved theme before first paint so there's no light/dark flash.
|
||
// No saved choice → leave it to the prefers-color-scheme media query.
|
||
(function () {
|
||
try {
|
||
var t = localStorage.getItem("puppy-tracker:theme");
|
||
if (t === "light" || t === "dark") document.documentElement.dataset.theme = t;
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<!-- Shown when a newer build's service worker is waiting. "Reload" activates
|
||
it and refreshes onto the new assets; "Later" dismisses until next time.
|
||
Suppressed on the very first install (see app.js). -->
|
||
<div id="update-banner" class="update-banner" hidden role="status" aria-live="polite">
|
||
<div class="update-banner-row">
|
||
<span class="update-banner-msg">A new version is available</span>
|
||
<span class="update-banner-actions">
|
||
<button type="button" id="update-reload" class="update-banner-btn">Reload</button>
|
||
<button type="button" id="update-later" class="update-banner-btn ghostish">Later</button>
|
||
</span>
|
||
</div>
|
||
<!-- What the waiting build adds over the running one; filled by app.js
|
||
from the diff between the cached and the fresh changelog.json. -->
|
||
<ul id="update-changelog" class="update-changelog" hidden></ul>
|
||
</div>
|
||
|
||
<!-- Login / register gate. Shown until the session check succeeds; the app
|
||
(#app) stays hidden behind it so no puppy data paints while logged out. -->
|
||
<div id="auth-screen" class="auth-screen" hidden>
|
||
<div class="auth-card">
|
||
<h1>🐶 Puppy Tracker</h1>
|
||
<p class="auth-sub" id="auth-sub">Sign in to continue</p>
|
||
<form id="auth-form">
|
||
<label>Email
|
||
<input type="email" id="auth-email" autocomplete="username" required />
|
||
</label>
|
||
<label>Password
|
||
<input type="password" id="auth-password" autocomplete="current-password" required minlength="8" />
|
||
</label>
|
||
<label id="auth-invite-field" hidden>Invite code
|
||
<input type="text" id="auth-invite" autocomplete="off" placeholder="Ask the owner for this" />
|
||
</label>
|
||
<p id="auth-error" class="auth-error" hidden></p>
|
||
<button type="submit" id="auth-submit">Sign in</button>
|
||
</form>
|
||
<p class="auth-toggle">
|
||
<span id="auth-toggle-text">No account yet?</span>
|
||
<button type="button" id="auth-toggle-btn" class="linklike">Create one</button>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="app" hidden>
|
||
<header>
|
||
<div class="title">
|
||
<h1 id="app-title">🐶 Puppy Tracker</h1>
|
||
<div id="puppy-age" class="puppy-age" hidden></div>
|
||
</div>
|
||
<div class="header-actions">
|
||
<button type="button" id="pedigree-btn" class="ghost icon-btn" aria-label="Pedigree" title="Pedigree" hidden>🌳</button>
|
||
<button type="button" id="settings-btn" class="ghost icon-btn" aria-label="Settings" title="Settings">⚙️</button>
|
||
<button type="button" id="logout-btn" class="ghost icon-btn" aria-label="Log out" title="Log out">🚪</button>
|
||
<div id="online-status" class="status-pill"></div>
|
||
</div>
|
||
</header>
|
||
|
||
<main>
|
||
<section class="day-bar">
|
||
<!-- Compact twin of the big timer below: invisible (but keeping its
|
||
slot) while the big card is on screen, shown once it scrolls
|
||
away. Hidden entirely until a sleep event exists. Tapping it logs
|
||
the boundary that flips the current state (asleep → sleep end,
|
||
awake → sleep start). -->
|
||
<button type="button" id="bar-clock" class="bar-clock" hidden>
|
||
<span id="bar-clock-icon" aria-hidden="true"></span>
|
||
<span id="bar-clock-time"></span>
|
||
</button>
|
||
<button type="button" id="day-prev" class="ghost" aria-label="Previous day">←</button>
|
||
<!-- A browser won't let us shorten the text a native date input shows,
|
||
so the face button carries a compact year-less date and the real
|
||
input stays (visually hidden) as the value + native picker. -->
|
||
<span class="day-date">
|
||
<button type="button" id="day-date-face" class="ghost"></button>
|
||
<input type="date" id="day-picker" tabindex="-1" aria-hidden="true" />
|
||
</span>
|
||
<button type="button" id="day-next" class="ghost" aria-label="Next day">→</button>
|
||
<button type="button" id="day-today" class="ghost">Today</button>
|
||
</section>
|
||
|
||
<section id="big-clock" class="big-clock" hidden>
|
||
<div class="bc-label" id="bc-label">—</div>
|
||
<div class="bc-time" id="bc-time">0:00</div>
|
||
<div class="bc-since" id="bc-since"></div>
|
||
</section>
|
||
|
||
<section class="quick-actions">
|
||
<h2>Log event</h2>
|
||
<!-- Explicit rows rather than one auto-fit grid: a start/end pair has
|
||
to stay side by side at every width (a flat grid split them apart
|
||
at some column counts, and orphaned the last button on a row of
|
||
its own). A row per timed pair, then the two one-tap moments, and
|
||
last the three that open a dialog to type a value. -->
|
||
<div class="actions">
|
||
<div class="action-row">
|
||
<button class="action sleep" data-type="sleep-start">😴 Sleep start</button>
|
||
<button class="action sleep" data-type="sleep-end">⏰ Sleep end</button>
|
||
</div>
|
||
<div class="action-row">
|
||
<button class="action walk" data-type="walk-start">🦮 Walk start</button>
|
||
<button class="action walk" data-type="walk-end">🏁 Walk end</button>
|
||
</div>
|
||
<div class="action-row">
|
||
<button class="action pee" data-type="pee">💧 Pee</button>
|
||
<button class="action poo" data-type="poo">💩 Poo</button>
|
||
</div>
|
||
<div class="action-row three-up">
|
||
<button class="action eat" data-type="eat">🍽️ Ate</button>
|
||
<button class="action weight" data-type="weight">⚖️ Weight</button>
|
||
<button class="action note" data-type="note">📝 Note</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Page-level, not a panel's: every panel below that covers more than
|
||
one day reads this, so it sits on its own row above them all rather
|
||
than inside one of them, where it read as that panel's own control
|
||
(see renderChartWindow). -->
|
||
<div class="chart-window">
|
||
<span class="chart-window-label">Charts cover</span>
|
||
<div class="chart-days-picker" role="group" aria-label="How many days the charts cover">
|
||
<button type="button" class="ghost" data-days="7">7d</button>
|
||
<button type="button" class="ghost" data-days="14">14d</button>
|
||
<button type="button" class="ghost" data-days="30">30d</button>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="training" data-panel="training">
|
||
<h2>Training</h2>
|
||
<ul id="training-list" class="training-list"></ul>
|
||
<p id="training-empty" class="empty">No exercises yet. Add one to start tracking training.</p>
|
||
<button type="button" id="exercise-add" class="ghost training-add">Add exercise</button>
|
||
<div class="chart training-chart" id="training-chart-wrap" hidden>
|
||
<div class="chart-title">Consistency <span data-chart-days-label>(last 7 days)</span></div>
|
||
<svg id="chart-training" class="chart-svg" viewBox="0 0 320 60" role="img" aria-label="Training sessions per exercise per day"></svg>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="overview" data-panel="overview">
|
||
<h2 id="overview-title">Today's overview</h2>
|
||
<div class="stats">
|
||
<div class="stat">
|
||
<div class="stat-label">Sleep</div>
|
||
<div class="stat-value" id="stat-sleep">0h 0m</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Awake</div>
|
||
<div class="stat-value" id="stat-awake">0h 0m</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Walks</div>
|
||
<div class="stat-value" id="stat-walk">0m</div>
|
||
<div class="stat-sub" id="stat-walk-count" hidden></div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Meals</div>
|
||
<div class="stat-value" id="stat-meals">0</div>
|
||
<div class="stat-sub" id="stat-meals-grams" hidden></div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Pees</div>
|
||
<div class="stat-value" id="stat-pees">0</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Poos</div>
|
||
<div class="stat-value" id="stat-poos">0</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Training</div>
|
||
<div class="stat-value" id="stat-training">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lasts">
|
||
<div class="last-row"><span>Last pee</span><span id="last-pee">—</span></div>
|
||
<div class="last-row"><span>Last poo</span><span id="last-poo">—</span></div>
|
||
<div class="last-row"><span>Last meal</span><span id="last-eat">—</span></div>
|
||
<div class="last-row"><span>Last sleep</span><span id="last-sleep">—</span></div>
|
||
<div class="last-row"><span>Last walk</span><span id="last-walk">—</span></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="timing" data-panel="timing">
|
||
<h2>Timing <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
||
<!-- One range chart per type, drawn by drawTimingChart: the band spans
|
||
the shortest to the typical gap and the marker is how long it has
|
||
been since the last one, so a marker past the band reads as due. -->
|
||
<div class="timing-charts">
|
||
<div class="timing-item">
|
||
<div class="timing-name">Pees</div>
|
||
<svg id="timing-chart-pee" class="timing-chart" viewBox="0 0 320 50" role="img"></svg>
|
||
<p class="muted-note timing-note" id="timing-note-pee" hidden></p>
|
||
</div>
|
||
<div class="timing-item">
|
||
<div class="timing-name">Poos</div>
|
||
<svg id="timing-chart-poo" class="timing-chart" viewBox="0 0 320 50" role="img"></svg>
|
||
<p class="muted-note timing-note" id="timing-note-poo" hidden></p>
|
||
</div>
|
||
<div class="timing-item">
|
||
<div class="timing-name">Meals</div>
|
||
<svg id="timing-chart-eat" class="timing-chart" viewBox="0 0 320 50" role="img"></svg>
|
||
<p class="muted-note timing-note" id="timing-note-eat" hidden></p>
|
||
</div>
|
||
</div>
|
||
<!-- The axis is stretched (see drawTimingChart), so say what the middle
|
||
of a bar means rather than leave it to be inferred. -->
|
||
<p class="muted-note">The middle of every bar is that type's typical gap: left of it is sooner than usual, right of it is longer, and the faded stretch runs out to the longest gap in the window.</p>
|
||
</section>
|
||
|
||
<!-- Sleep and wake windows are the same boundaries read two ways — a wake
|
||
window is exactly the gap between two sleeps — so they interleave into
|
||
one alternating list rather than sitting in two panels that each show
|
||
half the day. Every row carries its state; the open one keeps the
|
||
highlight. -->
|
||
<section class="sleepwake" data-panel="sleep-wake">
|
||
<h2>Sleep & wake</h2>
|
||
<ul id="sleep-wake-list" class="wake-list"></ul>
|
||
<p id="sleep-wake-empty" class="empty">No sleep or wake windows yet for this day.</p>
|
||
</section>
|
||
|
||
<section class="patterns" data-panel="sleep-daily">
|
||
<h2>Sleep <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
||
<div class="chart">
|
||
<div class="chart-title">Hours per day</div>
|
||
<svg id="chart-sleep" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Sleep hours per day"></svg>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="patterns" data-panel="sleep-timeline">
|
||
<h2><span id="sleep-timeline-title">When sleeping</span> <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
||
<svg id="chart-sleep-timeline" class="chart-svg" viewBox="0 0 320 125" role="img" aria-label="Sleep periods per day"></svg>
|
||
<p class="muted-note">Each row is a day, midnight to midnight; shaded = asleep. Tap a row to open that day.</p>
|
||
</section>
|
||
|
||
<section class="patterns" data-panel="sleep-trend">
|
||
<h2>Sleep trend</h2>
|
||
<svg id="chart-sleep-trend" class="chart-svg" viewBox="0 0 320 220" role="img" aria-label="Cumulative sleep hours through the selected day, the day before it, the recent average and (for today) the projected end-of-day total, with the age-based sleep goal band"></svg>
|
||
<div class="legend">
|
||
<span class="lg trend-today"><span class="sw"></span><span id="legend-trend-today-text">Today</span></span>
|
||
<span class="lg trend-projected" id="legend-trend-projected" hidden><span class="sw"></span><span id="legend-trend-projected-text">Projected</span></span>
|
||
<span class="lg trend-yesterday" id="legend-trend-yesterday"><span class="sw"></span><span id="legend-trend-yesterday-text">Yesterday</span></span>
|
||
<span class="lg trend-avg" id="legend-trend-avg"><span class="sw"></span><span id="legend-trend-avg-text">7-day avg</span></span>
|
||
<span class="lg trend-goal" id="legend-trend-goal" hidden><span class="sw"></span><span id="legend-trend-goal-text">Goal</span></span>
|
||
</div>
|
||
<p class="muted-note">Hours slept so far at each point of the day, against yesterday and the average over the picked chart window. The dashed tail continues today's line the way the average day usually plays out. The axis is stretched above 10h to give the hours around the goal more room.</p>
|
||
</section>
|
||
|
||
<!-- One panel for the three views of the same events: how many a day,
|
||
how much food went with them, and what hours they fall in. -->
|
||
<section class="patterns" data-panel="counts">
|
||
<h2>Pees, poos & meals <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
||
<div class="chart">
|
||
<div class="chart-title">Daily counts</div>
|
||
<svg id="chart-counts" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Pee, poo and meal counts per day"></svg>
|
||
<div class="legend legend-toggle" id="counts-metrics" role="group" aria-label="Which counts to show">
|
||
<label class="lg pee"><input type="checkbox" data-metric="pees" checked /><span class="sw"></span>Pees</label>
|
||
<label class="lg poo"><input type="checkbox" data-metric="poos" checked /><span class="sw"></span>Poos</label>
|
||
<label class="lg eat"><input type="checkbox" data-metric="meals" checked /><span class="sw"></span>Meals</label>
|
||
</div>
|
||
</div>
|
||
<div class="chart" id="grams-chart-wrap" hidden>
|
||
<div class="chart-title">Food (grams)</div>
|
||
<svg id="chart-grams" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Grams of food eaten per day"></svg>
|
||
</div>
|
||
<div class="chart">
|
||
<div class="chart-title">By hour of day</div>
|
||
<svg id="chart-hour-heatmap" class="chart-svg" viewBox="0 0 320 120" role="img" aria-label="Pee, poo and meal frequency by hour of day"></svg>
|
||
<p id="hour-heatmap-info" class="muted-note hour-point-info" aria-live="polite"></p>
|
||
<p class="muted-note">Darker = happens more often at that hour.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="walks" data-panel="walks">
|
||
<h2>Walks <span class="muted-note" id="walk-total"></span></h2>
|
||
<ul id="walk-list" class="wake-list"></ul>
|
||
<p id="walk-empty" class="empty">No walks yet for this day. Use 🦮 Walk start / 🏁 Walk end to time one.</p>
|
||
<div class="chart walk-chart" id="walk-chart-wrap" hidden>
|
||
<div class="chart-title">Minutes per day <span data-chart-days-label>(last 7 days)</span></div>
|
||
<svg id="chart-walk" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Minutes walked per day"></svg>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- The two walk patterns mirror the sleep ones below, drawn from walk
|
||
windows instead of sleep windows. Both stay hidden until there is a
|
||
walk to draw, so they cost nothing to anyone not tracking walks. -->
|
||
<section class="patterns" data-panel="walk-timeline" hidden>
|
||
<h2><span id="walk-timeline-title">When walking</span> <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
||
<svg id="chart-walk-timeline" class="chart-svg" viewBox="0 0 320 125" role="img" aria-label="Walks per day"></svg>
|
||
<p class="muted-note">Each row is a day, midnight to midnight; shaded = out on a walk. Tap a row to open that day.</p>
|
||
</section>
|
||
|
||
<section class="patterns" data-panel="walk-trend" hidden>
|
||
<h2>Walk trend</h2>
|
||
<svg id="chart-walk-trend" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Cumulative minutes walked through the selected day, the day before it, and the recent average"></svg>
|
||
<div class="legend">
|
||
<span class="lg wtrend-today"><span class="sw"></span><span id="legend-wtrend-today-text">Today</span></span>
|
||
<span class="lg wtrend-yesterday" id="legend-wtrend-yesterday"><span class="sw"></span><span id="legend-wtrend-yesterday-text">Yesterday</span></span>
|
||
<span class="lg wtrend-avg" id="legend-wtrend-avg"><span class="sw"></span><span id="legend-wtrend-avg-text">7-day avg</span></span>
|
||
</div>
|
||
<p class="muted-note">Minutes walked so far at each point of the day, against yesterday and the average over the picked window. The line climbs only while a walk is on, so every step is one walk.</p>
|
||
</section>
|
||
|
||
<section class="weight" data-panel="weight">
|
||
<h2>Weight</h2>
|
||
<div class="weight-summary">
|
||
<div class="stat">
|
||
<div class="stat-label">Latest</div>
|
||
<div class="stat-value" id="weight-latest">—</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-label">Since last</div>
|
||
<div class="stat-value" id="weight-change">—</div>
|
||
</div>
|
||
</div>
|
||
<div class="chart">
|
||
<div class="chart-title">Weight (kg)</div>
|
||
<svg id="chart-weight" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Weight in kilograms over time"></svg>
|
||
<p id="weight-point-info" class="muted-note weight-point-info"></p>
|
||
</div>
|
||
<!-- Folds on its own h3, independently of the Weight panel around it:
|
||
the row list grows by one every weigh-in, and the summary and the
|
||
curve above it are what you usually want left on screen. -->
|
||
<div class="subpanel" data-panel="weight-history">
|
||
<h3>History</h3>
|
||
<ul id="weight-list" class="wake-list"></ul>
|
||
<p id="weight-empty" class="empty">No weigh-ins logged yet.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="notes-log" data-panel="notes">
|
||
<h2>Notes</h2>
|
||
<ul id="notes-list" class="event-list"></ul>
|
||
<p id="notes-empty" class="empty">No notes yet. Use the 📝 Note button to jot down things like vaccinations or vet visits — they'll be listed here across every day.</p>
|
||
</section>
|
||
|
||
<section class="history" data-panel="history">
|
||
<h2>History</h2>
|
||
<ul id="event-list" class="event-list"></ul>
|
||
<p id="empty-state" class="empty">No events logged for this day.</p>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="app-footer">
|
||
<button type="button" id="changelog-btn" class="linklike">Changelog</button>
|
||
</footer>
|
||
</div><!-- /#app -->
|
||
|
||
<!-- Pedigree lookup. A distinct full-screen view (hides #app while open)
|
||
that resolves a dog by chip / registration number / name against SKK
|
||
and renders its ancestry as a tree. Online-only. -->
|
||
<div id="pedigree-screen" class="pedigree-screen" hidden>
|
||
<header class="pedigree-header">
|
||
<button type="button" id="pedigree-back" class="ghost icon-btn" aria-label="Back to tracker" title="Back">←</button>
|
||
<h1>🌳 Pedigree</h1>
|
||
<div class="ped-controls">
|
||
<button type="button" id="ped-view" class="ghost">Fan view</button>
|
||
<button type="button" id="ped-foldall" class="ghost">Collapse all</button>
|
||
<div class="ped-zoom" role="group" aria-label="Zoom">
|
||
<button type="button" id="ped-zoom-out" class="ghost icon-btn" aria-label="Zoom out" title="Zoom out">−</button>
|
||
<button type="button" id="ped-zoom-reset" class="ghost" aria-label="Reset zoom" title="Reset zoom">100%</button>
|
||
<button type="button" id="ped-zoom-in" class="ghost icon-btn" aria-label="Zoom in" title="Zoom in">+</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<main class="pedigree-main">
|
||
<p class="muted-note pedigree-hint">
|
||
Your dog's ancestry from <strong>SKK Hunddata</strong>, traced from the
|
||
ID set in Settings. The first generations show at once, then the line
|
||
fills in further back.
|
||
<button type="button" id="pedigree-refresh" class="linklike">Refresh</button>
|
||
</p>
|
||
<p id="pedigree-status" class="pedigree-status" hidden></p>
|
||
<div id="pedigree-choose" class="pedigree-choose" hidden></div>
|
||
<div id="pedigree-subject" class="pedigree-subject" hidden></div>
|
||
<p id="pedigree-repeat-note" class="muted-note pedigree-repeat-note" hidden>Some ancestors appear in more than one place further back (pedigree collapse). Expand the tree to reveal their ×N badges, then tap one to highlight every spot that dog appears.</p>
|
||
<div id="pedigree-tree" class="pedigree-tree"></div>
|
||
<p id="pedigree-caption" class="pedigree-caption" hidden></p>
|
||
</main>
|
||
</div><!-- /#pedigree-screen -->
|
||
|
||
<dialog id="changelog-dialog">
|
||
<form method="dialog" id="changelog-form">
|
||
<h3>Changelog</h3>
|
||
<ul id="changelog-list" class="changelog-list"></ul>
|
||
<p id="changelog-empty" class="empty" hidden>No changelog available.</p>
|
||
<menu>
|
||
<button value="close">Close</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="settings-dialog">
|
||
<form method="dialog" id="settings-form">
|
||
<h3>Puppy settings</h3>
|
||
<label>Name
|
||
<input type="text" id="settings-name" placeholder="e.g. Rex" autocomplete="off" />
|
||
</label>
|
||
<label>Birthday
|
||
<input type="date" id="settings-birthday" />
|
||
</label>
|
||
<label>Pedigree ID
|
||
<input type="text" id="settings-pedigree" autocomplete="off" spellcheck="false"
|
||
placeholder="SKK chip or reg. number (optional)" />
|
||
</label>
|
||
<p class="settings-hint">Set your dog's SKK chip or registration number to unlock the 🌳 pedigree page.</p>
|
||
<label class="toggle-row">
|
||
<span>Dark mode</span>
|
||
<input type="checkbox" id="settings-theme" role="switch" class="switch" />
|
||
</label>
|
||
<label class="toggle-row">
|
||
<span>Pee/poo confetti 💩</span>
|
||
<input type="checkbox" id="settings-confetti" role="switch" class="switch" />
|
||
</label>
|
||
<hr class="settings-sep" />
|
||
<div id="reminders-section" hidden>
|
||
<h4 class="settings-subhead">Reminders</h4>
|
||
<label class="toggle-row">
|
||
<span>Push notifications</span>
|
||
<input type="checkbox" id="reminders-enabled" role="switch" class="switch" />
|
||
</label>
|
||
<p class="settings-hint" id="reminders-hint" hidden></p>
|
||
<div id="reminders-rules" hidden></div>
|
||
<button type="button" id="reminders-test" class="ghost" hidden>Send a test notification</button>
|
||
</div>
|
||
<menu>
|
||
<button value="cancel" class="ghost">Cancel</button>
|
||
<button value="save" id="settings-save">Save</button>
|
||
</menu>
|
||
<hr class="settings-sep" />
|
||
<button type="button" id="delete-account-btn" class="danger danger-block">Delete account…</button>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="delete-account-dialog">
|
||
<form method="dialog" id="delete-account-form">
|
||
<h3>Delete account</h3>
|
||
<p class="danger-text">
|
||
This permanently deletes your account and <strong>all its data</strong> —
|
||
every event, photo and your puppy profile. This can't be undone.
|
||
</p>
|
||
<label>Confirm your password
|
||
<input type="password" id="delete-account-password" autocomplete="current-password" />
|
||
</label>
|
||
<p id="delete-account-error" class="auth-error" hidden></p>
|
||
<menu>
|
||
<button value="cancel" class="ghost">Cancel</button>
|
||
<button type="button" id="delete-account-confirm" class="danger">Delete forever</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="exercise-dialog">
|
||
<form method="dialog" id="exercise-form">
|
||
<h3 id="exercise-title">Add exercise</h3>
|
||
<label>Name
|
||
<input type="text" id="exercise-name" placeholder="e.g. Sit" autocomplete="off" />
|
||
</label>
|
||
<label>How to do it
|
||
<textarea id="exercise-note" rows="5" placeholder="Reminder for how to train it, e.g. lure with a treat, mark the moment the butt touches the ground, reward"></textarea>
|
||
</label>
|
||
<menu>
|
||
<!-- type="button" keeps Save the form's default button, so Enter /
|
||
the iOS keyboard's "Go" saves instead of silently hitting the
|
||
(hidden) Delete button via implicit form submission. -->
|
||
<button type="button" value="delete" id="exercise-delete" class="danger" hidden>Delete</button>
|
||
<button type="button" value="cancel" class="ghost">Cancel</button>
|
||
<button value="save" id="exercise-save">Save</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="note-dialog">
|
||
<form method="dialog" id="note-form">
|
||
<h3 id="note-title">Add note</h3>
|
||
<label>Time
|
||
<div class="time-row">
|
||
<input type="date" id="note-date" />
|
||
<input type="time" id="note-time" lang="en-GB" />
|
||
<button type="button" id="note-time-now" class="ghost">Now</button>
|
||
</div>
|
||
</label>
|
||
<label id="note-weight-field" hidden>Weight (kg)
|
||
<input type="number" id="note-weight" inputmode="decimal" step="0.01" min="0" placeholder="e.g. 5.2" />
|
||
</label>
|
||
<label id="note-grams-field" hidden>Amount (g)
|
||
<input type="number" id="note-grams" inputmode="numeric" step="1" min="0" placeholder="e.g. 80 — leave empty if unknown" />
|
||
</label>
|
||
<label>Note
|
||
<textarea id="note-input" rows="4" placeholder="e.g. pee was instant, poo took 5min, ate 300g raw food"></textarea>
|
||
</label>
|
||
<div class="photo-field">
|
||
<input type="file" id="note-photo-input" accept="image/*" multiple hidden />
|
||
<button type="button" id="note-photo-btn" class="ghost">📷 Add photos</button>
|
||
<div id="note-photo-preview" class="photo-preview" hidden></div>
|
||
</div>
|
||
<menu>
|
||
<button value="cancel" class="ghost">Cancel</button>
|
||
<button value="save" id="note-save">Save</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="edit-dialog">
|
||
<form method="dialog" id="edit-form">
|
||
<h3>Edit event</h3>
|
||
<label>Time
|
||
<div class="time-row">
|
||
<input type="date" id="edit-date" />
|
||
<input type="time" id="edit-time" lang="en-GB" />
|
||
</div>
|
||
</label>
|
||
<label id="edit-weight-field" hidden>Weight (kg)
|
||
<input type="number" id="edit-weight" inputmode="decimal" step="0.01" min="0" />
|
||
</label>
|
||
<label id="edit-grams-field" hidden>Amount (g)
|
||
<input type="number" id="edit-grams" inputmode="numeric" step="1" min="0" />
|
||
</label>
|
||
<label>Note
|
||
<textarea id="edit-note" rows="4"></textarea>
|
||
</label>
|
||
<div class="photo-field">
|
||
<input type="file" id="edit-photo-input" accept="image/*" multiple hidden />
|
||
<button type="button" id="edit-photo-btn" class="ghost">📷 Add photos</button>
|
||
<div id="edit-photo-preview" class="photo-preview" hidden></div>
|
||
</div>
|
||
<menu>
|
||
<button value="delete" id="edit-delete" class="danger">Delete</button>
|
||
<button value="cancel" class="ghost">Cancel</button>
|
||
<button value="save">Save</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="lightbox">
|
||
<button type="button" id="lightbox-close" class="lightbox-close" aria-label="Close">×</button>
|
||
<img id="lightbox-img" alt="" />
|
||
</dialog>
|
||
|
||
<!-- Brief confirmation after a one-tap quick log, with Undo / Add note. -->
|
||
<div id="snackbar" class="snackbar" hidden role="status" aria-live="polite">
|
||
<span id="snackbar-msg" class="snackbar-msg"></span>
|
||
<button type="button" id="snackbar-note" class="snackbar-action">Add note</button>
|
||
<button type="button" id="snackbar-undo" class="snackbar-action">Undo</button>
|
||
</div>
|
||
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|