Files
puppy-tracker/src/style.css
T
Alexander Heldt 31e04fb9a1 Lift the chart window out of the Sleep panel
It governs nearly every panel on the page — the training grid, the timing
charts, both sleep patterns, the counts panel, all three walk views — but it
sat inside Sleep, so it read as a sleep setting. That was already the reason
its old home carried a comment explaining it was not one; a control needing a
comment to deny what its position says is in the wrong position.

It now has its own row under the quick actions, above the first panel that
reads it, with a "Charts cover" label to say what it does. The row skips the
card chrome: it is a control, not content, and giving it a section's surface
and shadow would have made it look like a panel with nothing in it.

No JS change — both the click wiring and renderChartWindow address the buttons
by class, so they never cared where the picker lived.

Also removes that old comment, which had stopped being wrong about the picker
and started being wrong about the panel: the walks move carried it along by
accident and left it sitting above Weight, explaining a control three panels
away.
2026-09-01 07:58:14 +00:00

1673 lines
45 KiB
CSS
Raw 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.
:root {
--bg: #f7f5ff;
--surface: #ffffff;
--text: #1c1b22;
--muted: #6b6b78;
--accent: #7c5cff;
--accent-soft: #ebe6ff;
--sleep: #6c8cff;
--eat: #ff9b3d;
--pee: #ffd23f;
--poo: #8a5a3b;
--weight: #2bb3a3;
--training: #b04ecf;
--walk: #3f9e63;
/* Asleep and awake each come in two values: the bright one for stripes,
tints and fills, and an -ink for text on those pale grounds. Both inks
flip with the theme; the brights don't. --sleep itself was doing both
jobs and failing the second — 2.6:1 on the timer pill it painted. */
--sleep-ink: #4258cc;
--wake: #f2b705;
--wake-ink: #6f4d00;
/* The two timers, on the dark theme, take a near-white with a sixth of the
gold mixed into it (#fdf4da): warm enough to belong to the sunshine, far
enough from --wake-ink not to be yellow text on a yellow pill again. In
the light theme the same pill is near-white cream, so anything pale would
vanish and it keeps the dark ink. Separate from --wake-ink so the list's
AWAKE labels stay gold and go on matching their stripe. */
--wake-timer-ink: #6f4d00;
--note: #6f7a90;
--danger: #d64545;
--gain: #2e9e5b;
--border: #e9e6f5;
--radius: 12px;
--shadow: 0 1px 2px rgba(20, 14, 60, 0.05), 0 4px 16px rgba(20, 14, 60, 0.05);
}
/* Dark palette. Two triggers, same values (CSS has no way to share them):
system prefers dark and the user hasn't forced light, OR the user picked
dark via the Settings toggle. The [data-theme] attribute (set from JS) beats
the media query on specificity, so an explicit choice always wins. */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #14131a;
--surface: #1f1e28;
--text: #ebeaf2;
--muted: #9c9bab;
--accent: #a690ff;
--accent-soft: #2a2640;
--sleep-ink: #8ba4ff;
--wake-ink: #f5c451;
--wake-timer-ink: #fdf4da;
--border: #2c2a3a;
--shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 4px 16px rgba(0, 0, 0, 0.3);
}
}
:root[data-theme="dark"] {
--bg: #14131a;
--surface: #1f1e28;
--text: #ebeaf2;
--muted: #9c9bab;
--accent: #a690ff;
--accent-soft: #2a2640;
--sleep-ink: #8ba4ff;
--wake-ink: #f5c451;
--wake-timer-ink: #fdf4da;
--border: #2c2a3a;
--shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 4px 16px rgba(0, 0, 0, 0.3);
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.4;
}
body {
max-width: 720px;
margin: 0 auto;
padding: env(safe-area-inset-top, 0) 16px env(safe-area-inset-bottom, 0) 16px;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 4px 8px;
}
h1 {
font-size: 1.5rem;
margin: 0;
}
.title {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1 1 auto;
min-width: 0;
}
/* Truncate the name so a long one never runs into the action buttons. Scoped to
the header title so the auth-screen heading is unaffected. */
#app-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.puppy-age {
font-size: 0.8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
/* One line in the common case; on a very narrow screen it wraps rather than
truncating, so the age is never cut off. The name (#app-title) is what
truncates to keep the buttons clear. */
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.icon-btn {
padding: 6px 10px;
font-size: 1.1rem;
line-height: 1;
}
h2 {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin: 0 0 12px;
}
main {
display: flex;
flex-direction: column;
gap: 24px;
padding: 16px 0 32px;
}
section {
background: var(--surface);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 16px;
}
/* Paints over the notch/status-bar strip so content scrolling behind the
sticky day bar never peeks through above it. Zero-height where there is no
safe-area inset. */
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
height: env(safe-area-inset-top, 0px);
background: var(--bg);
z-index: 65;
}
.day-bar {
position: sticky;
top: env(safe-area-inset-top, 0px);
z-index: 60;
display: flex;
gap: 6px;
align-items: center;
justify-content: flex-end;
flex-wrap: nowrap;
padding: 8px 10px;
}
/* The face button shows the short date; the real input sits invisibly behind
it so its native picker still anchors here (and .showPicker() has a target). */
.day-date {
position: relative;
flex-shrink: 0;
display: inline-flex;
}
.day-date input[type="date"] {
position: absolute;
inset: 0;
width: 100%;
opacity: 0;
pointer-events: none;
}
#day-date-face {
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.day-bar button:not(.bar-clock) {
padding: 8px 10px;
flex-shrink: 0;
}
/* Keep the single row intact on narrow phones. */
@media (max-width: 370px) {
/* Three columns leave ~55px of text room on the narrowest phones, and
"⚖️ Weight" is the longest of the three; trimming the side padding keeps
it on one line down to about 360px. */
.action-row.three-up button { padding-left: 6px; padding-right: 6px; }
.day-bar { gap: 4px; }
.day-bar button:not(.bar-clock) { padding: 8px 7px; }
.bar-clock { font-size: 0.9rem; padding: 6px 8px; gap: 5px; }
}
.day-bar button:not(.bar-clock):disabled {
opacity: 0.4;
cursor: not-allowed;
}
.bar-clock {
display: flex;
/* It is a button (tapping it flips the sleep state), so undo the default
accent look — the asleep/awake classes below paint it. */
background: var(--surface);
color: var(--text);
align-items: center;
gap: 6px;
margin-right: auto; /* pin left; the flexible gap sits between it and the day controls */
padding: 7px 10px;
border-radius: 999px;
font-weight: 700;
font-size: 1rem;
font-variant-numeric: tabular-nums;
flex-shrink: 0;
}
.bar-clock[hidden] { display: none; }
/* Big timer still on screen: keep the pill's slot but show nothing. */
.bar-clock.standby { visibility: hidden; }
.bar-clock.asleep { background: color-mix(in srgb, var(--sleep) 18%, var(--surface)); color: var(--sleep-ink); }
/* Dark text on the yellow, the way the pee button already does it: a gold
light enough to read as sunshine is never legible as text on a pale ground.
12% where asleep takes 18% — equal percentages of these two hues are not
equally strong, and yellow at 18% shouted while the blue did not. */
.bar-clock.awake { background: color-mix(in srgb, var(--wake) 12%, var(--surface)); color: var(--wake-timer-ink); }
.big-clock {
text-align: center;
padding: 24px 16px;
}
.big-clock .bc-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
margin-bottom: 6px;
}
.big-clock .bc-time {
font-size: 3.25rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1.05;
letter-spacing: -0.01em;
}
.big-clock .bc-since {
margin-top: 6px;
font-size: 0.8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.big-clock.asleep { background: linear-gradient(180deg, var(--surface), color-mix(in srgb, var(--sleep) 10%, var(--surface))); }
.big-clock.asleep .bc-time { color: var(--sleep-ink); }
.big-clock.awake { background: linear-gradient(180deg, var(--surface), color-mix(in srgb, var(--wake) 10%, var(--surface))); }
.big-clock.awake .bc-time { color: var(--wake-timer-ink); }
/* Quick actions: a stack of explicit rows (see index.html) instead of one
auto-fit grid, so the grouping is the same at every width. Equal columns
within a row, two by default and three for the dialog row at the bottom. */
.actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.action-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.action-row.three-up { grid-template-columns: repeat(3, 1fr); }
button {
font: inherit;
border: none;
border-radius: var(--radius);
padding: 14px 12px;
cursor: pointer;
background: var(--accent);
color: white;
font-weight: 600;
transition: transform 0.08s ease, filter 0.15s ease;
display: inline-flex;
align-items: center;
justify-content: center;
}
button:active { transform: scale(0.97); }
button:hover { filter: brightness(1.05); }
button.action.sleep { background: var(--sleep); }
button.action.eat { background: var(--eat); }
button.action.pee { background: var(--pee); color: #2b240a; }
button.action.poo { background: var(--poo); }
button.action.weight { background: var(--weight); }
button.action.walk { background: var(--walk); }
button.action.note { background: var(--note); }
/* Unlikely given the current sleep state (see renderActionHints) — dimmed
but fully tappable, so corrections are never blocked. */
button.action.unlikely { opacity: 0.4; }
/* The boundary that would repeat the latest sleep event is disabled outright
(see renderActionHints) — greyed out rather than merely dimmed, so it reads
as unavailable instead of just discouraged. */
button.action:disabled {
opacity: 0.35;
filter: grayscale(1);
cursor: not-allowed;
}
button.action:disabled:active { transform: none; }
button.action:disabled:hover { filter: grayscale(1); }
button.ghost {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
font-weight: 500;
padding: 10px 14px;
}
button.danger { background: var(--danger); }
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
gap: 10px;
margin-bottom: 16px;
}
.stat {
background: var(--accent-soft);
padding: 12px;
border-radius: var(--radius);
text-align: center;
}
.stat-label {
font-size: 0.75rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.stat-sub {
color: var(--muted);
font-size: 0.7rem;
margin-top: 2px;
}
.stat-value {
font-size: 1.25rem;
font-weight: 700;
margin-top: 4px;
}
.lasts {
display: flex;
flex-direction: column;
gap: 6px;
}
.last-row {
display: flex;
justify-content: space-between;
padding: 6px 4px;
font-size: 0.95rem;
border-bottom: 1px dashed var(--border);
}
.last-row:last-child { border-bottom: none; }
.last-row span:first-child { color: var(--muted); }
.muted-note {
color: var(--muted);
font-size: 0.8rem;
font-weight: normal;
}
/* ---------- timing range charts ---------- */
.timing-charts {
display: flex;
flex-direction: column;
gap: 12px;
}
.timing-name {
color: var(--muted);
font-size: 0.95rem;
padding: 0 4px;
}
.timing-chart { display: block; width: 100%; height: auto; }
/* Values wear ink, the words beside them are muted — the band alone carries
which type a chart is about (its heading says it too). */
.timing-chart text { fill: var(--text); font-size: 9px; font-variant-numeric: tabular-nums; }
.timing-chart .tm-word { fill: var(--muted); }
.timing-chart .tm-track { fill: var(--border); }
.timing-chart .tm-tick { stroke: var(--muted); stroke-width: 1; opacity: 0.45; }
.timing-chart .tm-band { fill-opacity: 1; }
/* The full spread sits behind the solid shortest-to-typical stretch, faded so
the everyday half of the range stays the one that reads first. */
.timing-chart .tm-range { fill-opacity: 0.32; }
.timing-chart .tm-pee { fill: var(--pee); }
.timing-chart .tm-poo { fill: var(--poo); }
.timing-chart .tm-eat { fill: var(--eat); }
/* "Since the last one" marker: ink over a surface-coloured ring, so it stays
readable on top of any band colour in either theme. */
.timing-chart .tm-now-ring { stroke: var(--surface); stroke-width: 7; stroke-linecap: round; }
.timing-chart .tm-now { stroke: var(--text); stroke-width: 3; stroke-linecap: round; }
.timing-chart .tm-over { fill: var(--text); }
.timing-note { margin: 2px 4px 0; }
.settings-hint { color: var(--muted); font-size: 0.8rem; margin: -4px 0 4px; line-height: 1.4; }
.history-controls {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.history-controls label { color: var(--muted); font-size: 0.85rem; }
input[type="date"], input[type="datetime-local"], input[type="text"], input[type="number"], textarea {
font: inherit;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 10px;
width: 100%;
}
textarea { resize: vertical; }
.event-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.wake-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.ww {
display: flex;
align-items: center;
gap: 10px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 12px;
}
.ww .ww-range {
font-variant-numeric: tabular-nums;
color: var(--muted);
min-width: 120px;
}
.ww .ww-dur {
font-weight: 600;
flex: 1;
}
.ww.ongoing {
border-color: var(--accent);
background: var(--accent-soft);
}
/* Every row can carry a state, not only the open one: the merged sleep/wake
list labels each row asleep or awake, where the label is the whole point. */
.ww .ww-tag {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
}
/* A stripe in the row's own colour, so an alternating list reads as alternating
before you get to the words. */
.ww.sleep-ww { border-left: 3px solid color-mix(in srgb, var(--sleep) 55%, transparent); }
.ww.wake-ww { border-left: 3px solid color-mix(in srgb, var(--wake) 75%, transparent); }
.ww.walk-ww { border-left: 3px solid color-mix(in srgb, var(--walk) 55%, transparent); }
.ww.sleep-ww .ww-tag { color: var(--sleep-ink); }
.ww.wake-ww .ww-tag { color: var(--wake-ink); }
.ww.walk-ww .ww-tag { color: var(--walk); }
.ww.ongoing .ww-tag { color: var(--accent); }
.ww.sleep-ww.ongoing {
border-color: var(--sleep);
background: color-mix(in srgb, var(--sleep) 14%, var(--surface));
}
.ww.sleep-ww.ongoing .ww-tag { color: var(--sleep-ink); }
/* Spelled out like the other two: the faded stripe above would otherwise win
over the generic .ww.ongoing on specificity ties and leave the open awake
row without its full-strength edge. The tag needs saying too — .ww.wake-ww
.ww-tag ties with .ww.ongoing .ww-tag and loses on order. */
.ww.wake-ww.ongoing {
border-color: var(--wake);
background: color-mix(in srgb, var(--wake) 16%, var(--surface));
}
.ww.wake-ww.ongoing .ww-tag { color: var(--wake-ink); }
.ww.walk-ww.ongoing {
border-color: var(--walk);
background: color-mix(in srgb, var(--walk) 14%, var(--surface));
}
.ww.walk-ww.ongoing .ww-tag { color: var(--walk); }
.event {
display: flex;
align-items: center;
gap: 10px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 12px;
cursor: pointer;
}
.event .dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
/* Rail bracketing a sleep or walk pair in the history log (see historyRails).
It is drawn in the section's own padding, left of the cards, so tying rows
together costs no layout: nothing indents and no row moves. The anchored end
stops at the row's middle, level with its dot; the other ends overshoot the
6px list gap by 4px each so consecutive segments overlap into one line. */
.event.rail { position: relative; }
.event.rail::before {
content: "";
position: absolute;
left: -10px;
width: 0;
border-left: 2px dotted var(--rail, var(--border));
}
.event.rail-top::before { top: 50%; bottom: -4px; }
.event.rail-mid::before { top: -4px; bottom: -4px; }
.event.rail-bottom::before { top: -4px; bottom: 50%; }
.event.rail-sleep { --rail: color-mix(in srgb, var(--sleep) 70%, transparent); }
.event.rail-walk { --rail: color-mix(in srgb, var(--walk) 70%, transparent); }
.event[data-type="sleep-start"] .dot,
.event[data-type="sleep-end"] .dot { background: var(--sleep); }
.event[data-type="eat"] .dot { background: var(--eat); }
.event[data-type="pee"] .dot { background: var(--pee); }
.event[data-type="poo"] .dot { background: var(--poo); }
.event[data-type="weight"] .dot { background: var(--weight); }
.event[data-type="training"] .dot { background: var(--training); }
.event[data-type="walk-start"] .dot,
.event[data-type="walk-end"] .dot { background: var(--walk); }
.event[data-type="note"] .dot { background: var(--note); }
.event .time { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 60px; }
.event .label { font-weight: 600; min-width: 110px; }
.event .note { color: var(--muted); font-size: 0.9rem; flex: 1; }
/* Notes log rows: a date instead of a time-of-day, then the note text. */
.event .note-date { font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.event .note-text { flex: 1; white-space: pre-wrap; overflow-wrap: anywhere; }
.empty {
color: var(--muted);
text-align: center;
padding: 12px 0;
}
.status-pill {
font-size: 0.75rem;
padding: 4px 10px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
font-weight: 600;
}
.status-pill.offline { background: #ffe0e0; color: #b03030; }
.status-pill.error { background: #ffe0e0; color: #b03030; }
.status-pill.syncing { background: #e0eaff; color: #2a4ab0; }
.status-pill.pending { background: #fff1d4; color: #8a5a00; }
@media (prefers-color-scheme: dark) {
.status-pill.offline { background: #4a1f1f; color: #ff9090; }
.status-pill.error { background: #4a1f1f; color: #ff9090; }
.status-pill.syncing { background: #1f2c4a; color: #9eb6ff; }
.status-pill.pending { background: #4a3a1f; color: #ffd28a; }
}
dialog {
border: none;
border-radius: var(--radius);
padding: 20px;
background: var(--surface);
color: var(--text);
box-shadow: var(--shadow);
max-width: 480px;
width: calc(100% - 32px);
}
dialog::backdrop { background: rgba(0,0,0,0.4); }
dialog h3 { margin: 0 0 12px; }
dialog label { display: block; font-size: 0.85rem; color: var(--muted); margin-bottom: 10px; }
/* The display rule above beats the UA [hidden] rule, so hide explicitly —
otherwise the weight/grams fields show on event types that don't use them. */
dialog label[hidden] { display: none; }
dialog label input, dialog label textarea { margin-top: 4px; }
dialog menu {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 0;
margin: 14px 0 0;
}
.photo-field {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.photo-field button { padding: 8px 12px; }
.photo-preview {
margin-top: 8px;
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.photo-preview[hidden] { display: none; }
.photo-thumb { position: relative; }
.photo-thumb img {
display: block;
width: 76px;
height: 76px;
object-fit: cover;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--accent-soft);
}
.photo-thumb-remove {
position: absolute;
top: -7px;
right: -7px;
width: 22px;
height: 22px;
padding: 0;
border-radius: 50%;
background: var(--danger);
color: #fff;
font-size: 14px;
line-height: 1;
}
.event .thumb {
width: 40px;
height: 40px;
border-radius: 6px;
object-fit: cover;
border: 1px solid var(--border);
flex-shrink: 0;
cursor: zoom-in;
background: var(--accent-soft);
}
#lightbox {
background: #000;
padding: 0;
border: none;
max-width: 100vw;
max-height: 100vh;
width: auto;
height: auto;
}
#lightbox::backdrop { background: rgba(0,0,0,0.85); }
#lightbox img {
display: block;
max-width: 95vw;
max-height: 95vh;
object-fit: contain;
}
.lightbox-close {
position: absolute;
top: 8px;
right: 8px;
background: rgba(0,0,0,0.5);
color: #fff;
font-size: 1.5rem;
line-height: 1;
padding: 4px 12px 6px;
border-radius: 999px;
font-weight: 400;
}
.time-row {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.time-row input { flex: 1 1 120px; min-width: 0; }
.time-row button { padding: 8px 12px; }
/* The chart window is a page-level control rather than a panel's, so its row
skips the card chrome the sections have: no surface, no shadow, just a label
and the buttons sitting on the page background between two cards. */
.chart-window {
display: flex;
align-items: center;
gap: 10px;
padding: 0 4px;
}
.chart-window-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.chart-days-picker {
display: flex;
gap: 6px;
margin: -4px 0 14px;
}
/* No heading above it out here, so nothing to pull up under or clear beneath. */
.chart-window .chart-days-picker { margin: 0; }
.chart-days-picker button {
padding: 5px 12px;
font-size: 0.8rem;
}
.chart-days-picker button.active {
background: var(--accent);
border-color: transparent;
color: #fff;
}
.chart { margin-bottom: 16px; }
.chart:last-child { margin-bottom: 0; }
.chart-title {
font-size: 0.8rem;
color: var(--muted);
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.chart-svg { display: block; width: 100%; height: auto; }
.chart-svg text { fill: var(--muted); font-size: 9px; }
/* Dense 1h y-axes: smaller labels so every gridline can carry one. */
.chart-svg text.y-dense { font-size: 7px; }
.chart-svg .grid {
stroke: var(--border);
stroke-dasharray: 2 3;
}
.chart-svg .bar { cursor: pointer; }
.chart-svg .bar-faded { opacity: 0.7; }
/* The selected day's slice in every per-day chart: a soft accent band behind
the bars/cells (visible even when the day's values are all zero) plus an
accent-colored day label. */
.chart-svg .day-highlight {
fill: var(--accent);
fill-opacity: 0.08;
stroke: var(--accent);
stroke-opacity: 0.45;
}
.chart-svg text.day-label-sel { fill: var(--accent); font-weight: 600; }
.chart-svg .bar-sleep { fill: var(--sleep); }
.chart-svg .bar-walk { fill: var(--walk); }
.chart-svg .bar-pee { fill: var(--pee); }
.chart-svg .bar-poo { fill: var(--poo); }
.chart-svg .bar-eat { fill: var(--eat); }
.chart-svg .weight-line { stroke: var(--weight); stroke-width: 2; fill: none; }
.chart-svg .weight-dot { fill: var(--weight); }
.chart-svg .weight-hit { fill: transparent; cursor: pointer; }
.chart-svg .weight-hit.active {
fill: color-mix(in srgb, var(--weight) 22%, transparent);
stroke: var(--weight);
stroke-width: 1.5;
}
.weight-point-info,
.hour-point-info {
margin: 6px 4px 0;
min-height: 1.2em;
text-align: center;
}
.weight-summary {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-bottom: 16px;
}
.weight-summary .stat-value.up { color: var(--gain); }
.weight-summary .stat-value.down { color: var(--danger); }
.ww.weight-ww { cursor: pointer; }
/* Keep each weight row to one line: the date/age column shrinks and ellipses,
the weight value stays a fixed size, right-aligned and always visible. */
.ww.weight-ww .ww-range {
min-width: 0;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ww.weight-ww .ww-dur { flex: 0 0 auto; text-align: right; }
.legend {
display: flex;
gap: 14px;
font-size: 0.8rem;
color: var(--muted);
margin-top: 8px;
flex-wrap: wrap;
}
.lg { display: inline-flex; align-items: center; gap: 5px; }
.lg .sw {
width: 10px;
height: 10px;
border-radius: 2px;
display: inline-block;
}
.lg.pee .sw { background: var(--pee); }
.lg.poo .sw { background: var(--poo); }
.lg.eat .sw { background: var(--eat); }
/* Interactive legend: each item is a checkbox that toggles its metric. */
.legend-toggle label.lg { cursor: pointer; user-select: none; }
.legend-toggle input[type="checkbox"] { margin: 0; cursor: pointer; }
.legend-toggle label.pee input { accent-color: var(--pee); }
.legend-toggle label.poo input { accent-color: var(--poo); }
.legend-toggle label.eat input { accent-color: var(--eat); }
/* Dim an unchecked item so it's clear its bars are hidden. */
.legend-toggle label.lg:has(input:not(:checked)) { opacity: 0.5; }
/* ---------- auth (login / register) ---------- */
.auth-screen {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: var(--bg);
z-index: 50;
}
/* The display rule above beats the UA [hidden] rule, so hide explicitly. */
.auth-screen[hidden] { display: none; }
.auth-card {
width: 100%;
max-width: 360px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 28px 22px;
}
.auth-card h1 { margin: 0 0 4px; font-size: 1.5rem; text-align: center; }
.auth-sub { margin: 0 0 20px; text-align: center; color: var(--muted); font-size: 0.9rem; }
.auth-card label {
display: block;
font-size: 0.85rem;
color: var(--muted);
margin-bottom: 12px;
}
.auth-card input[type="email"],
.auth-card input[type="password"],
.auth-card input[type="text"] {
font: inherit;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
width: 100%;
margin-top: 4px;
}
.auth-card #auth-submit { width: 100%; margin-top: 6px; }
.auth-error {
color: var(--danger);
font-size: 0.85rem;
margin: 0 0 12px;
text-align: center;
}
.auth-toggle {
margin: 16px 0 0;
text-align: center;
font-size: 0.85rem;
color: var(--muted);
}
button.linklike {
background: none;
border: none;
color: var(--accent);
font-weight: 600;
padding: 0 0 0 4px;
display: inline;
cursor: pointer;
}
button.linklike:hover { text-decoration: underline; filter: none; }
/* ---------- delete account ---------- */
.settings-sep {
border: none;
border-top: 1px solid var(--border);
margin: 18px 0 12px;
}
.danger-block { width: 100%; }
.danger-text {
font-size: 0.9rem;
color: var(--muted);
margin: 0 0 14px;
line-height: 1.4;
}
.danger-text strong { color: var(--danger); }
/* ---------- settings toggle switch ---------- */
.toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.toggle-row > span { color: var(--text); font-size: 0.95rem; }
input.switch {
appearance: none;
-webkit-appearance: none;
width: 44px;
height: 26px;
margin: 0;
border-radius: 13px;
background: var(--border);
position: relative;
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s ease;
}
input.switch::after {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: #fff;
transition: transform 0.15s ease;
}
input.switch:checked { background: var(--accent); }
input.switch:checked::after { transform: translateX(18px); }
/* ---------- update banner ---------- */
.update-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 70;
padding: calc(10px + env(safe-area-inset-top, 0)) 16px 10px;
background: var(--accent);
color: #fff;
box-shadow: var(--shadow);
}
.update-banner[hidden] { display: none; }
.update-banner-row {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
.update-changelog {
max-width: 480px;
margin: 6px auto 0;
padding: 0 0 0 18px;
font-size: 0.8rem;
line-height: 1.45;
opacity: 0.92;
}
.update-changelog[hidden] { display: none; }
.app-footer {
text-align: center;
padding: 4px 0 24px;
}
.app-footer .linklike { color: var(--muted); font-size: 0.8rem; }
.changelog-list {
list-style: none;
margin: 0 0 12px;
padding: 0;
max-height: 60vh;
overflow-y: auto;
}
.changelog-date {
color: var(--muted);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
margin: 10px 0 4px;
}
.changelog-date:first-child { margin-top: 0; }
.changelog-entry {
font-size: 0.9rem;
line-height: 1.45;
padding: 3px 0 3px 14px;
position: relative;
}
.changelog-entry::before {
content: "•";
position: absolute;
left: 2px;
color: var(--accent);
}
.update-banner-msg { font-size: 0.9rem; font-weight: 600; }
.update-banner-actions { display: flex; gap: 8px; }
.update-banner-btn {
background: #fff;
color: var(--accent);
font-weight: 700;
padding: 6px 14px;
}
.update-banner-btn.ghostish {
background: transparent;
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.6);
}
.update-banner-btn:hover { filter: brightness(0.97); }
/* ---------- quick-log snackbar ---------- */
.snackbar {
position: fixed;
left: 50%;
bottom: calc(16px + env(safe-area-inset-bottom, 0));
transform: translate(-50%, 12px);
z-index: 60;
display: flex;
align-items: center;
gap: 8px;
max-width: calc(100% - 32px);
padding: 8px 8px 8px 16px;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: 999px;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.18s ease, transform 0.18s ease;
}
.snackbar[hidden] { display: none; }
.snackbar.show {
opacity: 1;
transform: translate(-50%, 0);
pointer-events: auto;
}
.snackbar-msg {
font-size: 0.9rem;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.snackbar-action {
background: transparent;
color: var(--accent);
font-weight: 700;
padding: 6px 10px;
flex-shrink: 0;
}
.snackbar-action:hover { filter: none; background: var(--accent-soft); }
/* ---------- pattern charts (sleep timeline + hour heatmap) ---------- */
.chart-svg .stl-track {
fill: var(--bg);
stroke: var(--border);
stroke-width: 0.5;
}
.chart-svg .stl-sleep { fill: var(--sleep); }
/* Same actogram, walk windows instead of sleep ones (see drawActogram). */
.chart-svg .wtl-walk { fill: var(--walk); }
.chart-svg .stl-today { fill: var(--accent); font-weight: 600; }
/* Selected day: accent ring on the row's track + accent label. */
.chart-svg .stl-track.stl-selected {
stroke: var(--accent);
stroke-width: 1.2;
stroke-opacity: 0.8;
}
.chart-svg .stl-sel { fill: var(--accent); }
.chart-svg .stl-hit { fill: transparent; cursor: pointer; }
.chart-svg .stl-hit:hover { fill: var(--accent); fill-opacity: 0.08; }
/* Sleep trend lines: today strongest, the reference curves lighter/dashed. */
.chart-svg .trend-today {
stroke: var(--sleep);
stroke-width: 2.5;
stroke-linejoin: round;
fill: none;
}
.chart-svg .trend-yesterday {
stroke: var(--eat); /* orange — the gray it had before sank into the grid */
stroke-width: 1.5;
stroke-linejoin: round;
fill: none;
opacity: 0.85;
}
.chart-svg .trend-avg {
stroke: var(--weight); /* teal — keeps it apart from today's blue and its blue projected tail */
stroke-width: 1.5;
stroke-dasharray: 4 3;
stroke-linejoin: round;
fill: none;
opacity: 0.85;
}
/* Today's projected tail: same weight/color as today so it reads as its
continuation, dashed + faded because it hasn't happened yet. */
.chart-svg .trend-projected {
stroke: var(--sleep);
stroke-width: 2.5;
stroke-dasharray: 2 4;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
opacity: 0.55;
}
.chart-svg .trend-goal { fill: var(--sleep); fill-opacity: 0.12; }
.lg.trend-goal .sw { background: color-mix(in srgb, var(--sleep) 20%, var(--surface)); }
.lg.trend-today .sw { background: var(--sleep); }
.lg.trend-projected .sw { background: color-mix(in srgb, var(--sleep) 40%, var(--surface)); }
.lg.trend-yesterday .sw { background: var(--eat); }
.lg.trend-avg .sw { background: var(--weight); }
/* Walk trend: the sleep trend's line weights, in the walk palette. The goal is
a single line rather than a band — a walk total is a target to reach, with no
upper bound to overshoot. */
.chart-svg .wtrend-today {
stroke: var(--walk);
stroke-width: 2.5;
stroke-linejoin: round;
fill: none;
}
.chart-svg .wtrend-yesterday {
stroke: var(--eat);
stroke-width: 1.5;
stroke-linejoin: round;
fill: none;
opacity: 0.85;
}
.chart-svg .wtrend-avg {
stroke: var(--weight);
stroke-width: 1.5;
stroke-dasharray: 4 3;
stroke-linejoin: round;
fill: none;
opacity: 0.85;
}
.chart-svg .wtrend-goal {
stroke: var(--walk);
stroke-width: 1.5;
stroke-dasharray: 2 3;
opacity: 0.55;
}
.lg.wtrend-today .sw { background: var(--walk); }
.lg.wtrend-yesterday .sw { background: var(--eat); }
.lg.wtrend-avg .sw { background: var(--weight); }
.lg.wtrend-goal .sw { background: color-mix(in srgb, var(--walk) 45%, var(--surface)); }
.lg[hidden] { display: none; }
.chart-svg .hm-cell { stroke: none; }
/* Focused block: an accent ring, drawn at full opacity so it stays visible on
the palest (zero-count) cells. */
.chart-svg .hm-cell.hm-active {
stroke: var(--accent);
stroke-width: 1.2;
}
.chart-svg .hm-hit { fill: transparent; cursor: pointer; }
.chart-svg .hm-pee { fill: var(--pee); }
.chart-svg .hm-poo { fill: var(--poo); }
.chart-svg .hm-eat { fill: var(--eat); }
.chart-svg .hm-training { fill: var(--training); }
/* ---------- training ---------- */
.training-list {
list-style: none;
margin: 0 0 10px;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.training-list:empty { margin: 0; }
.exercise {
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 12px;
}
.ex-row {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
.ex-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.ex-name { font-weight: 600; }
.ex-meta { color: var(--muted); font-size: 0.8rem; }
button.ex-log {
background: var(--training);
padding: 8px 14px;
flex-shrink: 0;
}
.ex-detail { display: none; }
.exercise.expanded .ex-detail {
display: flex;
align-items: flex-start;
gap: 10px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--border);
}
.ex-note {
flex: 1;
margin: 0;
color: var(--muted);
font-size: 0.9rem;
line-height: 1.4;
white-space: pre-wrap;
}
button.ex-edit { padding: 6px 12px; flex-shrink: 0; }
.training-add { width: 100%; }
/* Charts that sit under a panel's list rather than being the whole panel, so
they need the separation a list-then-chart stack doesn't get for free. */
.training-chart { margin-top: 16px; }
.walk-chart { margin-top: 16px; }
/* ---------- collapsible panels ---------- */
/* A panel folds by its own heading: h2 for a whole section, h3 for a block
inside one. Scoped to main on purpose — the pedigree tree uses .collapsed
for its own branches, and a bare selector here would fold those too. */
main .collapsible > h2,
main .collapsible > h3 {
cursor: pointer;
position: relative;
padding-right: 20px;
-webkit-user-select: none;
user-select: none;
}
main .collapsible > h2::after,
main .collapsible > h3::after {
content: "▾";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
transition: transform 0.15s ease;
color: var(--muted);
font-size: 0.85em;
}
main .collapsed > h2::after,
main .collapsed > h3::after { transform: translateY(-50%) rotate(-90deg); }
main .collapsed > h2,
main .collapsed > h3 { margin-bottom: 0; }
main .collapsed > :not(h2):not(h3) { display: none; }
/* Sub-heading for a foldable block inside a panel: the section h2's look, a
size down, so it reads as subordinate to it rather than as a second panel. */
.subpanel > h3 {
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin: 0 0 10px;
}
/* ---------- pedigree lookup ---------- */
.pedigree-screen {
padding-top: 12px;
}
.pedigree-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 4px 8px;
}
.pedigree-header { flex-wrap: wrap; }
.pedigree-header h1 { font-size: 1.4rem; }
.ped-controls {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
#ped-foldall { padding: 5px 10px; font-size: 0.8rem; white-space: nowrap; }
.ped-zoom {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.ped-zoom .icon-btn { padding: 4px 9px; font-size: 1.1rem; }
#ped-zoom-reset {
padding: 5px 8px;
font-size: 0.8rem;
font-variant-numeric: tabular-nums;
min-width: 48px;
}
.pedigree-hint { margin: 4px 0 12px; }
/* The tree scales with the CSS `zoom` property, which reflows so the container
still scrolls to reach the edges at any zoom. */
.pedigree-tree { touch-action: pan-x pan-y; }
.pedigree-status {
margin: 10px 0;
font-size: 0.9rem;
color: var(--muted);
}
.pedigree-status.busy::before {
content: "";
display: inline-block;
width: 12px; height: 12px;
margin-right: 8px;
vertical-align: -1px;
border: 2px solid var(--accent);
border-top-color: transparent;
border-radius: 50%;
animation: ped-spin 0.8s linear infinite;
}
@keyframes ped-spin { to { transform: rotate(360deg); } }
.pedigree-status.error { color: var(--danger); }
.pedigree-status.done { color: var(--gain); }
/* disambiguation list */
.pedigree-choose {
display: grid;
gap: 8px;
margin: 8px 0 16px;
}
.ped-match {
display: flex;
flex-direction: column;
gap: 2px;
text-align: left;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
color: var(--text);
cursor: pointer;
}
.ped-match:hover { border-color: var(--accent); }
.ped-match-name { font-weight: 600; }
.ped-match-meta { font-size: 0.8rem; color: var(--muted); }
/* looked-up dog */
.pedigree-subject {
margin: 6px 0 14px;
padding: 12px 14px;
border: 1px solid var(--border);
border-left: 4px solid var(--accent);
border-radius: var(--radius);
background: var(--surface);
}
.ped-subject-name { font-size: 1.15rem; font-weight: 700; }
.ped-subject-meta { font-size: 0.85rem; color: var(--muted); margin-top: 2px; }
/* Top-down family tree: the dog on top, parents branching below, connected by
lines drawn with each <li>'s ::before/::after (the classic CSS tree). Wider
than the screen once expanded, so the container scrolls horizontally. */
.pedigree-tree {
overflow-x: auto;
padding: 8px 0 28px;
}
.ped-tree-h, .ped-tree-h ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.ped-tree-h {
/* "safe" centers when the tree fits and falls back to start-aligned (no
clipped/unreachable left edge) once it's wider than the screen. */
justify-content: safe center;
min-width: max-content;
padding: 4px 16px 12px;
}
.ped-tree-h ul {
justify-content: center;
position: relative;
padding-top: 22px; /* room for the connector from the parent above */
}
.ped-tree-h li {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 22px 6px 0;
}
/* Elbow from each child up to the horizontal bar shared by its siblings. */
.ped-tree-h li::before,
.ped-tree-h li::after {
content: "";
position: absolute;
top: 0;
width: 50%;
height: 22px;
border-top: 2px solid var(--border);
}
.ped-tree-h li::before { right: 50%; }
.ped-tree-h li::after { left: 50%; border-left: 2px solid var(--border); }
/* Vertical drop from a parent card down to its children's bar. */
.ped-tree-h ul::before {
content: "";
position: absolute;
top: 0;
left: 50%;
height: 22px;
border-left: 2px solid var(--border);
}
/* A lone parent connects with a straight line, no elbow. */
.ped-tree-h li:only-child::before,
.ped-tree-h li:only-child::after { display: none; }
/* Trim the outer half-lines at the ends of a sibling row. */
.ped-tree-h li:first-child::before,
.ped-tree-h li:last-child::after { border: 0 none; }
.ped-tree-h li:last-child::before { border-right: 2px solid var(--border); }
/* The dog sits on top with no connector above it. */
.ped-tree-h > li { padding-top: 0; }
.ped-tree-h > li::before,
.ped-tree-h > li::after { display: none; }
/* Collapsed: hide the ancestry below this dog (and its connectors go with it). */
.ped-tree-h li.collapsed > ul { display: none; }
.ped-card {
position: relative;
width: 140px;
box-sizing: border-box;
padding: 7px 18px 7px 20px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--surface);
box-shadow: var(--shadow);
text-align: center;
}
.ped-name { font-weight: 600; font-size: 0.85rem; line-height: 1.2; }
.ped-name.ped-unknown { color: var(--muted); font-weight: 500; font-style: italic; }
.ped-titles { font-size: 0.66rem; color: var(--accent); margin-top: 2px; line-height: 1.2; }
.ped-reg {
font-size: 0.66rem;
color: var(--muted);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
margin-top: 1px;
}
/* sire ♂ (blue) / dam ♀ (purple) accents on the parent cards */
.ped-sire > .ped-card { border-top: 3px solid var(--sleep); }
.ped-dam > .ped-card { border-top: 3px solid var(--training); }
.ped-sire > .ped-card::after,
.ped-dam > .ped-card::after {
position: absolute;
right: 6px;
top: 5px;
font-size: 0.72rem;
}
.ped-sire > .ped-card::after { content: "♂"; color: var(--sleep); }
.ped-dam > .ped-card::after { content: "♀"; color: var(--training); }
/* pedigree collapse: a dog filling more than one position gets a ×N badge, a
stable hue, and lights up (with every copy) when tapped. */
.ped-card.ped-repeat { cursor: pointer; }
.ped-repeat-badge {
position: absolute;
right: 5px;
bottom: 5px;
font-size: 0.6rem;
font-weight: 700;
line-height: 1;
padding: 2px 5px;
border-radius: 999px;
color: #fff;
background: hsl(var(--repeat-hue, 0), 58%, 48%);
}
.ped-card.ped-lit {
border-color: hsl(var(--repeat-hue, 0), 70%, 50%);
box-shadow: 0 0 0 2px hsl(var(--repeat-hue, 0), 70%, 50%), var(--shadow);
}
.pedigree-repeat-note { margin: 0 0 10px; }
/* expand/collapse toggle (top-left of the card) */
.ped-toggle {
position: absolute;
left: 5px;
top: 5px;
width: 18px; height: 18px;
padding: 0;
font-size: 0.9rem;
line-height: 1;
border: 1px solid var(--border);
border-radius: 5px;
background: var(--bg);
color: var(--muted);
cursor: pointer;
}
.ped-toggle:hover { border-color: var(--accent); color: var(--accent); }
/* ---- radial fan chart ---- */
.ped-fan { display: block; margin: 0 auto; max-width: none; }
.ped-wedge {
cursor: pointer;
stroke: var(--border);
stroke-width: 1;
/* outer rings tint gradually darker for depth */
fill: color-mix(in srgb, var(--accent-soft) calc(var(--gen, 1) * 7%), var(--surface));
transition: filter 0.1s ease;
}
.ped-wedge:hover { filter: brightness(0.95); }
/* a dog that appears more than once is filled with its stable hue */
.ped-wedge-repeat { fill: hsl(var(--repeat-hue, 0), 60%, 80%); stroke: hsl(var(--repeat-hue, 0), 45%, 60%); }
.ped-wedge.ped-lit {
fill: hsl(var(--repeat-hue, 0), 72%, 62%);
stroke: hsl(var(--repeat-hue, 0), 72%, 38%);
stroke-width: 2;
}
.ped-wedge-label {
font-size: 8px;
fill: var(--text);
text-anchor: middle;
dominant-baseline: central;
pointer-events: none;
}
.ped-fan-center { fill: var(--accent); stroke: none; cursor: pointer; }
.ped-fan-center-label {
fill: #fff;
font-size: 9px;
font-weight: 600;
dominant-baseline: central;
pointer-events: none;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .ped-wedge-label { fill: var(--text); }
}
.pedigree-caption {
margin: 10px 0 0;
font-size: 0.85rem;
color: var(--muted);
text-align: center;
}
/* ---- pee/poo confetti ---- */
#confetti-layer {
position: fixed;
inset: 0;
pointer-events: none;
overflow: hidden;
z-index: 9999;
}
.confetti-piece {
position: absolute;
line-height: 1;
will-change: transform, opacity;
animation-name: potty-firework;
animation-timing-function: ease-out;
/* `both` so the 0% state (invisible, at the bottom) also applies during the
per-piece launch delay — no flash before it takes off. */
animation-fill-mode: both;
}
/* Launch up from the bottom, rise to a peak while spreading sideways, slowing
(ease-out) and fading out as it reaches the top — a firework fountain.
Distances come from JS custom props. */
@keyframes potty-firework {
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5) rotate(0deg); }
10% { opacity: 1; }
70% { opacity: 1; }
100% { opacity: 0; transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--peakY))) scale(1) rotate(var(--rot)); }
}
@media (prefers-reduced-motion: reduce) {
.confetti-piece { display: none; }
}
/* ---------- reminders ---------- */
.settings-subhead {
margin: 0 0 10px;
font-size: 0.95rem;
color: var(--text);
}
#reminders-rules {
display: flex;
flex-direction: column;
gap: 12px;
margin: 12px 0;
}
.reminder-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
/* Overrides dialog's block labels: each rule is a switch and its name on one
line, with the interval pushed to the far end. */
.reminder-row .reminder-name {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
min-width: 0;
font-size: 0.9rem;
color: var(--text);
cursor: pointer;
}
.reminder-row .reminder-name input { margin-top: 0; }
.reminder-after {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
font-size: 0.85rem;
color: var(--muted);
}
input.reminder-mins {
width: 4.75rem;
padding: 6px 8px;
text-align: right;
}
/* The full-size switch crowds a row that also carries a number field. */
.reminder-row input.switch {
width: 36px;
height: 22px;
border-radius: 11px;
}
.reminder-row input.switch::after {
top: 3px;
left: 3px;
width: 16px;
height: 16px;
}
.reminder-row input.switch:checked::after { transform: translateX(14px); }
#reminders-test { width: 100%; }