* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* NIGHT MODE — one rule.
 *
 * invert(1) flips every colour on the page: white backgrounds become black,
 * black text becomes white, borders and lines invert with them. Nothing has
 * to be restyled per element, and anything added later is covered
 * automatically -- which is the point.
 *
 * hue-rotate(180deg) undoes the hue shift the invert causes, so the chart
 * series keep their identities: teal stays teal rather than becoming orange.
 * Brightness is still inverted, which is what we want; only the hue is put
 * back.
 *
 * Applied to <html> rather than <body> so the page margin inverts too,
 * leaving no white gutter at the edges. */
/* The night-mode button. Bottom-right, fixed, small enough to ignore and
   large enough to hit with a thumb while holding a baby. */
#night-toggle {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 200;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--panel-bg);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    /* The moon glyph renders as colour emoji on most systems, which the page
       invert would turn into a negative. Stripping the colour makes it a
       plain shape that inverts cleanly. */
    filter: grayscale(1);
}

#night-toggle:hover {
    background: var(--bg);
}

/* Night mode. Still one rule -- the whole feature is this line and a button.
 *
 * invert(1) alone is too harsh: the panels are #ffffff, so they invert to pure
 * #000000, and the text is #000000, so it inverts to pure #ffffff. Maximum
 * possible contrast, which is exactly what you do not want at 3am.
 *
 * brightness and contrast soften it without restyling anything. The numbers
 * were solved for, not guessed -- they map the two colours that matter onto a
 * terminal-like palette:
 *
 *     panel #ffffff  ->  #242424   (a soft dark grey, not black)
 *     text  #000000  ->  #b8b8b8   (dimmed, not glaring white)
 *
 * hue-rotate keeps the blues blue after inversion; without it the dream-blue
 * sleep panel comes back orange.
 */
/* Night mode filters each direct child of <body> rather than <html> itself.
 *
 * The reason is the overscroll strip -- the band a phone shows when it
 * rubber-bands past the top or bottom. A filter paints only its own element's
 * box, while the browser fills that strip from the html/body background
 * OUTSIDE the filtered output. With the filter on <html> the strip kept the
 * untransformed colour and stayed white in night mode, and no value could fix
 * it: the page needs the pre-inversion colour and the strip needs the
 * post-inversion one, from the same property.
 *
 * Filtering one level down frees <html> to carry the real dark colour for the
 * strip. `body > *` rather than a wrapper div because a filter on an ANCESTOR
 * makes it the containing block and breaks position:fixed -- and this app has
 * twelve fixed elements (the night toggle, modals, overlays, tutorial
 * bubbles). Filtering each child individually means no fixed element has a
 * filtered ancestor. They all attach directly to <body>, including the nine
 * created at runtime by app.js, so the selector covers them without naming
 * any of them -- a new one is picked up automatically.
 *
 * Verified on iOS Safari: no strip, fixed elements stay anchored to the
 * viewport, and they invert with the rest of the page.
 */
html[data-night] body > * {
    filter: invert(1) hue-rotate(180deg) brightness(0.81) contrast(0.72);
}

/* The strip's colour. Outside the filter, so this is the literal value shown:
 * #272727 is what the filter above resolves --bg (#fafafa) to, computed so the
 * strip matches the page exactly rather than being eyeballed. */
html[data-night] {
    background-color: #272727;
    /* Without a height, <html> collapses to its content box and the area above
     * the content is painted by something else -- which left the TOP strip
     * white while the bottom was already correct. */
    min-height: 100%;
}

/* body must NOT paint a background in night mode. When it does, the canvas --
 * and so the strip -- takes ITS colour instead of html's, which put the white
 * strip straight back. Scoped to night mode, so day mode keeps var(--bg)
 * exactly as before. */
html[data-night] body {
    background: none;
}

/* Stop Safari filling the status-bar strip with white after a fast scroll.
 *
 * On iOS Safari, a quick fling collapses the toolbar and Safari then paints
 * the region at the top of the phone -- the one holding the clock, wifi and
 * battery -- with an opaque light colour instead of letting the page show
 * through. It happens in BOTH modes; in day mode the page is already light so
 * nobody notices, but against night mode it is a glaring white band, and it
 * persists until you scroll back up decisively.
 *
 * The presence of a fixed element in that region is what stops it. NOT its
 * colour: a red band removed the white block without making the status bar
 * red, and this transparent one works identically. Anything under about 12px
 * is ignored -- 1px and 4px were both measured as having no effect.
 *
 * Deliberately NOT scoped to night mode: the behaviour is the same in day
 * mode, and a transparent element costs nothing there.
 *
 * pointer-events:none so it can never swallow a tap meant for the header
 * beneath it. */
html::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 12px;
    background: transparent;
    z-index: 2147483647;
    pointer-events: none;
}

/* The overlay pages need the same treatment body got, one level down.
 *
 * .menu-page and .tips-view are `position: fixed; inset: 0; overflow-y: auto`
 * -- viewport height (695px measured) holding taller content (1562px), so
 * they scroll INSIDE themselves and the rubber-band reveals their own
 * background past the box. That background is an opaque var(--panel-bg), so
 * it showed white at the top and bottom of Settings, About and Contact.
 *
 * `background: none` is what fixed body, but it cannot be used here: behind
 * body is the dark <html>, whereas behind an overlay is the TRACKER, and
 * transparency simply showed it through.
 *
 * So do what <html> does instead -- sit OUTSIDE the filter and carry the
 * literal post-inversion colour. The `filter: none` is what makes the colour
 * below literal rather than inverted; its children are filtered individually,
 * exactly as body > * does for the page, so the content still inverts while
 * the box itself stays a real dark grey that extends through the overscroll.
 */
html[data-night] .menu-page,
html[data-night] .tips-view {
    filter: none;
    background-color: #272727;
    /* NO `color` here. The children are filtered below, so a light colour set
     * on this container would be inverted by them into near-black -- which is
     * what made the About, Contact and Settings text unreadable. Their own
     * dark-on-light colours invert correctly on their own. */
}

html[data-night] .menu-page > *,
html[data-night] .tips-view > * {
    filter: invert(1) hue-rotate(180deg) brightness(0.81) contrast(0.72);
}

/* The demo banner is sticky at top:0 and is body's FIRST child, so in night
 * mode an upward overscroll reveals IT rather than the canvas -- which kept
 * the top strip white while the bottom was already correct.
 *
 * Transparent rather than a dark colour: the banner is filtered along with
 * every other body child, so any value set here gets inverted. Making it
 * transparent lets the unfiltered dark <html> show through instead, which
 * matches by construction rather than by a value that has to be recomputed if
 * the filter is ever retuned.
 *
 * Sandbox only -- jazz never creates this element (setupDemoUI is gated on
 * IS_DEMO), so its top strip was never affected. */
html[data-night] .demo-banner,
html[data-night] .sandbox-banner {
    background: transparent;
}



:root {
    --bg: #fafafa;
    --panel-bg: #ffffff;
    --border: #000000;
    --border-light: #cccccc;
    --text: #000000;
    --text-light: #444444;
    --accent: #000000;
    --accent-hover: #333333;
    --danger: #000000;
    --success: #000000;
    --warning: #000000;
}

body {
    font-family: 'Times New Roman', Times, Georgia, serif;
    background: var(--bg);
    color: var(--text);
    height: 100vh;
    overflow: hidden;
    line-height: 1.6;
    letter-spacing: 0.02em;
}

.container {
    display: flex;
    height: 100vh;
}

/* Left Panel - Spreadsheet */
.left-panel {
    width: 420px;
    background: var(--panel-bg);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
}

/* Title + all four nav boxes on ONE line, at every width. The title never
   wraps or shrinks; the nav strip absorbs the remaining space and scales its
   own padding/font down (see .panel-header-buttons) rather than wrapping. */
.panel-header {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 14px 16px;
    border-bottom: 2px solid var(--border);
    background: var(--panel-bg);
}

.panel-header h2 {
    font-size: 18px;
    font-weight: normal;
    font-style: italic;
    letter-spacing: 0.05em;
    /* Never wrap mid-name, and never let the flex parent squash it. */
    white-space: nowrap;
    flex: 0 0 auto;
}

/* ============================================================
   App lock — password prompt over a blurred backdrop
   ============================================================ */
/* While locked, the app behind is blurred AND desaturated to near-nothing.
   The blur alone is only cosmetic — text stays legible when a screenshot is
   scaled up — so opacity does the actual masking and blur supplies the look. */
body.locked #tracker-view,
body.locked .growth-view {
    filter: blur(14px) saturate(0.4);
    opacity: 0.25;
    pointer-events: none;
    user-select: none;
}

body.locked {
    overflow: hidden;
}

.app-lock {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(250, 250, 250, 0.72);
    /* Frosts whatever shows through the panel itself. */
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.app-lock.hidden {
    display: none !important;
}

.app-lock-box {
    width: 100%;
    max-width: 340px;
    background: white;
    border: 1px solid var(--border);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.14);
    padding: 30px 26px 26px;
    text-align: center;
}

.app-lock-title {
    font-size: 21px;
    font-weight: normal;
    font-style: italic;
    letter-spacing: 0.05em;
    margin-bottom: 10px;
}

.app-lock-sub {
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-light);
    margin-bottom: 20px;
}

.app-lock input[type="password"] {
    width: 100%;
    box-sizing: border-box;
    padding: 11px 12px;
    border: 1px solid var(--border);
    border-radius: 0;
    /* 16px stops iOS Safari zooming the page when the field is focused. */
    font-size: 16px;
    font-family: inherit;
    text-align: center;
    letter-spacing: 0.08em;
}

.app-lock input[type="password"]:focus {
    outline: none;
    border-color: var(--text);
}

.app-lock-btn {
    width: 100%;
    margin-top: 12px;
    padding: 11px 14px;
    background: var(--text);
    color: white;
    border: 1px solid var(--text);
    border-radius: 0;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
}

.app-lock-btn:hover {
    background: white;
    color: var(--text);
}

.app-lock-error {
    margin-top: 12px;
    font-size: 11px;
    color: #c0392b;
}

.app-lock-error.hidden {
    display: none;
}

@media (max-width: 500px) {
    .app-lock-box {
        padding: 24px 20px 22px;
    }
    .app-lock-title { font-size: 18px; }
}

/* Generic hide utility (modals have their own .modal.hidden rule).
   Uses !important so it always wins over element rules like
   .stat-row/.chart-section { display: flex } that appear later in this file
   and would otherwise override it (equal specificity, later rule wins). */
.hidden {
    display: none !important;
}

/* Screen-nav boxes in the panel header (Feeding / Sleep / Poop / Animation).
   Four peers; the one you're on is filled in. */
.view-toggle {
    background: white;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 0.05em;
    padding: 5px 10px;
    cursor: pointer;
}

.view-toggle:hover {
    background: var(--text);
    color: white;
}

/* Current screen. Filled black so which view you're on is unambiguous.
   Not a hover target — it's where you already are. */
.view-toggle.active {
    background: var(--text);
    color: white;
    border-color: var(--text);
    cursor: default;
}

.spreadsheet-container {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
}

#feed-table {
    /* The log sizes to a comfortable reading width and stays left-aligned,
       rather than stretching to fill the panel. This is what keeps it stable at
       every screen size: on desktop the panel is 420px so the table just fills
       it; on mobile the panel goes full-width, and this cap stops the table (and
       its flexible Notes column) from ballooning across a tablet-width viewport
       and stranding the ⋮ far to the right. 100% lets it shrink on a real phone. */
    width: 100%;
    max-width: 440px;
    border-collapse: collapse;
    font-size: 13px;
}

#feed-table thead,
#poop-table thead {
    position: sticky;
    top: 0;
    background: var(--bg);
    z-index: 10;
}

/* Headers match the sleep log, which never had this rule and so kept the
   browser default: bold, normal case. That reads better at a glance than
   10px letter-spaced capitals, and having two of the three logs styled one
   way and the third another was simply an oversight. Sleep is now the
   reference; these follow it. */
#feed-table th,
#poop-table th,
#feed-entry-table th,
#poop-entry-table th,
#sleep-table th,
#sleep-entry-table th {
    padding: 6px 4px;
    text-align: left;
    font-weight: 600;
    font-size: 12px;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text);
    border-bottom: 1px solid var(--border);
    /* Let header labels wrap instead of spilling into the next column
       (fixed table layout clips/overflows otherwise — was why SPITTLE and
       NOTES ran together). */
    white-space: normal;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
}

/* --- Entry form card --------------------------------------------------------
   The new-entry form is its own table inside a highlighted card, separate from
   the history log below. Splitting them lets each table size its own columns:
   the form needs a wide Date field, the log doesn't need a Date column at all
   (each day has a .date-separator row), so the log can spend that space on
   Spittle and Notes instead of leaving ~112px empty down the left edge. */
.entry-card {
    margin: 10px 8px 16px;
    padding: 10px 10px 12px;
    background: var(--panel-bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    /* Neutral gray halo (not blue) to match the app's black-and-white feel: a
       tight light-gray ring fading out into a soft darker-gray glow. */
    box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.06),
                0 0 16px 3px rgba(0, 0, 0, 0.22);
}

.entry-card-title {
    font-size: 12px;
    font-weight: normal;
    font-style: italic;
    letter-spacing: 0.05em;
    color: var(--text);
    margin-bottom: 6px;
}

#feed-entry-table,
#poop-entry-table {
    width: 100%;
    max-width: 440px;   /* match the log cap so the form and log align */
    border-collapse: collapse;
    table-layout: fixed;
    font-size: 12px;
}

/* Entry form keeps a full-width Date field. mL is wide enough to show its "mL"
   placeholder (38px clipped it to "m"); Notes (auto) gives up the difference. */
#feed-entry-table th:nth-child(1) { width: 108px; }
#feed-entry-table th:nth-child(2) { width: 96px; }
#feed-entry-table th:nth-child(3) { width: 56px; }
#feed-entry-table th:nth-child(4) { width: 60px; }
#feed-entry-table th:nth-child(5) { width: auto; }

#poop-entry-table th:nth-child(1) { width: 108px; }
#poop-entry-table th:nth-child(2) { width: 96px; }
#poop-entry-table th:nth-child(3) { width: auto; }

/* The card supplies the separation, so the last entry row needs no rule. */
.entry-card tr:last-child td {
    border-bottom: none;
}

/* --- History log ------------------------------------------------------------
   No Date column. The reclaimed width goes to Spittle and Notes, which were
   previously so narrow that notes were almost always ellipsized.

   --log-indent keeps the times, dates and headers off the panel's left border
   now that no empty Date column is providing that breathing room. It's applied
   as padding on the first cell of every row type (header, date separator, data)
   so the whole left edge stays aligned.

   Columns spread across the panel in roughly-even halves instead of packing
   against the left: Time + mL fill the left half, Spittle + Notes the right.
   The right half tilts a little toward Notes (rather than a mathematically even
   split) so notes stay readable — an exact 50/50 would squeeze Notes back down
   to ~86px, undoing the readability win that motivated splitting the tables. */
#feed-table,
#poop-table,
#sleep-table {
    --log-indent: 60px;
    /* Date separators sit at HALF the row indent so each date reads as a group
       heading slightly outset from its times, rather than lining up flush with
       them (which made the date look like just another data cell). */
    --date-indent: 30px;
}

/* Time / mL / Spittle are tight EQUAL columns so their three headers cluster
   together as an evenly-spaced group, pushed toward center by the row indent.
   Notes then takes ALL remaining width and runs to the ⋮ — so the Notes *label*
   sits right after that compact group (no big gap before it), while the Notes
   *text* still gets the full width out to the edit column. This is the "even
   group + notes overflows the right padding" layout: headers look evenly spaced,
   yet notes stay wide and readable. */
#feed-table th:nth-child(1) { width: calc(76px + var(--log-indent)); } /* Time */
#feed-table th:nth-child(2) { width: 60px; }    /* mL */
#feed-table th:nth-child(3) { width: 76px; }    /* Spittle */
#feed-table th:nth-child(4) { width: auto; }    /* Notes — runs to the ⋮ */
#feed-table th:nth-child(5) { width: 28px; }    /* edit ⋮ */

/* Row/header indent. Date separators are handled separately (--date-indent)
   so they can sit at half this distance as group headings. */
#feed-table th:first-child,
#poop-table th:first-child,
#sleep-table th:first-child,
#feed-table .history-row td:first-child,
#poop-table .history-row td:first-child,
#sleep-table .history-row td:first-child {
    padding-left: var(--log-indent);
}

#feed-table {
    table-layout: fixed;
    font-size: 12px;
}

#feed-table td,
#poop-table td,
#sleep-table td,
#feed-entry-table td,
#poop-entry-table td,
#sleep-entry-table td {
    padding: 4px 4px;
    border-bottom: 1px solid var(--border-light);
    vertical-align: top;
}

#feed-table tr,
#poop-table tr,
#sleep-table tr,
#feed-entry-table tr,
#poop-entry-table tr,
#sleep-entry-table tr {
    position: relative;
}

#feed-table tbody tr:hover,
#poop-table tbody tr:hover,
#sleep-table tbody tr:hover {
    background: #f0f0f0;
}

#feed-table input,
#feed-table select,
#poop-table input,
#poop-table select,
#sleep-table input,
#sleep-table select,
#feed-entry-table input,
#feed-entry-table select,
#poop-entry-table input,
#poop-entry-table select {
    width: 100%;
    padding: 6px 4px;
    border: 1px solid transparent;
    background: transparent;
    font-size: 13px;
    font-family: inherit;
}

#feed-table input:focus,
#feed-table select:focus,
#poop-table input:focus,
#poop-table select:focus,
#sleep-table input:focus,
#sleep-table select:focus,
#feed-entry-table input:focus,
#feed-entry-table select:focus,
#poop-entry-table input:focus,
#poop-entry-table select:focus {
    outline: none;
    border-color: var(--accent);
    background: white;
}

#feed-table input[type="date"],
#poop-table input[type="date"],
#sleep-table input[type="date"],
#feed-entry-table input[type="date"],
#poop-entry-table input[type="date"] {
    width: 100%;
}

#feed-table input[type="number"],
#poop-table input[type="number"],
#feed-entry-table input[type="number"],
#poop-entry-table input[type="number"] {
    width: 100%;
    text-align: right;
}

#feed-table select,
#poop-table select,
#sleep-table select,
#feed-entry-table select,
#poop-entry-table select {
    cursor: pointer;
}

.row-actions {
    text-align: center;
    /* Anchors the "edit entry" hint, which is positioned against this cell. */
    position: relative;
}

/* "edit entry →" pointing at the three-dot control, shown only while the row
   is selected.
 *
 * The three dots are a 11px glyph in a 4px-padded button, and a parent trying
 * the app for the first time could not find them -- so the row highlighted and
 * nothing appeared to happen. The hint says what the dots are for, at the
 * moment the user has shown interest in that row by tapping it.
 *
 * Rendered entirely in CSS off the .expanded class the row toggle already
 * sets, so there is no extra markup per row and nothing to keep in sync when
 * rows re-render. */
.history-row:hover .row-actions::before,
.history-row.expanded .row-actions::before {
    content: "edit entry \2192";   /* the arrow is part of the run, so it
                                       cannot overlap the text -- two
                                       absolutely-positioned pseudo-elements
                                       anchored to the same edge would */
    position: absolute;
    right: 100%;
    top: 50%;
    transform: translateY(-50%);
    margin-right: 2px;
    /* Opaque, so the hint BLOCKS the Notes text behind it rather than
       overlapping it. The row is white normally and #f0f0f0 on hover, so the
       background is set per-state below rather than once here. */
    padding: 2px 4px;
    border-radius: 2px;
    white-space: nowrap;
    font-size: 11px;
    line-height: 1;
    color: #666;
    pointer-events: none;
}

/* Narrow screens: the hint would collide with the Notes column, and on a
   phone the dots are easier to hit anyway. Show the arrow only. */
/* The hint's background must match the row it sits on, or it reads as a
   floating label. Two states, because the row changes colour on hover. */
.history-row.expanded .row-actions::before {
    background: #fff;
}
.history-row:hover .row-actions::before {
    background: #f0f0f0;
}
/* Hovering an already-selected row: hover wins, so this must come last. */
.history-row.expanded:hover .row-actions::before {
    background: #f0f0f0;
}

/* Touch devices have no real hover -- :hover sticks after a tap and would
   leave the hint on a row the finger has left. There, .expanded alone drives
   it, which the tap already sets. */
@media (hover: none) {
    .history-row:hover .row-actions::before { content: none; }
    .history-row.expanded .row-actions::before { content: "edit entry \2192"; }
}

@media (max-width: 560px) {
    .history-row:hover .row-actions::before,
    .history-row.expanded .row-actions::before { content: none; }
}

/* Custom 12-hour time picker (hour / minute / AM-PM dropdowns) */
.time-picker {
    display: flex;
    align-items: center;
    gap: 2px;
}

.time-picker .time-colon {
    font-size: 13px;
}

/* Picker inside a new-entry table row: compact white-boxed selects. */
#feed-table .time-picker,
#poop-table .time-picker,
#sleep-table .time-picker,
#feed-entry-table .time-picker,
#poop-entry-table .time-picker {
    gap: 2px;
}

#feed-table .time-picker .time-colon,
#poop-table .time-picker .time-colon,
#sleep-table .time-picker .time-colon,
#feed-entry-table .time-picker .time-colon,
#poop-entry-table .time-picker .time-colon {
    margin: 0;
}

#feed-table .time-picker .time-select,
#poop-table .time-picker .time-select,
#sleep-table .time-picker .time-select,
#feed-entry-table .time-picker .time-select,
#poop-entry-table .time-picker .time-select {
    width: auto;
    min-width: 0;
    padding: 4px 3px;
    border: 1px solid var(--border);
    background: white;
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
    /* Hide the native dropdown arrow so it can't overlap the digits in these
       narrow selects; the colons keep the hour:minute:ampm grouping clear. */
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    text-align: center;
    text-align-last: center;
}

#feed-table .time-picker .time-select:focus,
#poop-table .time-picker .time-select:focus,
#sleep-table .time-picker .time-select:focus,
#feed-entry-table .time-picker .time-select:focus,
#poop-entry-table .time-picker .time-select:focus {
    outline: none;
    border-color: var(--text);
}

/* Picker inside the edit modal: match the other modal fields */
.modal-content .time-picker {
    gap: 6px;
}

.modal-content .time-picker .time-select {
    width: auto;
    flex: 1 1 0;
    min-width: 0;
    padding: 12px 6px;
    border: 1px solid var(--border);
    border-radius: 0;
    font-size: 14px;
    font-family: inherit;
    cursor: pointer;
}

/* Ensure notes and spittle columns handle overflow.
   Spittle/Notes are cells 3/4 now that history rows carry no Date cell. */
#feed-table .history-row td:nth-child(3),
#feed-table .history-row td:nth-child(4) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Expanded row shows full content */
#feed-table .history-row.expanded td:nth-child(3),
#feed-table .history-row.expanded td:nth-child(4) {
    white-space: normal;
    word-wrap: break-word;
    overflow: visible;
    text-overflow: clip;
}

/* Same truncate-then-expand behaviour for the sleep log's Notes column (cell 4
   there: Asleep / Awake / Length / Notes). Without this a long note stretched
   its row and pushed the layout around instead of ellipsising like feeding. */
#sleep-table .history-row td:nth-child(4) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#sleep-table .history-row.expanded td:nth-child(4) {
    white-space: normal;
    word-wrap: break-word;
    overflow: visible;
    text-overflow: clip;
}

/* Poop log's Notes column (cell 2: Time / Notes) gets the same treatment. */
#poop-table .history-row td:nth-child(2) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#poop-table .history-row.expanded td:nth-child(2) {
    white-space: normal;
    word-wrap: break-word;
    overflow: visible;
    text-overflow: clip;
}

.feed-row.history-row {
    cursor: pointer;
}

.feed-row.history-row:active {
    background: #e8e8e8;
}

.row-actions button {
    padding: 4px 6px;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    font-size: 11px;
    background: transparent;
    color: var(--text-light);
}

.row-actions button:hover {
    background: var(--bg);
}

.row-actions .btn-save {
    color: var(--success);
}

.row-actions .btn-save:hover {
    background: #e8f5e9;
}

.row-actions .btn-delete {
    color: var(--danger);
}

.row-actions .btn-delete:hover {
    background: #ffebee;
}

.feed-row {
    transition: background 0.15s;
}

.feed-row:hover {
    background: #fafafa;
}


/* New entry row - distinct styling */
.feed-row.new-row {
    background: #f5f5f5;
    border-bottom: 1px solid var(--border);
}

.feed-row.new-row td {
    padding: 5px 4px;
    vertical-align: middle;
}

.feed-row.new-row input,
.feed-row.new-row select {
    background: white;
    border: 1px solid var(--border);
    border-radius: 0;
    font-family: inherit;
    font-size: 12px;
    padding: 3px 4px;
}

/* Give every control in the fields row the SAME box height and middle
   alignment so date / time / mL / spittle / notes line up cleanly, and keep
   the row compact. box-sizing makes the heights match despite differing
   borders/padding. */
.new-row-fields td > input,
.new-row-fields td > select,
.new-row-fields .time-picker .time-select {
    box-sizing: border-box;
    height: 28px;
    vertical-align: middle;
    margin: 0;
}

.new-row-fields .time-picker {
    height: 28px;
}

.feed-row.new-row input:focus,
.feed-row.new-row select:focus {
    border-color: var(--border);
    outline: 1px solid var(--border);
    outline-offset: 1px;
}

/* The Add button now lives on its own full-width row beneath the inputs, so it
   spans the table and the five input cells keep their column alignment. */
.new-row-action td {
    padding: 4px 4px 8px;
}

.feed-row.new-row .btn-add {
    background: var(--text);
    color: white;
    padding: 6px 14px;
    border-radius: 0;
    font-weight: normal;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 0.05em;
    border: 1px solid var(--border);
    cursor: pointer;
}

/* On its own dedicated row (feeding entry), the Add button spans full width. */
.new-row-action .btn-add {
    width: 100%;
}

/* Don't double-draw the divider between the fields row and its action row. */
.new-row-fields td {
    border-bottom: none;
}

/* Poop table mirrors the feeding table: same width cap so it stays left-aligned
   and readable instead of stretching across a full-width mobile panel. */
#poop-table {
    width: 100%;
    max-width: 440px;
    border-collapse: collapse;
    table-layout: fixed;
    font-size: 12px;
}

/* No Date column here. Time must fit the 60px indent PLUS the timestamp on one
   line — at 105px only ~45px was left for the text, so "12:45 PM" wrapped. Sized
   to indent + a full timestamp; Notes fills the rest. */
#poop-table th:nth-child(1) { width: calc(var(--log-indent) + 70px); }
#poop-table th:nth-child(2) { width: auto; }
#poop-table th:nth-child(3) { width: 28px; }

/* Sleep table mirrors the other two. Four data columns (Asleep / Awake /
   Length / Notes): the two timestamps and the duration are all fixed short
   strings, so they get just enough room to stay on one line and Notes takes the
   remainder — same "compact group + notes absorbs the rest" layout as feeding. */
#sleep-table {
    width: 100%;
    max-width: 440px;
    border-collapse: collapse;
    table-layout: fixed;
    font-size: 12px;
}

#sleep-table th:nth-child(1) { width: calc(var(--log-indent) + 66px); } /* Asleep */
#sleep-table th:nth-child(2) { width: 66px; }   /* Awake */
#sleep-table th:nth-child(3) { width: 56px; }   /* Length */
#sleep-table th:nth-child(4) { width: auto; }   /* Notes */
#sleep-table th:nth-child(5) { width: 28px; }   /* edit ⋮ */

/* Baby blue.
   OPAQUE, not an rgba tint. The highlight sits on the entry card's #f5f5f5
   grey, and a translucent blue composited over grey just reads as grey — which
   is exactly how the earlier alpha versions looked. A solid pastel gives the
   same soft feel while actually showing as blue, and keeps black text legible
   (contrast ~11:1 on the light tone). */
:root {
    --dream-blue: #cfe8fb;          /* row / banner fill */
    --dream-blue-strong: #a9d6f7;   /* hover + emphasis  */
    --dream-blue-ink: #2b7fc4;      /* dots, links, accents */
}

/* An in-progress sleep is the one row that changes on its own, so mark it. */
#sleep-table .sleep-row-active {
    background: var(--dream-blue);
}

/* Hovering the ongoing row must not replace the blue with the generic grey
   hover (#f0f0f0) — deepen the blue instead. */
#sleep-table tbody tr.sleep-row-active:hover {
    background: var(--dream-blue-strong);
}

/* --- Ignored ("not logged") days ---------------------------------------- */
/* The flex row is an inner div, so the <td> stays a real table cell and keeps
   honouring its colspan (a display:flex td shrink-wraps and collapses the row). */
.date-separator-inner {
    display: flex;
    align-items: center;
    gap: 8px;
}

.date-separator-label {
    flex: 0 0 auto;
}

/* "— 13h 40m asleep" after the date in the sleep log. Pushed to the right so
   the day totals line up in a column and can be scanned down the log; the
   Ignore button follows it. */
.date-separator-suffix {
    flex: 0 0 auto;
    margin-left: auto;
    text-align: right;
    font-size: 10px;
    font-style: normal;
    color: var(--text-light);
    white-space: nowrap;
}

/* Arrows marking a sleep that continues across midnight, shown on each of the
   day-pieces it was split into. */
.sleep-continues {
    color: var(--text-light);
    font-size: 10px;
    opacity: 0.75;
}

/* Pushed to the right edge, quiet until hovered — it's a rare action. */
.btn-ignore-day {
    margin-left: auto;
    background: none;
    border: 1px solid var(--border);
    border-radius: 0;
    font-family: inherit;
    font-size: 9px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-light);
    padding: 2px 6px;
    cursor: pointer;
    opacity: 0.45;
    transition: opacity 0.12s;
}

.date-separator:hover .btn-ignore-day {
    opacity: 1;
}

.btn-ignore-day:hover {
    background: var(--text);
    border-color: var(--text);
    color: white;
    opacity: 1;
}

/* An ignored day's rows are dimmed so it's obvious the data is still there but
   isn't counted. */
.date-separator.day-ignored {
    background: #f7f7f7;
}

.date-separator.day-ignored .date-separator-label {
    text-decoration: line-through;
    color: var(--text-light);
}

/* Rows belonging to an ignored day: still readable, visibly not counted. */
.feed-row.history-row.row-ignored {
    opacity: 0.4;
}

/* A day (or run of days) with no entries at all. Muted, italic and hatched so
   it reads as an absence rather than a data row — these are the rows the
   Ignore button mostly exists for. */
.date-separator.empty-days {
    background: repeating-linear-gradient(
        45deg, #fbfbfb, #fbfbfb 6px, #f4f4f4 6px, #f4f4f4 12px);
}

.date-separator.empty-days .date-separator-label {
    font-style: italic;
    color: var(--text-light);
    font-weight: normal;
}

.empty-days-note {
    font-size: 9px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-light);
    opacity: 0.8;
}

/* Explanation of the Ignore control, below the log.
   flex:0 0 auto so it never absorbs space the scrolling table needs, and never
   gets squashed either — it sits at its natural height under the log. */
.ignore-help {
    flex: 0 0 auto;
    margin: 10px 12px 12px;
    padding: 8px 10px;
    border-left: 2px solid var(--border);
    background: #fbfbfb;
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-light);
}

.ignore-help strong {
    color: var(--text);
    font-weight: 600;
}

.ignore-help em {
    font-style: normal;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 10px;
    border: 1px solid var(--border);
    padding: 0 4px;
    color: var(--text);
}

.ignored-badge {
    font-size: 9px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-light);
    border: 1px solid var(--border-light);
    padding: 1px 5px;
}

/* Touch targets need to stay comfortable on a phone. */
@media (max-width: 500px) {
    .btn-ignore-day {
        font-size: 9px;
        padding: 3px 7px;
        opacity: 1; /* no hover on touch, so always visible */
    }
}

.sleep-ongoing {
    color: var(--text-light);
    font-size: 11px;
    font-style: italic;
}

/* Live-logging controls. "Sleeping Now" is the primary action on this view, so
   it reads as a filled button while "Add Past Sleep" keeps the default outline
   treatment shared with the other entry forms. */
.btn-sleep-now {
    background: var(--text);
    color: white;
    font-weight: 600;
}

.btn-sleep-now:hover {
    background: white;
    color: var(--text);
}

/* Wake Up stays black like the other actions — sitting inside the baby-blue
   panel already makes it read as part of that group. */
.btn-wake {
    background: var(--text);
    color: white;
    font-weight: 600;
}

.btn-wake:hover {
    background: white;
    color: var(--text);
}

/* The "asleep since ..." banner sits in the same cell as the Wake Up button.
   The blue fill and its padding go on the LABEL, not the cell — padding the
   cell inset the button too, making it narrower than "Add Past Sleep" below. */
.sleep-active-row td {
    padding: 4px 0;
}

/* The "awake since ..." banner shown when no sleep is running. Left unfilled
   so the tinted background always means "asleep" at a glance. */
.sleep-awake-row td {
    background: transparent;
}

.sleep-active-label {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-size: 12px;
    margin-bottom: 8px;
    color: var(--text);
}

/* The blue panel wrapping the "asleep since ..." line and its Wake Up button.
   NO horizontal padding: any left/right padding here (or on the <td>) insets
   the button, leaving it narrower than "Add Past Sleep" below. The vertical
   breathing room comes from the label's own padding instead. */
/* The panel's blue outline is drawn with a ring (box-shadow) rather than a
   real border, so it adds no box width — the Wake Up button keeps the full
   panel width and stays exactly as wide as "Add Past Sleep" below it.
   The bottom padding leaves a band of blue under the button so it reads as
   sitting INSIDE the panel rather than capping it. */
.sleep-active-panel {
    background: var(--dream-blue);
    padding: 0 0 8px;
    border-radius: 3px;
    box-shadow: 0 0 0 1px var(--dream-blue-ink);
}

/* Label carries the horizontal inset, so the button keeps full panel width. */
.sleep-active-panel .sleep-active-label {
    margin-bottom: 0;
    padding: 9px 10px 8px;
}

/* The start time inside the active-sleep banner is a button (tap to correct a
   late "Sleeping Now"). Styled as underlined text, not a chrome button, so the
   banner still reads as a sentence. */
.sleep-start-edit {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: inherit;
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 3px;
    cursor: pointer;
}

.sleep-start-edit:hover {
    color: var(--dream-blue-ink);
}

/* Pulsing dot so an active sleep is obvious at a glance across the room. */
.sleep-active-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--dream-blue-ink);
    animation: sleep-pulse 2s ease-in-out infinite;
}

/* Awake: same dot, hollow and still, so the two states are distinguishable
   without relying on colour alone. */
.sleep-awake-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: 1.5px solid var(--text-light);
    background: transparent;
}

@keyframes sleep-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.25; }
}

/* Respect a user's reduced-motion preference — the dot still shows, it just
   stops blinking. */
@media (prefers-reduced-motion: reduce) {
    .sleep-active-dot { animation: none; }
}

/* The sleep entry card is just two stacked actions, so its table shouldn't
   inherit the 440px log-table cap — the buttons span the full card width.
   (Back-filling a past sleep happens in a modal; see openSleepAddModal.) */
#sleep-entry-table {
    width: 100%;
    max-width: none;
}

#sleep-entry-table td {
    padding: 4px 0;
    border-bottom: none;
}

/* Drop the generic .feed-row.new-row grey (#f5f5f5) and its divider on this
   card. Those exist to mark a data-entry ROW in the feeding/poop forms, but
   here the rows are just stacked buttons, so the grey showed as a rectangle
   framing the blue panel and the black button. */
#sleep-entry-table .feed-row.new-row {
    background: transparent;
    border-bottom: none;
}

/* Greyed-out end-time picker while "Still sleeping" is checked. */
.disabled-field {
    opacity: 0.45;
    pointer-events: none;
}

.checkbox-label {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
    width: auto;
    margin: 0;
}

.feed-row.new-row .btn-add:hover {
    background: white;
    color: var(--text);
}

/* Historical rows - read-only display */
.feed-row.history-row td {
    padding: 4px 4px;
    font-size: 12px;
}

.feed-row.history-row:hover {
    background: #f0f0f0;
}

.history-time {
    font-weight: normal;
    /* The timestamp ("12:45 PM") is a fixed short string — never let it wrap to
       two lines when a column is a little tight (happened on narrow phones where
       the Time column's percentage width barely fit the text). nowrap keeps it on
       one line at any width; the column widths below give it room to sit cleanly. */
    white-space: nowrap;
}

.history-amount {
    font-weight: normal;
    color: var(--text);
}

.history-spittle {
    color: var(--text);
    font-size: 11px;
    font-style: italic;
}

.history-notes {
    color: var(--text-light);
    font-size: 11px;
    font-style: italic;
}

/* Three-dot menu button */
.btn-menu {
    background: none;
    border: none;
    font-size: 18px;
    color: var(--text-light);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 4px;
    line-height: 1;
}

.btn-menu:hover {
    background: var(--bg);
    color: var(--text);
}

.date-separator {
    background: var(--bg);
    font-weight: normal;
    font-size: 11px;
    font-style: italic;
    color: var(--text);
}

.date-separator td {
    /* Indent at --date-indent (half the row indent) so the date sits slightly
       outset from its times as a group heading. The old shorthand
       `padding: 6px 4px !important` overrode any indent and made dates hug the
       left border. */
    padding: 6px 4px 6px var(--date-indent) !important;
    border-bottom: 1px solid var(--border-light);
    letter-spacing: 0.03em;
}

/* Main Panel */
.main-panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    padding: 16px;
    overflow: hidden;
}

.stats-section {
    margin-bottom: 12px;
    position: relative;   /* anchor for the top-right lookback dropdown (desktop) */
}

/* Desktop: lay all the feeding stat cards out on ONE line — "Today So Far"
   (its own row wrapper) first, then the windowed stats. `display: contents`
   dissolves each row wrapper so its cards become direct children of this flex
   container. `.hidden` still wins (it's display:none !important), so the poop
   toggle keeps working. The dropdown is absolutely positioned top-right, out
   of this flow. */
@media (min-width: 901px) {
    .stats-feeding-wrap,
    .stats-sleep-wrap { display: flex; gap: 8px; align-items: stretch; }
    .today-only, #feeding-stat-row, #sleep-stat-row { display: contents; }
    /* Reserve a strip at the top of the stats area for the floated dropdown. */
    .stats-section { padding-top: 26px; }
    /* Sit the dropdown roughly above the second card ("Daily Average"), not at
       the far right. With 5 equal cards, the 2nd card spans ~20%-40% from the
       left, so anchor the dropdown's left edge around there. */
    .stats-header { left: 20%; right: auto; }
}

.stats-header {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-style: italic;
    color: var(--text-light);
    /* Desktop: float to the top-right of the stats area, out of the row flow so
       the stat cards (starting with "Today So Far") occupy the full width. */
    position: absolute;
    top: 0;
    right: 0;
    z-index: 2;
}

.stats-header select {
    padding: 3px 6px;
    border: 1px solid var(--border-light);
    border-radius: 0;
    font-size: 11px;
    font-weight: normal;
    font-family: inherit;
}

.stat-row {
    display: flex;
    gap: 8px;
}

.stat-card {
    flex: 1;
    background: transparent;
    border: none;
    padding: 8px 12px;
    text-align: center;
}

.stat-label {
    font-size: 11px;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.stat-value {
    font-size: 16px;
    font-weight: normal;
    color: var(--text);
    margin: 4px 0;
}

.stat-controls {
    display: flex;
    gap: 4px;
    justify-content: center;
}

.stat-controls select {
    padding: 2px 4px;
    border: 1px solid var(--border-light);
    border-radius: 0;
    font-size: 10px;
    font-family: inherit;
}

.stat-card-wide {
    flex: 1.5;
}

.day-night-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    margin: 4px 0;
    font-size: 10px;
    color: var(--text-light);
}

.day-night-controls .time-picker {
    gap: 1px;
}

.day-night-controls .time-select {
    padding: 2px 4px;
    border: 1px solid var(--border-light);
    border-radius: 0;
    font-size: 10px;
    font-family: inherit;
    cursor: pointer;
    background: white;
    color: var(--text);
    /* Same fix as the new-entry picker: drop the native arrow so it can't
       obscure the digits in these tiny selects. */
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    text-align: center;
    text-align-last: center;
}

.day-night-controls .time-colon {
    font-size: 10px;
}

.day-night-stats {
    display: flex;
    justify-content: center;
    gap: 16px;
    font-size: 11px;
}

.day-night-column {
    text-align: center;
}

.day-night-column .label {
    font-weight: normal;
    color: var(--text);
    margin-bottom: 2px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.day-night-column .total {
    font-size: 15px;
    font-weight: normal;
    color: var(--text);
}

.day-night-column .details {
    font-size: 11px;
    color: var(--text-light);
}

.chart-section {
    flex: 1;
    background: var(--panel-bg);
    border: 1px solid var(--border);
    border-radius: 0;
    padding: 20px;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.chart-tabs {
    display: flex;
    gap: 0;
    margin-bottom: 12px;
    justify-content: center;
}

.tab-btn {
    padding: 6px 16px;
    border: 1px solid var(--border);
    background: white;
    border-radius: 0;
    cursor: pointer;
    font-size: 13px;
    font-family: inherit;
    color: var(--text);
}

.tab-btn:not(:first-child) {
    border-left: none;
}

.tab-btn.active {
    background: var(--text);
    color: white;
}

.chart-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
    justify-content: center;
}

.nav-btn {
    padding: 6px 14px;
    border: 1px solid var(--border);
    background: white;
    border-radius: 0;
    cursor: pointer;
    font-size: 14px;
    font-family: inherit;
}

.nav-btn:hover {
    background: var(--text);
    color: white;
}

#chart-date-label {
    font-weight: 600;
    min-width: 100px;
    text-align: center;
    font-size: 14px;
}

#chart-container,
#poop-chart-container,
#sleep-chart-container {
    flex: 1;
    /* min-height:0 lets the container shrink to fit the space left after the
       tabs/controls rows. A non-zero min-height (it used to be 250px) forces
       the container taller than its box, pushing the SVG's x-axis and the
       bottom of the y-axis out below the chart border. */
    min-height: 0;
    overflow: hidden;
}

/* Modal */
.modal {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
}

.modal.hidden {
    display: none;
}

.modal-content {
    background: white;
    padding: 30px;
    border-radius: 0;
    border: 2px solid var(--border);
    min-width: 300px;
    max-width: 380px;
}

.modal-content h3 {
    margin-bottom: 20px;
    font-size: 18px;
    font-weight: normal;
    font-style: italic;
    letter-spacing: 0.05em;
}

.modal-content input,
.modal-content select {
    width: 100%;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 0;
    font-size: 14px;
    font-family: inherit;
}

.form-group {
    margin-bottom: 12px;
}

.form-group label {
    display: block;
    font-size: 12px;
    color: var(--text-light);
    margin-bottom: 4px;
    text-transform: uppercase;
}

.modal-buttons {
    display: flex;
    gap: 10px;
    margin-top: 20px;
}

.modal-buttons button {
    flex: 1;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 0;
    cursor: pointer;
    font-size: 14px;
    font-family: inherit;
    background: white;
    color: var(--text);
}

.modal-buttons button:hover {
    background: var(--text);
    color: white;
}

.modal-buttons button:first-child {
    background: white;
}

.modal-buttons button:last-child {
    background: var(--text);
    color: white;
}

.modal-buttons button:last-child:hover {
    background: white;
    color: var(--text);
}

.modal-buttons button.danger {
    background: white;
    color: var(--text);
    text-decoration: underline;
}

.modal-buttons button.danger:hover {
    background: var(--text);
    color: white;
}

.modal-buttons.three-buttons {
    gap: 8px;
}

.modal-buttons.three-buttons button {
    flex: none;
    padding: 10px 16px;
}

.modal-buttons.three-buttons button:first-child {
    margin-right: auto;
}

.error {
    color: var(--danger);
    font-size: 13px;
    margin-top: 8px;
}

.error.hidden {
    display: none;
}

/* Chart Styles */
.line {
    fill: none;
    stroke: var(--text);
    stroke-width: 1.5;
}

.dot {
    fill: var(--text);
}

.density-area {
    fill: var(--text);
    opacity: 0.1;
}

/* Sleep bars share the gap-bar treatment so the sleep charts read as the same
   family as the feeding ones. */
.sleep-bar,
.sleep-span-bar {
    fill: var(--text);
    opacity: 0.75;
    cursor: pointer;
}

.sleep-bar:hover,
.sleep-span-bar:hover {
    opacity: 1;
}

/* The still-running sleep, drawn in the active-sleep accent instead of grey. */
.sleep-span-bar.ongoing {
    fill: var(--dream-blue-ink);
}

/* --- Sleep/wake by day: horizontal interval bars ------------------------- */
/* Awake is the background the asleep blocks are painted over, so any hour not
   covered by a sleep segment reads as awake. */
.awake-bar {
    fill: #f0f0f0;
    stroke: var(--border-light);
    stroke-width: 0.5;
}

/* The deeper accent blue, not the pale row-highlight tone — at bar size the
   pale tint read as washed out against the grey awake background. */
.asleep-bar {
    fill: var(--dream-blue-ink);
    opacity: 0.9;
    cursor: pointer;
}

.asleep-bar:hover {
    opacity: 1;
}

/* The still-running sleep, a shade deeper again so it stands out from the
   completed bars around it. */
.asleep-bar.ongoing {
    fill: #1c5f9b;
    opacity: 1;
}

.day-row-label {
    font-size: 10px;
    fill: var(--text);
}

.day-row-total {
    font-size: 9px;
    fill: var(--text-light);
}

/* The interval chart is two stacked boxes (pinned header + scroll body) rather
   than a single svg, so let it lay out as a column and start at the top
   instead of being centred like the other charts. */
#sleep-chart-container:has(.sleep-day-header) {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
}

/* The newest day is drawn in its own svg above the scroll area so it stays
   put while older days scroll underneath it. The rule separates the pinned
   row from the scrolling ones so it reads as a fixed header. */
.sleep-day-header {
    display: block;
    flex: 0 0 auto;
    background: white;
    border-bottom: 1px solid var(--border);
}

/* Scrollbar kept VISIBLE (rather than the platform's auto-hiding overlay) so
   it's obvious there are older days below — the scrollability was previously
   undiscoverable. */
.sleep-day-scroll {
    overflow-y: scroll;
    overflow-x: hidden;
    /* Contain the scroll so reaching the end doesn't start scrolling the page
       behind it — important on a phone where this sits mid-page. */
    overscroll-behavior: contain;
    scrollbar-width: thin;                     /* Firefox */
    scrollbar-color: #bbb transparent;
}

.sleep-day-scroll::-webkit-scrollbar {
    width: 9px;
    -webkit-appearance: none;
}

.sleep-day-scroll::-webkit-scrollbar-track {
    background: #f2f2f2;
}

.sleep-day-scroll::-webkit-scrollbar-thumb {
    background: #bbb;
    border-radius: 5px;
    border: 2px solid #f2f2f2;
}

.sleep-day-scroll::-webkit-scrollbar-thumb:hover {
    background: #999;
}

/* Estimated stretch of the 24h feeding line, covering days that were never
   logged. Dashed and lightened so an estimate can't be read as a measurement. */
.line-estimated {
    stroke-dasharray: 5 4;
    stroke: var(--text-light);
    opacity: 0.75;
}

/* "⋮" marker between bars where days were skipped. */
.band-gap-dots {
    font-size: 15px;
    fill: var(--text-light);
    letter-spacing: 1px;
}

.band-gap:hover .band-gap-dots {
    fill: var(--text);
}

/* Per-chart range selector, sitting under each chart's tabs. */
.chart-range-row {
    display: flex;
    justify-content: center;
    padding: 2px 0 6px;
}

.chart-range {
    font-family: inherit;
    font-size: 11px;
    padding: 3px 6px;
    border: 1px solid var(--border);
    border-radius: 0;
    background: white;
    color: var(--text);
    cursor: pointer;
}

/* "· · ·" marker standing in for days that were never logged. */
.sleep-day-gap-dots {
    font-size: 13px;
    letter-spacing: 2px;
    fill: var(--text-light);
}

.sleep-day-gap:hover .sleep-day-gap-dots {
    fill: var(--text);
}

.sleep-day-scroll svg {
    display: block;
}

.gap-bar {
    fill: var(--text);
    opacity: 0.75;
    cursor: pointer;
}

.gap-bar:hover {
    opacity: 1;
}

.axis text {
    font-size: 11px;
}

.axis path,
.axis line {
    stroke: var(--border);
}

.grid line {
    stroke: none;
}

.grid path {
    stroke-width: 0;
}

.tooltip {
    position: absolute;
    /* Parked off-layout when idle.
     *
     * This is an absolutely-positioned box with a white background that sits
     * at the end of the document when no chart point is hovered. It EXTENDS
     * THE SCROLL AREA by its own height: measured on jazz, document
     * scrollHeight 1440 against an html box of 1418 -- a 22px band past the
     * end of the painted background, showing white in night mode once you
     * scrolled to the bottom. (Not seen on the sandbox, whose shorter content
     * never pushed it into view.)
     *
     * d3 appends it to <body> with opacity 0 and hides it by animating
     * OPACITY only -- it is never taken out of layout. Anchoring it to the
     * top-left keeps it off the end of the document; the JS sets explicit
     * left/top on hover, which overrides this, so the tooltip still appears
     * exactly where it should. */
    top: 0;
    left: 0;
    background: white;
    color: var(--text);
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: 0;
    font-size: 13px;
    pointer-events: none;
    z-index: 50;
    font-family: 'Times New Roman', Times, Georgia, serif;
    box-shadow: 2px 2px 0 rgba(0,0,0,0.1);
}


/* Responsive */
@media (max-width: 900px) {
    /* Intermediate step so the header strip keeps fitting on one line as the
       panel narrows, before the phone rules take over at 500px. */
    .panel-header-buttons .view-toggle {
        font-size: 10px;
        letter-spacing: 0.02em;
    }

    body {
        height: auto;
        min-height: 100vh;
        overflow-y: auto;
    }

    .container {
        flex-direction: column;
        height: auto;
        min-height: 100vh;
    }

    /* Cap the SCROLLING LOG, not the whole panel. Capping the panel made every
       sibling (header, and now the Ignore hint) eat into the table's height —
       the hint alone took ~88px, 12% of the box. With the cap on the scroll
       area the log always gets its full allowance and the hint simply extends
       the panel below it. */
    .left-panel {
        width: 100%;
        height: auto;
        min-height: 320px;
        border-right: none;
        border-bottom: 1px solid var(--border);
    }

    .spreadsheet-container {
        max-height: 70vh;
    }

    .main-panel {
        width: 100%;
        flex: none;
        height: auto;
        min-height: 400px;
        overflow: visible;
    }

    /* Fixed-height chart box on mobile. With #chart-container at min-height:0
       the SVG fills exactly the space left after the tabs/controls rows, so
       nothing spills past this border. Tall enough to keep the plot usable. */
    .chart-section {
        flex: none;
        height: 380px;
    }

    .stat-row {
        flex-wrap: wrap;
    }

    .stat-card {
        min-width: calc(50% - 4px);
    }

    .stat-card-wide {
        min-width: 100%;
    }

    /* Mobile: the dropdown returns to normal flow (not floated top-right) and
       sits full-width just below the "Today So Far" card. DOM order already is
       Today So Far -> dropdown -> windowed stats, so no reordering is needed. */
    .stats-header {
        position: static;
        justify-content: center;
        margin: 4px 0 10px;
        padding-bottom: 10px;
        border-bottom: 1px solid var(--border-light);
    }

    /* Below 900px the .left-panel becomes full-width, so the log tables fill it.
       The history columns are PROPORTIONAL (percentages), not fixed pixels — that
       was the whole problem: fixed widths only looked right at one panel size and
       left Notes stranding the ⋮ at every other. As percentages the columns flow
       with the panel at any width from a phone to a tablet: Time/mL/Spittle take
       content-appropriate shares and Notes takes the remainder, with no dead zone.
       The indent is a percentage too so it scales instead of dominating a narrow
       screen. Drop the 440px cap here so the table actually fills the panel. */
    #feed-entry-table,
    #poop-entry-table,
    #sleep-entry-table,
    #feed-table,
    #poop-table,
    #sleep-table {
        max-width: none;
    }

    /* Larger indent shifts the whole group of columns further right. */
    #feed-table,
    #poop-table,
    #sleep-table {
        --log-indent: 12%;
        --date-indent: 6%;
    }

    /* Time gets a generous share so "12:45 PM" fits after the indent without the
       other columns crowding it (with white-space:nowrap on .history-time it can
       no longer wrap, so it needs real room). Notes (auto) absorbs the change. */
    #feed-table th:nth-child(1) { width: 32%; }   /* Time (carries the indent) */
    #feed-table th:nth-child(2) { width: 14%; }   /* mL */
    #feed-table th:nth-child(3) { width: 18%; }   /* Spittle */
    #feed-table th:nth-child(4) { width: auto; }  /* Notes — the remainder */
    #feed-table th:nth-child(5) { width: 34px; }  /* edit ⋮ (icon, fixed) */

    /* Poop has only Time + Notes, so Time gets a much larger share than the
       feeding Time column — otherwise the timestamp ("12:45 PM") wraps to two
       lines while Notes hogs the rest. A bigger indent also nudges Time right. */
    #poop-table {
        --log-indent: 16%;
        --date-indent: 8%;
    }
    #poop-table th:nth-child(1) { width: 46%; }   /* Time — wide enough for one line */
    #poop-table th:nth-child(2) { width: auto; }  /* Notes */
    #poop-table th:nth-child(3) { width: 34px; }  /* edit ⋮ */

    /* The entry form is inside a full-width card here too, so its fields must
       spread across it rather than bunch at the left. Same fix as the log:
       proportional columns instead of fixed pixels. Date needs enough room for
       the native picker, so it and Time take the larger shares; Notes fills the
       rest. Percentages sum to 100 so the fields reach the right edge. */
    #feed-entry-table th:nth-child(1) { width: 23%; }   /* Date */
    #feed-entry-table th:nth-child(2) { width: 22%; }   /* Time */
    #feed-entry-table th:nth-child(3) { width: 16%; }   /* mL — wide enough to show the label */
    #feed-entry-table th:nth-child(4) { width: 15%; }   /* Spittle */
    #feed-entry-table th:nth-child(5) { width: auto; }  /* Notes */

    #poop-entry-table th:nth-child(1) { width: 26%; }   /* Date */
    #poop-entry-table th:nth-child(2) { width: 26%; }   /* Time */
    #poop-entry-table th:nth-child(3) { width: auto; }  /* Notes */

    /* Sleep log: three short fixed strings + Notes. The two timestamps carry
       nowrap, so give them real percentage room rather than letting Notes take
       everything — otherwise "12:45 PM" overflows its column on a phone. */
    #sleep-table th:nth-child(1) { width: 30%; }   /* Asleep (carries the indent) */
    #sleep-table th:nth-child(2) { width: 22%; }   /* Awake */
    #sleep-table th:nth-child(3) { width: 18%; }   /* Length */
    #sleep-table th:nth-child(4) { width: auto; }  /* Notes */
    #sleep-table th:nth-child(5) { width: 34px; }  /* edit ⋮ */

    /* The sleep entry form has no header columns to size — it's a stacked
       flex layout (.sleep-backfill) that already flows to any width. */
}

@media (max-width: 500px) {
    /* Same reasoning as the 900px block: cap the log, not the panel, so the
       Ignore hint extends the panel instead of shortening the table. */
    .spreadsheet-container {
        max-height: 65vh;
    }

    /* Tighter on a phone, where vertical space is scarcest. */
    .ignore-help {
        margin: 8px 8px 10px;
        padding: 7px 9px;
        font-size: 10px;
        line-height: 1.45;
    }

    /* Everything stays on one line on a phone: shrink the title and the nav
       labels rather than wrapping either. */
    .panel-header {
        padding: 12px 10px;
        gap: 7px;
    }

    .panel-header h2 {
        font-size: 13px;
    }

    .panel-header-buttons {
        gap: 3px;
    }

    .panel-header-buttons .view-toggle {
        font-size: 9px;
        letter-spacing: 0;
        padding: 6px 1px;
    }

    #feed-table,
    #poop-table,
    #sleep-table,
    #feed-entry-table,
    #poop-entry-table,
    #sleep-entry-table {
        font-size: 11px;
    }

    .entry-card {
        margin: 8px 6px 12px;
        padding: 8px 8px 10px;
    }

    /* Entry-form column widths are NOT re-pinned here — the proportional
       percentages in the max-width:900px block already flow to phone widths.
       Fixed pixels here were what re-bunched the fields on small screens. */

    #feed-table .time-picker .time-select,
    #poop-table .time-picker .time-select,
    #sleep-table .time-picker .time-select,
    #feed-entry-table .time-picker .time-select,
    #poop-entry-table .time-picker .time-select,
    #sleep-entry-table .time-picker .time-select {
        padding: 4px 2px;
        font-size: 12px;
    }

    .stat-card {
        min-width: 100%;
        padding: 6px 8px;
    }

    .stats-header {
        flex-wrap: wrap;
    }

    .day-night-controls {
        flex-wrap: wrap;
    }

    .nav-btn {
        padding: 4px 8px;
        font-size: 12px;
    }

    #chart-date-label {
        font-size: 12px;
        min-width: 80px;
    }
}

/* ============================================================
   Growth animation view
   ============================================================ */
/* The four screen-nav boxes, kept on one line with the title at any width.
   Equal 1fr columns so they read as one control strip of identical boxes.
   min-width:0 lets the strip shrink below its content's natural size instead
   of forcing a wrap. */
.panel-header-buttons {
    display: grid;
    /* auto-fit on the actual children, so hiding a nav box (e.g. the retired
       Animation view) re-spreads the rest instead of leaving a gap. */
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 5px;
    flex: 1 1 auto;
    min-width: 0;
}

/* Labels stay on one line; the box clips rather than wraps if space runs out.
   Font/padding shrink at the narrow breakpoints below so it rarely comes to
   that. */
.panel-header-buttons .view-toggle {
    padding: 6px 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: center;
    min-width: 0;
}

/* Header inside the growth view: Back button + title. */
.growth-header {
    width: 100%;
    max-width: 480px;
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 16px;
}
.growth-title {
    font-size: 20px;
    margin: 0;
}
.growth-view {
    max-width: 640px;
    margin: 0 auto;
    padding: 20px 16px 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* The stage holds the stacked photo layers; fixed portrait aspect so the
   crossfade has no layout shift. */
.growth-stage {
    position: relative;
    /* Size by height so the photo + caption + controls fit the viewport with no
       scroll; width is derived from the 3:4 portrait aspect ratio. On narrow
       screens the min() clamps to the available width instead. */
    height: min(62vh, 640px);
    width: calc(min(62vh, 640px) * 3 / 4);
    max-width: 100%;
    aspect-ratio: 3 / 4;
    background: #1c1c1e;
    border: 1px solid var(--border);
    overflow: hidden;
}
.growth-frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: opacity 0.9s ease-in-out;  /* must match GROWTH_FADE_MS */
    will-change: opacity;
}
.growth-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #999;
    font-size: 15px;
}

.growth-caption {
    text-align: center;
    margin: 18px 0 6px;
}
.growth-date {
    font-size: 24px;
    font-weight: bold;
    letter-spacing: 0.5px;
}
.growth-age {
    font-size: 15px;
    color: var(--text-light);
    margin-top: 2px;
    font-style: italic;
}

.growth-controls {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    max-width: 480px;
    margin-top: 14px;
}
.growth-btn {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border: 1px solid var(--border);
    background: white;
    color: var(--text);
    font-size: 15px;
    cursor: pointer;
    border-radius: 0;
    font-family: inherit;
}
.growth-btn:hover {
    background: var(--text);
    color: white;
}
.growth-scrubber {
    flex: 1 1 auto;
    accent-color: var(--text);
    cursor: pointer;
}
.growth-count {
    flex: 0 0 auto;
    font-size: 14px;
    color: var(--text-light);
    min-width: 52px;
    text-align: right;
}

@media (max-width: 600px) {
    .growth-date { font-size: 20px; }
    .nav-tab { padding: 8px 22px; font-size: 14px; }
}

/* Small grey note under a stat value, e.g. "based on 4 of 7 days". Tells the
   reader the average came from a partial window without changing the number. */
.stat-note {
    margin-top: 3px;
    font-size: 9px;
    letter-spacing: 0.02em;
    color: var(--text-light);
    opacity: 0.85;
}

/* A lookback option with nothing logged is disabled rather than showing "--",
   which would read as a real measurement of zero. */
#stats-lookback option:disabled {
    color: #bbb;
}

/* ==========================================================================
   Sandbox mode
   ==========================================================================
   Only rendered by the sandbox instance (SANDBOX_MODE in its data.js). The
   banner is deliberately loud: the one real hazard of a test copy is mistaking
   it for the live tracker and logging a real feed into it. */
.sandbox-banner {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 8px 12px;
    background: #ffd400;
    color: #000;
    border-bottom: 2px solid #000;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-align: center;
}

.sandbox-refresh-btn {
    padding: 4px 10px;
    border: 1px solid #000;
    border-radius: 0;
    background: #fff;
    color: #000;
    font-family: inherit;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
}

.sandbox-refresh-btn:hover:not(:disabled) { background: #000; color: #ffd400; }
.sandbox-refresh-btn:disabled { opacity: 0.6; cursor: default; }

@media (max-width: 900px) {
    .sandbox-banner { font-size: 11px; padding: 6px 8px; gap: 8px; }
}

/* ==========================================================================
   Demo mode
   ==========================================================================
   The public read-only instance (DEMO_MODE in its data.js). The banner is
   calm rather than loud — unlike the sandbox warning there's no hazard to
   flag, it's just context for a visitor. */
.demo-banner {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 8px 12px;
    background: var(--card, #fff);
    color: var(--text-light, #666);
    border-bottom: 1px solid var(--border, #e0e0e0);
    font-size: 12px;
    text-align: center;
}

/* Poop notes only. Blurred rather than removed so the column still reads as
   populated — the layout matches the real app, minus the words. The text IS
   still in the DOM: this is a courtesy screen, not redaction. */
.demo-blurred {
    filter: blur(4px);
    user-select: none;
    pointer-events: none;
}

/* Read-only notice shown when a visitor tries to edit. */
.demo-notice {
    position: fixed;
    left: 50%;
    bottom: 24px;
    transform: translateX(-50%);
    z-index: 300;
    max-width: min(420px, calc(100vw - 32px));
    padding: 10px 16px;
    background: var(--text, #222);
    color: #fff;
    border-radius: 6px;
    font-size: 13px;
    text-align: center;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
    opacity: 0;
    transition: opacity 0.18s ease;
}

.demo-notice.visible { opacity: 1; }

/* Editing controls stay VISIBLE in the demo, so the app reads as the real
   thing — clicking one raises the read-only notice instead of writing. The
   block is enforced in app.js (assertWritable + the showPasscodeModal
   chokepoint), so nothing needs hiding here. Only the sandbox's own banner is
   suppressed, since the demo has its own. */
body.demo-mode .sandbox-banner { display: none !important; }

@media (max-width: 900px) {
    .demo-banner { font-size: 11px; padding: 6px 8px; }
}

/* ==========================================================================
   Poop charts
   ==========================================================================
   "Poops by Day" reuses the sleep day-row structure (.sleep-day-header,
   .sleep-day-scroll, .day-row-label, .day-row-total), so only the marks
   themselves need their own rules. */

/* The empty day track. Lighter than .awake-bar: for sleep the background is a
   real state (awake), but here it's just an axis for the marks to sit on. */
.poop-day-track {
    fill: #f7f7f7;
    stroke: var(--border-light);
    stroke-width: 0.5;
}

.poop-day-dot {
    fill: #7a5a3a;   /* muted brown — reads as its own series without being crude */
    stroke: #fff;
    stroke-width: 1;
    cursor: pointer;
}

.poop-day-dot:hover {
    fill: #5a3f28;
}

.poop-gap-line {
    stroke: #7a5a3a;
    stroke-width: 1.5;
}

.poop-gap-dot {
    fill: #7a5a3a;
    cursor: pointer;
}

/* The still-running gap (last poop -> now): dashed line, hollow marker, so an
   in-progress measurement never looks like a completed one. */
.poop-gap-line.ongoing {
    stroke-dasharray: 4 3;
    opacity: 0.75;
}

.poop-gap-dot.ongoing {
    fill: #fff;
    stroke: #7a5a3a;
    stroke-width: 2;
}

/* ==========================================================================
   Tips & tricks — the crawling baby
   ========================================================================== */

/* The baby crawls along the bottom of the screen, left to right, with a gentle
   bob. Small and unhurried: it should read as something that wandered across
   the page, not as a button. */
.crawling-baby {
    position: fixed;
    left: -140px;
    width: 92px;
    height: 60px;
    z-index: 60;
    cursor: pointer;
    opacity: 0.88;
    /* The silhouette fills with currentColor, so this one line sets it. */
    color: #6b3fa0;
    animation: baby-crawl var(--crawl-ms, 19000ms) linear forwards;
    /* Big enough tap target on a phone without making the art bigger. */
    padding: 10px;
    box-sizing: content-box;
}

/* All crawl frames are stacked; JS shows one at a time via .on. */
.crawling-baby svg {
    position: absolute;
    inset: 10px;            /* matches .crawling-baby padding */
    width: 92px;
    height: 60px;
    display: none;
}

.crawling-baby svg.on { display: block; }

.crawling-baby:hover { opacity: 1; }

@keyframes baby-crawl {
    from { transform: translateX(0); }
    to   { transform: translateX(calc(100vw + 200px)); }
}

@media (prefers-reduced-motion: reduce) {
    .crawling-baby { animation-duration: 30s; }
}

/* --- The tips page itself ------------------------------------------------ */
.tips-view {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--panel-bg);
    overflow-y: auto;
    padding: 28px 20px 60px;
}

.tips-inner {
    max-width: 620px;
    margin: 0 auto;
}

.tips-close {
    background: none;
    border: none;
    font-family: inherit;
    font-size: 13px;
    font-style: italic;
    color: var(--text-light);
    cursor: pointer;
    padding: 4px 0;
    margin-bottom: 18px;
}

.tips-close:hover { color: var(--text); }

.tips-title {
    font-size: 30px;
    font-weight: normal;
    font-style: italic;
    letter-spacing: 0.04em;
    margin-bottom: 2px;
}


.tips-intro {
    font-size: 15px;
    line-height: 1.7;
    padding-bottom: 20px;
    margin-bottom: 8px;
    border-bottom: 1px solid var(--border-light);
}

.tips-list {
    list-style: none;
    counter-reset: tip;
}

.tip {
    counter-increment: tip;
    padding: 20px 0;
    border-bottom: 1px solid var(--border-light);
}

.tip:last-child { border-bottom: none; }

.tip-title {
    font-size: 17px;
    font-weight: normal;
    font-style: italic;
    margin-bottom: 7px;
}

/* The number sits in the margin so the titles stay flush. */
.tip-title::before {
    content: counter(tip) ".";
    font-style: normal;
    color: var(--text-light);
    margin-right: 8px;
}

.tip-body {
    font-size: 14.5px;
    line-height: 1.7;
    color: var(--text);
}

.tips-footer {
    margin-top: 26px;
    padding-top: 18px;
    border-top: 1px solid var(--border);
    font-size: 12px;
    font-style: italic;
    color: var(--text-light);
    line-height: 1.6;
}

@media (max-width: 600px) {
    .tips-view { padding: 20px 16px 50px; }
    .tips-title { font-size: 25px; }
    .tips-intro { font-size: 14px; }
}

/* ==========================================================================
   Mobile: stop ACCIDENTAL zoom
   ==========================================================================
   Double-tap-to-zoom was firing constantly in normal use — tapping a log row,
   an IGNORE button, or a chart twice in quick succession zoomed the page and
   left you stranded until you pinched back out.

   `touch-action: manipulation` disables the double-tap-zoom gesture while
   leaving PINCH-zoom working, so this fixes the accidental case without taking
   away deliberate zoom (which `user-scalable=no` in the viewport meta would,
   and which is a real accessibility loss). It also removes the ~300ms click
   delay browsers add while waiting to see if a second tap is coming, so taps
   feel more immediate. */
html {
    touch-action: manipulation;
}

/* Belt and braces for the controls people tap most, since some engines only
   honour touch-action on the element actually being tapped. */
button,
.view-toggle,
.tab-btn,
.nav-btn,
.ignore-btn,
select,
.stat-card,
.crawling-baby {
    touch-action: manipulation;
}

/* iOS Safari auto-zooms the whole page whenever you focus a form field whose
   font-size is under 16px — and the entry-form inputs are 13px, so tapping the
   mL box (or a time field, or the notes box) zoomed the app EVERY time. That
   is almost certainly the zoom that keeps catching you out, since it fires on
   the most common action in the app.

   16px on touch devices only: desktop keeps the compact 13px sizing, which the
   narrow entry-form columns are designed around. Applied at the media-query
   level rather than per-rule so it beats the specific rules above. */
@media (hover: none) and (pointer: coarse) {
    input[type="text"],
    input[type="number"],
    input[type="date"],
    input[type="time"],
    input[type="password"],
    select,
    textarea {
        font-size: 16px;
    }
}

/* ==========================================================================
   Hamburger menu + secondary pages (About / Settings / Contact)
   ==========================================================================
   These are reference and configuration screens, not the day-to-day logging
   the app is for, so they sit behind a menu rather than taking header space
   from the four log nav boxes. */
.hamburger {
    flex: 0 0 auto;
    width: 30px;
    height: 26px;
    padding: 5px 4px;
    margin-right: 2px;
    background: none;
    border: 1px solid var(--border);
    border-radius: 0;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.hamburger span {
    display: block;
    height: 3px;
    background: var(--text);
}

/* While the app is locked, #tracker-view (which contains this button) is
   blurred 14px and dropped to 25% opacity. That blur erased the thin bars
   entirely while the button's 1px border still read as a box, so the menu
   looked like an empty square.
   The button is already unusable while locked (#tracker-view is
   pointer-events: none and the lock overlay sits on top), so hide it outright
   rather than rendering a control that looks broken and does nothing. */
body.locked .hamburger {
    visibility: hidden;
}

.hamburger:hover { background: var(--text); }
.hamburger:hover span { background: #fff; }

.app-menu {
    position: absolute;
    top: 52px;
    left: 12px;
    z-index: 300;
    background: var(--panel-bg);
    border: 1px solid var(--border);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);
    min-width: 150px;
}

.app-menu-item {
    display: block;
    width: 100%;
    padding: 10px 16px;
    background: none;
    border: none;
    border-bottom: 1px solid var(--border-light);
    font-family: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}

.app-menu-item:last-child { border-bottom: none; }
.app-menu-item:hover { background: var(--text); color: #fff; }

/* --- The overlay pages --------------------------------------------------- */
.menu-page {
    position: fixed;
    inset: 0;
    z-index: 250;
    background: var(--panel-bg);
    overflow-y: auto;
    padding: 26px 20px 60px;
}

.menu-page-inner {
    max-width: 620px;
    margin: 0 auto;
}

.menu-page-close {
    background: none;
    border: none;
    font-family: inherit;
    font-size: 13px;
    font-style: italic;
    color: var(--text-light);
    cursor: pointer;
    padding: 4px 0;
    margin-bottom: 16px;
}

.menu-page-close:hover { color: var(--text); }

.menu-page-title {
    font-size: 28px;
    font-weight: normal;
    font-style: italic;
    letter-spacing: 0.04em;
    margin-bottom: 14px;
}

.menu-intro {
    font-size: 14.5px;
    line-height: 1.7;
    padding-bottom: 18px;
    border-bottom: 1px solid var(--border-light);
}

.menu-section { padding: 18px 0; border-bottom: 1px solid var(--border-light); }
.menu-section:last-of-type { border-bottom: none; }

.menu-section h2 {
    font-size: 16px;
    font-weight: normal;
    font-style: italic;
    margin-bottom: 8px;
}

.menu-section p {
    font-size: 14px;
    line-height: 1.7;
    margin-bottom: 9px;
}

.menu-section p:last-child { margin-bottom: 0; }
.menu-empty { color: var(--text-light); font-style: italic; }

/* --- Settings controls --------------------------------------------------- */
.setting-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

.setting-row label {
    flex: 0 0 90px;
    font-size: 13px;
}

.setting-row input[type="text"],
.setting-row input[type="date"] {
    flex: 1;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 0;
    font-family: inherit;
    font-size: 14px;
    background: #fff;
}

.setting-note {
    font-size: 12px;
    font-style: italic;
    color: var(--text-light);
    line-height: 1.6;
}

.unit-toggle { display: flex; }

.unit-opt {
    padding: 7px 18px;
    border: 1px solid var(--border);
    background: #fff;
    font-family: inherit;
    font-size: 13px;
    cursor: pointer;
}

.unit-opt + .unit-opt { border-left: none; }
.unit-opt.active { background: var(--text); color: #fff; }

.setting-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 22px;
    padding-top: 18px;
    border-top: 1px solid var(--border);
}

.setting-save {
    padding: 9px 22px;
    background: var(--text);
    color: #fff;
    border: 1px solid var(--text);
    border-radius: 0;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
}

.setting-save:hover { background: #fff; color: var(--text); }

.setting-status {
    font-size: 12px;
    font-style: italic;
    color: var(--text-light);
}

@media (max-width: 600px) {
    .menu-page { padding: 18px 16px 50px; }
    .menu-page-title { font-size: 23px; }
    .app-menu { left: 8px; top: 48px; }
    .setting-row { flex-wrap: wrap; gap: 6px; }
    .setting-row label { flex: 0 0 100%; }
}

/* The hamburger takes ~32px from the header strip, which was enough to make
   "Feeding Log" ellipsis on a narrow panel. Tighten the nav boxes slightly so
   all three labels still fit on one line beside it. */
.panel-header-buttons .view-toggle {
    padding-left: 7px;
    padding-right: 7px;
    letter-spacing: 0.02em;
}


/* Sex toggle: the ♀/♂ symbols alone, so it reads as a neutral data field
   rather than a gendered statement. Tapping the active one clears it. */
.sex-toggle { display: flex; }

.sex-opt {
    width: 44px;
    padding: 6px 0;
    border: 1px solid var(--border);
    background: #fff;
    font-family: inherit;
    font-size: 17px;
    line-height: 1.1;
    cursor: pointer;
}

.sex-opt + .sex-opt { border-left: none; }
.sex-opt.active { background: var(--text); color: #fff; }

/* ==========================================================================
   Tutorial
   ==========================================================================
   One speech bubble per feature, anchored to the thing it describes. The
   backdrop dims everything except the spotlit element, so it's obvious what
   the current bubble is talking about. */
.tut-backdrop {
    position: fixed;
    inset: 0;
    z-index: 400;
    background: rgba(0, 0, 0, 0.45);
}

/* Lift the element being described above the backdrop so it stays readable. */
.tut-spotlight {
    position: relative;
    z-index: 401;
    box-shadow: 0 0 0 3px #ffd400, 0 0 0 9px rgba(255, 212, 0, 0.25);
    border-radius: 2px;
}

/* THE NAV BOX FOR THE STEP YOU ARE ON.
 *
 * The tour spotlights the thing it is describing, but the Feeding / Sleep /
 * Poop buttons above it also change between steps, and that change was
 * invisible: `.active` is the same quiet fill it uses in normal operation. So
 * a step would switch to the sleep log and nothing told you WHERE it had gone.
 *
 * THREE THINGS THIS RULE GETS RIGHT THAT THE FIRST VERSION DID NOT:
 *
 * 1. It is driven by a class the TUTORIAL sets (.tut-nav), not by `.active`.
 *    The settings step has no `view`, so setLeftView() never runs and whatever
 *    log you happened to be on keeps `.active` -- which meant the Poop box
 *    glowed during a step about Settings. showTutorialStep() now sets .tut-nav
 *    on the step's own log and clears it when a step has none.
 * 2. It is scoped to the three log boxes by ID. `.view-toggle` also matches
 *    the Animation button and the growth screen's Back button, so the old
 *    selector could light either of those up.
 * 3. It is much stronger. A 3px ring on a grey button is easy to miss on a
 *    busy screen; this fills the button, inverts the text, and swings a wide
 *    halo, so it cannot be mistaken for the ordinary selected state.
 */
@keyframes tut-nav-pulse {
    0%, 100% {
        box-shadow: 0 0 0 4px #ffd400, 0 0 0 8px rgba(255, 212, 0, 0.45);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 0 4px #ffd400, 0 0 0 22px rgba(255, 212, 0, 0);
        transform: scale(1.06);
    }
}

body.tut-running #nav-feeding.tut-nav,
body.tut-running #nav-sleep.tut-nav,
body.tut-running #nav-poop.tut-nav {
    position: relative;
    z-index: 401;
    /* Filled, not outlined. The ordinary active state is a pale fill, so an
       outline alone reads as "selected"; this has to read as "look here". */
    background: #ffd400;
    color: #000;
    font-weight: 700;
    border-color: #ffd400;
    border-radius: 3px;
    animation: tut-nav-pulse 1.4s ease-in-out infinite;
}

/* Someone who asks for less motion still gets the highlight, just still. */
@media (prefers-reduced-motion: reduce) {
    body.tut-running #nav-feeding.tut-nav,
    body.tut-running #nav-sleep.tut-nav,
    body.tut-running #nav-poop.tut-nav {
        animation: none;
        transform: none;
        box-shadow: 0 0 0 4px #ffd400, 0 0 0 10px rgba(255, 212, 0, 0.4);
    }
}

.tut-bubble {
    position: fixed;
    z-index: 402;
    width: min(310px, calc(100vw - 32px));
    background: var(--panel-bg);
    border: 1px solid var(--border);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    padding: 14px 16px 12px;
}

.tut-step {
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-light);
    margin-bottom: 5px;
}

.tut-bubble h3 {
    font-size: 15px;
    font-weight: normal;
    font-style: italic;
    margin-bottom: 6px;
}

.tut-bubble p {
    font-size: 13px;
    line-height: 1.6;
    margin-bottom: 12px;
}

.tut-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
}

.tut-skip {
    background: none;
    border: none;
    font-family: inherit;
    font-size: 12px;
    font-style: italic;
    color: var(--text-light);
    cursor: pointer;
    padding: 4px 0;
}

.tut-skip:hover { color: var(--text); }

.tut-next {
    padding: 7px 18px;
    background: var(--text);
    color: #fff;
    border: 1px solid var(--text);
    border-radius: 0;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
}

.tut-next:hover { background: #fff; color: var(--text); }

/* The settings step opens the menu overlay, which sits at z-index 250 — the
   bubble and backdrop must clear it. */
body .menu-page { z-index: 250; }

/* Three unit options now (mL / US oz / UK oz) — let them wrap on a phone
   rather than overflowing the row. */
.unit-toggle { flex-wrap: wrap; }
.unit-opt { padding: 7px 14px; }

/* While the tutorial is running on a phone, reserve a strip at the bottom of
   the page so a tall spotlit element (a chart) can be scrolled clear of the
   bubble that is pinned down there. Without this the last section of the page
   can't scroll far enough to get out from under it. */
@media (max-width: 699px) {
    body.tut-running #tracker-view {
        padding-bottom: 240px;
    }
}

/* The third sex option is the Mercury symbol (U+263F ☿), which renders
   narrower and lighter than the Venus/Mars glyphs. Nudge it up so the three
   read as one row of equal weight.
   (Not Prince's Love Symbol, which was never properly encoded in Unicode and
   would not render for most users. Mercury sits in the same block as ♀ and ♂,
   so the three are consistently supported.) */
.sex-opt-unknown {
    font-size: 19px;
    line-height: 1;
}

/* ==========================================================================
   Due hints
   ==========================================================================
   A pulsing dot, a short label, and a "?" that explains itself. Sits directly
   under the entry-card title so it reads as context for the form below it.
   Absent entirely when there is nothing to say — no reassuring green state. */
.due-hint {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 8px;
    font-size: 12px;
}

.due-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    flex: 0 0 auto;
    /* duration is set inline, scaled to how overdue it is */
    animation-name: due-pulse;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
}

@keyframes due-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.45; transform: scale(0.82); }
}

@media (prefers-reduced-motion: reduce) {
    .due-dot { animation: none; }
}

.due-label {
    font-style: italic;
    color: var(--text);
}

.due-hint-poop .due-label { color: var(--text-light); }

/* The explainer trigger. Deliberately understated — it should be findable but
   never compete with the logging controls. */
.due-why {
    width: 15px;
    height: 15px;
    padding: 0;
    border: 1px solid var(--border-light);
    border-radius: 50%;
    background: none;
    font-family: inherit;
    font-size: 10px;
    line-height: 1;
    color: var(--text-light);
    cursor: pointer;
}

.due-why:hover { background: var(--text); color: #fff; border-color: var(--text); }

/* --- The explainer itself ------------------------------------------------- */
.due-modal {
    position: fixed;
    inset: 0;
    z-index: 500;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.due-modal-inner {
    position: relative;
    background: var(--panel-bg);
    border: 1px solid var(--border);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
    padding: 22px 24px 20px;
    max-width: 460px;
    max-height: 80vh;
    overflow-y: auto;
}

.due-modal-inner h3 {
    font-size: 18px;
    font-weight: normal;
    font-style: italic;
    margin-bottom: 12px;
}

.due-modal-inner h4 {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-light);
    margin: 16px 0 6px;
}

.due-modal-inner p,
.due-modal-inner li {
    font-size: 13.5px;
    line-height: 1.65;
    margin-bottom: 8px;
}

.due-modal-inner ul { padding-left: 18px; margin-bottom: 8px; }

.due-note {
    font-style: italic;
    color: var(--text-light);
}

.due-modal-close {
    position: absolute;
    top: 10px;
    right: 12px;
    background: none;
    border: none;
    font-size: 22px;
    line-height: 1;
    color: var(--text-light);
    cursor: pointer;
}

.due-modal-close:hover { color: var(--text); }

/* Export button in Settings. Secondary to Save, so outlined rather than filled. */
.setting-export {
    padding: 9px 18px;
    background: #fff;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    cursor: pointer;
    margin-right: 10px;
}

.setting-export:hover { background: var(--text); color: #fff; }

/* A quiet prediction, before it is actually due: no dot, softer text. */
.due-hint-quiet .due-label { color: var(--text-light); }

/* Superscript citation links in explainers and settings. */
.cite {
    font-size: 9px;
    vertical-align: super;
    line-height: 0;
    margin-left: 1px;
    color: var(--text-light);
    text-decoration: none;
}

.cite:hover { color: var(--text); text-decoration: underline; }

/* --- Toggleable column header -------------------------------------------- */
/* The sleep table's Length header is a button: clicking it flips the column
   between time asleep and time awake. Styled to look like the other headers
   (the surrounding th rules set the size/case) with only a dotted underline
   to hint that it does something. */
.col-toggle {
    font: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    border-bottom: 1px dotted var(--border);
}

.col-toggle:hover { border-bottom-style: solid; }

/* --- Tutorial demo-data badge -------------------------------------------- */
/* Shown only while the tutorial is running against synthetic data, so the
   numbers on screen are never mistaken for the user's own. Sits above the
   tutorial backdrop (z-index 60 there) and is click-through so it can never
   block the Next button. */
.demo-badge {
    position: fixed;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    /* Above the tutorial backdrop (400), or the badge explaining that the
       numbers are fake would itself be dimmed out behind it. */
    z-index: 405;
    pointer-events: none;
    padding: 5px 12px;
    border-radius: 999px;
    background: var(--text);
    color: var(--bg, #fff);
    font-size: 11px;
    letter-spacing: 0.03em;
    opacity: 0.92;
    max-width: calc(100vw - 24px);
    text-align: center;
}

/* --- Tutorial escort babies ---------------------------------------------- */
/* Crawl toward whatever the current tutorial step points at, then sit up on
   arrival. Shares the artwork with the Tips easter egg (.crawling-baby) but
   not its behaviour: these are aimed at a target and cleaned up with the
   tutorial, and they are never clickable.

   Drawn above everything else in the tutorial so nothing clips them, and
   pointer-events:none so they can never swallow a tap. */
.tut-baby {
    position: fixed;
    width: 66px;
    height: 44px;
    /* Above the backdrop (400) AND the bubble (402): at 70 the babies were
       drawn under both, and on a phone the bubble pins to the bottom of the
       screen exactly where they arrive, so their heads were being covered.
       They are pointer-events:none, so sitting on top costs no interaction. */
    z-index: 410;
    pointer-events: none;
    opacity: 0.92;
    will-change: transform;
}

/* Inner wrapper carries the left/right facing so the outer element's
   translateX is never flipped by a scaleX on the same node. */
.tut-baby-inner {
    position: absolute;
    inset: 0;
}

.tut-baby svg {
    position: absolute;
    inset: 0;
    width: 66px;
    height: 44px;
    display: none;
}

.tut-baby svg.on { display: block; }

/* A day the clock changed on is shorter or longer than 24h, so its bar is
   drawn to the day's real length and labelled rather than silently mis-scaled. */
.travel-label {
    font-size: 9px;
    fill: var(--muted, #888);
    font-style: italic;
}

/* ===================================================================
   Weight and height entry, with unit toggles
   =================================================================== */

.measure-entry {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.measure-entry input {
    width: 70px;
}

.measure-unit {
    font-size: 14px;
    color: var(--text-light);
}

/* Same shape as the mL/oz toggle in Units, so the two read as one idea. */
.unit-toggle {
    display: inline-flex;
    border: 1px solid var(--border);
    margin-left: 4px;
}

.unit-opt {
    font-family: inherit;
    font-size: 13px;
    padding: 3px 9px;
    border: none;
    background: var(--panel-bg);
    color: var(--text);
    cursor: pointer;
}

.unit-opt + .unit-opt { border-left: 1px solid var(--border); }
.unit-opt.active { background: var(--border); color: #fff; }
