  :root{
    --bg:#14141C;
    --surface:#1E1E2A;
    --surface-2:#262635;
    --ink:#F5F5FA;
    --grey:#9C9AB5;
    --grey-light:#6E6C87;
    --line:#33333D;
    --accent:#3B82F6;
    --hero-bg:#0A0A10;
    --good:#22C55E;
    --bad:#F87171;
    --chart-line:#6B6B8C;
    --shadow-a:rgba(0,0,0,0.4);
    --shadow-b:rgba(0,0,0,0.5);
    --shadow-c:rgba(0,0,0,0.6);
    --shadow-d:rgba(0,0,0,0.7);
    --shadow-e:rgba(0,0,0,0.8);
    /* Single source of truth for the notch insets — the page top padding and
       the sticky header both need them, and applying env() in both places
       double-counted the notch, leaving a dead gap under the title. */
    --safe-top:env(safe-area-inset-top);
    --safe-bottom:env(safe-area-inset-bottom);
    color-scheme: dark;
  }
  /* Light theme: same variable names, different values — everything that
     already references var(--bg)/var(--ink)/etc. (which is nearly the
     whole stylesheet) picks this up automatically with no further changes
     needed there. --hero-bg (the big digital reference-clock display) and
     the analog clock face in clock.js are deliberately left dark/white
     respectively in BOTH themes — they're meant to look like a real
     clock's display regardless of the app's own chrome. */
  :root[data-theme="light"]{
    --bg:#F7F7FA;
    --surface:#FFFFFF;
    --surface-2:#F0F0F5;
    --ink:#14141C;
    --grey:#6B6B85;
    --grey-light:#9C9AB5;
    --line:#E1E1E8;
    --good:#16A34A;
    --bad:#DC2626;
    --chart-line:#52526B;
    /* Shadows tuned for dark backgrounds read as harsh, floating black
       smudges on a white page — much lighter and less opaque here, using
       the page's own ink color rather than pure black so they blend
       rather than looking like a separate dark layer. */
    --shadow-a:rgba(20,20,28,0.08);
    --shadow-b:rgba(20,20,28,0.10);
    --shadow-c:rgba(20,20,28,0.12);
    --shadow-d:rgba(20,20,28,0.14);
    --shadow-e:rgba(20,20,28,0.18);
    color-scheme: light;
  }
  *{box-sizing:border-box;}
  /* The browser paints its own translucent blue over whatever clickable
     element you press. On the collection cards that washed the entire card
     blue for a moment before the green wind sweep began, reading as a second,
     unrelated effect. We draw our own press feedback everywhere, so this one
     is never wanted. */
  *{-webkit-tap-highlight-color:transparent;}
  button, label, input, .tab{
    -webkit-text-fill-color:currentColor;
  }
  html,body{
    margin:0;
    padding:0;
    overscroll-behavior-y:auto;
    overflow-anchor:none;
  }
  html{
    background:var(--bg);
    color-scheme:dark;
  }
  body{
    background:var(--bg);
    color:var(--ink);
    font-family:'Inter',-apple-system,sans-serif;
    -webkit-font-smoothing:antialiased;
    min-height:100vh;
  }
  .app{
    max-width:480px;
    margin:0 auto;
    padding:calc(var(--safe-top) + 14px) 18px calc(96px + var(--safe-bottom));
    background:var(--bg);
    /* Always taller than the viewport by a fixed margin, regardless of how
       much a given tab actually contains — guarantees real, consistent
       scroll room on every tab (even an empty one), so the browser's own
       address-bar show/hide behaves the same everywhere instead of only
       working on tabs that happen to have enough content. */
    min-height:calc(100vh + 120px);
    min-height:calc(100dvh + 120px);
  }
  .app-title{
    margin-top:10px;
    margin-bottom:8px;
    text-align:center;
  }
  h1{
    font-family:'Inter',sans-serif;
    font-weight:700;
    font-size:19px;
    letter-spacing:-0.3px;
    margin:0 0 2px;
    color:var(--ink);
  }
  .app-title p{
    font-family:'Inter',sans-serif;
    font-size:11px;
    color:var(--accent);
    margin:0;
  }
  .subhead{
    font-size:13px;
    color:var(--grey);
    margin:0 0 20px;
  }

  /* Watch tabs — underline indicator, not pills */
  .tabs{
    display:flex;
    gap:18px;
    flex-wrap:nowrap;
    overflow-x:auto;
    overflow-y:hidden;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
    /* Matches the (lack of) margin-top the .section wrapper gives the other
       tabs' titles, so the watch names land on the same line as those
       headings — this used to be 8px on both sides, which combined with the
       clock box's own 10px margin-bottom read as a much bigger gap above
       the watch names than the 10px below them to the dial. */
    padding-top:0;
    /* No bottom margin: it would sit inside the header (see flow-root there)
       as a band of background below the rule, which scrolling content
       disappears behind early — reading as a gap under the line. The header
       now ends exactly at the rule, and the spacing below it comes from the
       content's own top margin, which scrolls away with it. */
    margin-bottom:0;
    border-bottom:1px solid var(--line);
    background:var(--bg);
    padding-bottom:10px;
  }
  .tabs::-webkit-scrollbar{display:none;}
  .tab{
    font-family:'Inter',sans-serif;
    /* matches .section-title so the watch name reads as this tab's heading */
    font-size:15px;
    font-weight:600;
    padding:0;
    border:none;
    background:transparent;
    color:var(--grey);
    cursor:pointer;
    flex-shrink:0;
    white-space:nowrap;
  }
  .tab.active{
    color:var(--accent);
  }
  /* The jump to the Collection tab's add form. Inherits .tab wholesale — no
     size or weight of its own — so it shares one baseline with the watch
     names instead of stretching the row's line box and lifting them. */
  .tab-add{
    color:var(--grey);
  }
  /* The glyph alone sits optically low against the caps beside it. */
  .tab-add-plus{
    position:relative;
    top:-1px;
  }
  /* Collection detail view: replaces the watch-tabs row with the watch's
     own full-width card (back button where the wind button sits on the
     list view's card), so it stays pinned under the clock like the tabs
     row does, instead of scrolling away with the rest of the detail page. */
  .collection-watch-bar{
    overflow:visible;
    padding-bottom:12px;
  }
  .collection-watch-bar .collection-card{
    width:100%;
  }
  /* Plays once when a watch's detail page opens — a small rise-and-settle so
     the page reads as sliding into place rather than snapping in instantly.
     Same start and end state as with no animation at all, just motion
     in between. */
  .collection-detail-enter{
    animation:collection-detail-enter 250ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  @keyframes collection-detail-enter{
    0%{transform:translateY(10px); opacity:0;}
    100%{transform:translateY(0); opacity:1;}
  }
  @media (prefers-reduced-motion: reduce){
    .collection-detail-enter{animation:none;}
  }

  /* Dial */
  .dial-wrap{
    display:flex;
    flex-direction:column;
    align-items:center;
    /* The figure is set at line-height 1, so its digits reach the very top of
       its box and crowd the rule above. This is that missing leading. */
    padding:10px 0 0;
  }
  .dial-figure{
    font-family:'Inter',sans-serif;
    font-size:24px;
    font-weight:600;
    letter-spacing:-0.5px;
    line-height:1;
  }
  .dial-unit{
    font-family:'Inter',sans-serif;
    font-size:11px;
    font-weight:500;
    color:var(--grey);
  }
  .dial-meta{
    margin-top:2px;
    text-align:center;
    font-size:11px;
    color:var(--grey);
    line-height:1.3;
  }
  .empty-dial{
    font-family:'Inter',sans-serif;
    font-size:14px;
    color:var(--grey);
    text-align:center;
    /* Same total vertical padding as before (32px), just split evenly so
       the text centers in the field's own height instead of sitting low
       in it. */
    padding:16px 10px;
  }

  /* Chart */
  .chart-box{
    margin:20px 0 4px;
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:20px;
    padding:16px 16px 8px;
    box-shadow:0 8px 24px -8px var(--shadow-b);
  }
  .chart-header{
    display:flex;
    justify-content:space-between;
    align-items:center;
    margin-bottom:8px;
    gap:8px;
  }
  .chart-label{
    font-size:11.5px;
    color:var(--grey);
  }
  .chart-zoom{
    display:flex;
    gap:4px;
    flex-shrink:0;
  }
  .tg-sens-slider{
    -webkit-appearance:none;
    appearance:none;
    width:100%;
    height:4px;
    border-radius:2px;
    background:var(--line);
    accent-color:var(--accent);
    outline:none;
  }
  .tg-sens-slider::-webkit-slider-thumb{
    -webkit-appearance:none;
    appearance:none;
    width:18px;
    height:18px;
    border-radius:50%;
    background:var(--accent);
    border:2px solid #fff;
    cursor:pointer;
    box-shadow:0 2px 6px var(--shadow-a);
  }
  .tg-sens-slider::-moz-range-thumb{
    width:18px;
    height:18px;
    border-radius:50%;
    background:var(--accent);
    border:2px solid #fff;
    cursor:pointer;
    box-shadow:0 2px 6px var(--shadow-a);
  }
  .zoom-btn{
    width:30px;
    height:30px;
    border-radius:50%;
    border:1px solid var(--line);
    background:var(--surface-2);
    color:var(--ink);
    font-size:17px;
    font-weight:600;
    line-height:1;
    padding:0;
    display:flex;
    align-items:center;
    justify-content:center;
  }
  .stepper-row{
    display:flex;
    align-items:center;
    gap:8px;
  }
  .stepper-row input[type="number"],
  .stepper-row input[type="text"]{
    flex:1;
    min-width:0;
    text-align:center;
    -moz-appearance:textfield;
  }
  .stepper-row input[type="number"]::-webkit-inner-spin-button,
  .stepper-row input[type="number"]::-webkit-outer-spin-button{
    -webkit-appearance:none;
    margin:0;
  }
  .stepper-row-field label{
    text-align:center;
  }
  .stepper-row .zoom-btn{
    flex-shrink:0;
  }
  .chart-bottom-row{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    gap:10px;
    margin-top:6px;
    padding-bottom:2px;
  }
  .chart-meta{
    font-family:'Inter',sans-serif;
    font-size:10.5px;
    color:var(--grey);
    margin-left:auto;
  }
  .chart-tooltip{
    font-family:'Inter',sans-serif;
    font-size:10.5px;
    color:var(--grey);
    white-space:nowrap;
  }
  .tg-stat-row{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    font-family:'Inter',sans-serif;
    font-size:13px;
    padding:5px 0;
    border-bottom:1px solid var(--line);
  }
  .tg-stat-row:last-of-type{border-bottom:none;}
  .tg-live-grid{
    display:flex;
    justify-content:space-around;
    margin-bottom:12px;
  }
  .tg-level-label{
    font-family:'Inter',sans-serif;
    font-size:11px;
    color:var(--grey);
    display:flex;
    justify-content:space-between;
    margin-bottom:5px;
  }
  .tg-level-track{
    position:relative;
    height:8px;
    background:var(--surface-2);
    border-radius:4px;
    overflow:visible;
    margin-bottom:14px;
  }
  .tg-level-fill{
    height:100%;
    width:0%;
    background:var(--good);
    border-radius:4px;
  }
  .tg-level-threshold{
    position:absolute;
    top:-3px;
    left:0%;
    width:2px;
    height:14px;
    background:var(--accent);
  }
  .tg-live-num{
    font-family:'Inter',sans-serif;
    font-size:20px;
    font-weight:600;
    text-align:center;
    font-variant-numeric:tabular-nums;
  }
  .tg-tick-dot{
    width:14px;
    height:14px;
    border-radius:50%;
    background:var(--line);
    margin:3px auto 0;
  }

  .bottom-tabs{
    position:fixed;
    /* Was 10px. Five equal columns with a label as long as "Timegrapher"
       genuinely doesn't leave much room to spare on a real phone — measured
       against the actual rendered font, not estimated, every extra pixel
       reclaimed here goes straight into the pill actually looking like a
       pill instead of a label with barely any margin around it. */
    left:8px;
    right:8px;
    bottom:calc(14px + var(--safe-bottom));
    max-width:444px;
    margin:0 auto;
    z-index:30;
    display:flex;
    background:rgba(255,255,255,0.08);
    -webkit-backdrop-filter:blur(20px) saturate(180%);
    backdrop-filter:blur(20px) saturate(180%);
    border:1px solid var(--line);
    border-radius:999px;
    padding:6px 5px;
    box-shadow:0 12px 30px -10px var(--shadow-c);
    -webkit-transform:translateZ(0);
    transform:translateZ(0);
    will-change:transform;
  }
  .bottom-tab{
    /* Equal-width tap targets — five even columns across the bar. The
       active highlight below no longer has to fill this whole column, so
       widening it here never makes one tab's target bigger than another's. */
    flex:1;
    /* A flex item's default minimum width is its content's natural size, not
       zero — so a fixed-width child taller than the item's own flex:1 share
       stopped the item shrinking to fit, which stopped the whole row
       shrinking to fit, which pushed the fifth tab out past the bar's own
       background. This is what actually let that happen. */
    min-width:0;
    display:flex;
    justify-content:center;
    background:none;
    border:none;
    padding:0;
    font-family:'Inter',sans-serif;
    /* Was 9.5px. "Timegrapher" measured 58px wide at that size against a
       real column of only 68-79px on an actual phone — there was nowhere
       for padding to come from. Measured again at 8px: 49px, which is what
       finally leaves the pill genuine room on every side rather than a
       fraction of a pixel of it. */
    font-size:8px;
    font-weight:500;
    color:var(--grey);
  }
  .bottom-tab.active{
    color:var(--accent);
  }
  /* The actual pill: sized to its own icon+label, not to the button's equal
     flex share. A fully round 999px radius — right for a stadium shape —
     only reads as a pill when the box is snug around its content; stretched
     out to fill a whole equal-width column it flattens into a rounded
     rectangle, and on a long label like "Timegrapher" the curve pinches
     straight into the text. Sizing to content is also what lets that same
     999px radius stay genuinely round for every label, short or long,
     without the fixed columns around it ever changing width. */
  .bottom-tab-pill{
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:0px;
    padding:7px 8px 6px;
    border-radius:999px;
    /* One shared size, not one per label. Sized for "Timegrapher", the
       longest — align-items:center then centers every shorter label inside
       the same box, so the highlight lands in the same place and at the
       same size switching to any tab, rather than visibly resizing itself
       tab to tab.
       max-width:100% is doing real work here, not just a safety net: with
       five tabs actually on the bar (Profile included — easy to miss if it
       was never actually rendered while testing this) even a phone-sized
       screen doesn't always have 84px of column to spare per tab. Where it
       doesn't, this caps the pill at whatever its own column actually has,
       so it still lands the same size on every tab that fits it — it's only
       the one fixed number that stops being universal, on the narrowest
       phones. */
    width:84px;
    max-width:100%;
  }
  .bottom-tab.active .bottom-tab-pill{
    background:rgba(255,255,255,0.14);
    box-shadow:inset 0 1px 0 rgba(255,255,255,0.2);
  }
  .bottom-tab-icon{
    display:flex;
    align-items:center;
    justify-content:center;
    /* Springs back slower than it presses in, which is what makes a button
       feel physical rather than just rescaled. */
    transition:transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
  }
  .bottom-tab-icon svg{
    width:23px;
    height:23px;
  }
  .bottom-tab:active .bottom-tab-icon, .bottom-tab.pressed .bottom-tab-icon{
    transform:scale(0.84);
    transition-duration:90ms;
  }
  @media (prefers-reduced-motion: reduce){
    .bottom-tab-icon,
    .bottom-tab:active .bottom-tab-icon,
    .bottom-tab.pressed .bottom-tab-icon{transition:none; transform:none;}
  }

  .analog-clock-wrap{
    display:flex;
    justify-content:center;
    padding:24px 10px;
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:24px;
    box-shadow:0 8px 24px -8px var(--shadow-b);
  }
  svg.analog-clock{
    width:100%;
    max-width:320px;
    height:auto;
    display:block;
  }
  .chart-scroll{
    overflow-x:auto;
    overflow-y:hidden;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
    -ms-overflow-style:none;
  }
  .chart-scroll::-webkit-scrollbar{
    display:none;
  }
  .chart-container{
    position:relative;
  }
  .chart-yaxis{
    position:absolute;
    top:0;
    left:0;
    background:var(--surface);
    pointer-events:none;
    z-index:2;
  }
  .yaxis-tick{
    position:absolute;
    left:0;
    right:6px;
    text-align:right;
    font-family:'Inter',sans-serif;
    font-size:9px;
    color:#A1A1AA;
    transform:translateY(-50%);
  }
  svg.chart{
    display:block;
  }
  .chart-dot{
    cursor:pointer;
  }
  .chart-dot circle{
    -webkit-tap-highlight-color:transparent;
  }

  /* Section */
  .section{
    margin-top:23px;
    padding-top:18px;
    border-top:1px solid var(--line);
  }
  .section-title{
    font-family:'Inter',sans-serif;
    font-size:15px;
    font-weight:600;
    margin:0 0 12px;
  }

  /* Form */
  form{
    display:flex;
    flex-direction:column;
    gap:12px;
  }
  .field{
    display:flex;
    flex-direction:column;
    gap:5px;
  }
  .quick-log-box > .field + .field,
  .history-edit-row > .field + .field{
    margin-top:12px;
  }
  .field label{
    font-size:11.5px;
    color:var(--grey);
  }
  .cert-checkbox-list{
    display:flex;
    flex-direction:column;
    gap:8px;
    padding:12px 13px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--surface-2);
    max-height:220px;
    overflow-y:auto;
  }
  .cert-checkbox{
    display:flex;
    align-items:center;
    gap:9px;
    font-size:13.5px;
    color:var(--ink);
  }
  .cert-checkbox input[type="checkbox"]{
    width:16px;
    height:16px;
    accent-color:var(--accent);
    flex-shrink:0;
  }
  .row2{
    display:flex;
    align-items:flex-start;
    gap:10px;
  }
  .row2 .field{flex:1;min-width:0;}
  .row3{
    display:flex;
    gap:8px;
  }
  .row3 .field{flex:1;min-width:0;}
  .row3 .condition-select{
    font-size:12px;
    padding:10px 8px;
  }
  .price-currency-row{
    display:flex;
    gap:6px;
    align-items:stretch;
  }
  .price-currency-row input[type="number"]{flex:1;min-width:0;}
  .price-currency-row .select-wrap{flex:1;min-width:0;}
  .price-currency-row .condition-select{
    font-size:12px;
    padding:11px 8px;
    height:100%;
  }
  input[type="date"]{
    font-family:'Inter',sans-serif;
    font-size:14px;
    padding:11px 13px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--surface-2);
    color:var(--ink);
    width:100%;
    min-width:0;
    min-height:44px;
    box-sizing:border-box;
    overflow:hidden;
    -webkit-appearance:none;
    appearance:none;
  }
  /* Just the calendar icon: iOS/Safari draw it dark-on-dark by default,
     which is what made the field look broken/empty in the first place.
     Inverting only the icon (not the whole control) keeps it visible
     without needing -webkit-appearance left at its native value, which is
     what caused the oversized pill rendering on iOS specifically. */
  input[type="date"]::-webkit-calendar-picker-indicator{
    filter:invert(1);
  }
  input[type="number"], input[type="text"], input[type="email"], input[type="password"], input[type="tel"]{
    font-family:'Inter',sans-serif;
    font-size:14px;
    padding:11px 13px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--surface-2);
    color:var(--ink);
    width:100%;
    box-sizing:border-box;
    -webkit-appearance:none;
    appearance:none;
  }
  .select-wrap{position:relative;}
  .condition-select{
    font-family:'Inter',sans-serif;
    font-size:13px;
    padding:11px 13px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--surface-2);
    color:var(--ink);
    width:100%;
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:10px;
    text-align:left;
  }
  .select-value{
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
    min-width:0;
  }
  .condition-select.placeholder .select-value{color:var(--grey);}
  .select-chevron{
    flex-shrink:0;
    color:var(--grey);
  }
  .select-menu{
    position:absolute;
    top:calc(100% + 6px);
    left:0;
    right:0;
    z-index:60;
    padding:6px;
    border:1px solid var(--line);
    border-radius:12px;
    background:var(--surface-2);
    box-shadow:0 14px 32px -10px var(--shadow-e);
    /* Capped and scrollable rather than growing to fit every option — a
       short list (Position, Wear state) still looks exactly as before,
       but a long one (currencies) scrolls in place instead of needing so
       much room that it triggers the drop-up fallback below. */
    max-height:220px;
    overflow-y:auto;
  }
  .select-menu.drop-up{
    top:auto;
    bottom:calc(100% + 6px);
  }
  .select-option{
    display:block;
    width:100%;
    text-align:left;
    padding:10px 12px;
    border:none;
    border-radius:8px;
    background:transparent;
    color:var(--ink);
    font-size:13px;
  }
  .select-option.selected{
    color:var(--accent);
    background:rgba(59,130,246,0.12);
    font-weight:600;
  }
  .select-option:active{background:rgba(255,255,255,0.06);}
  .condition-select:focus{
    outline:2px solid var(--accent);
    outline-offset:1px;
  }
  input:focus{
    outline:2px solid var(--accent);
    outline-offset:1px;
  }
  .hint{
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey);
    margin-top:-4px;
  }
  button{
    font-family:'Inter',sans-serif;
    cursor:pointer;
    -webkit-appearance:none;
    appearance:none;
    -webkit-tap-highlight-color:transparent;
  }
  .btn-primary{
    margin-top:4px;
    background:var(--accent);
    color:#fff;
    border:none;
    border-radius:999px;
    padding:13px;
    font-size:14px;
    font-weight:600;
  }
  .btn-primary:active{background:#2563EB;}
  .btn-primary:disabled{opacity:0.5;}
  .btn-secondary{
    flex:1;
    background:var(--surface-2);
    color:var(--ink);
    border:1px solid var(--line);
    border-radius:999px;
    padding:13px;
    font-size:13px;
    font-weight:500;
  }

  /* Covers the notch strip so content scrolling underneath isn't visible
     behind the translucent status bar. Invisible at rest. */
  body::before{
    content:'';
    position:fixed;
    top:0;
    left:0;
    right:0;
    height:var(--safe-top);
    background:var(--bg);
    z-index:25;
    pointer-events:none;
  }
  #stickyHeader{
    position:sticky;
    /* Sticks below the notch rather than padding itself past it — the page's
       own top padding already clears the notch at rest. */
    top:var(--safe-top);
    z-index:20;
    background:var(--bg);
    padding-top:14px;
    /* Contains the last child's bottom margin. Without it that margin
       collapses out through the header's bottom edge, leaving an 8px strip
       below the header that the header's own background doesn't paint — so
       scrolling content showed through it as a sliver above the first card. */
    display:flow-root;
    /* This box's height is animated on every scroll-driven rAF frame (see
       clockCollapseLoop in clock.js). Without this, the browser's scroll
       anchoring "corrects" scrollY to compensate for that self-inflicted
       height change, which shifts the collapse sentinel, which changes the
       height again — a feedback loop that looks like the clock jittering. */
    overflow-anchor:none;
  }
  .master-clock-box{
    background:var(--hero-bg);
    color:#fff;
    border-radius:20px;
    padding:20px 22px 6px;
    /* Constant, not part of the collapse animation — it's the gap above the
       watch-tab row, which must stay equal to the gap below it. */
    margin-bottom:10px;
    /* Fixed, not var(--line): this box stays dark in both themes (see the
       note on --hero-bg above), so a theme-reactive border would turn
       light-grey in light mode and read as a bright ring around an
       otherwise-dark box — wrong regardless of the surrounding page. */
    border:1px solid rgba(255,255,255,0.08);
    /* Theme-reactive, unlike the box's own fixed-dark background/border
       above: a shadow tuned for a dark page reads as a harsh black smudge
       on a light one, especially where it fades across the tab row right
       below it. The box stays a "dark display" either way; the shadow it
       casts onto the page around it shouldn't. */
    box-shadow:0 10px 30px -10px var(--shadow-d);
    cursor:pointer;
    position:relative;
  }
  .master-clock-box.ticking{
    border-color:var(--accent);
  }
  .master-clock-label{
    position:absolute;
    top:8px;
    left:0;
    right:0;
    font-family:'Inter',sans-serif;
    font-size:11px;
    color:#A1A1AA;
    text-align:center;
  }
  .master-clock{
    font-family:'Inter',sans-serif;
    font-size:56px;
    font-weight:600;
    /* letter-spacing has no effect here — see the note on .clock-digit
       below, which does the equivalent job instead. */
    /* No tabular-nums here on purpose: Inter's tabular "1" carries a flat
       base serif to hold its column width even with other digits, which
       reads as a different typeface next to the plain "1" the rest of the
       app uses. Ticking every second can shift the digits' width by a
       hair as a result, but that's the tradeoff for matching the page. */
    display:block;
    flex:1;
    text-align:center;
    line-height:1;
  }
  /* Sized in em so it scales with the collapse animation's own font-size
     shrink — 0.7em comfortably fits the widest digit ("4"/"0") at this
     weight without needing to change alongside it. Being inline-block
     makes the parent's letter-spacing a no-op between these boxes (Chrome
     doesn't apply it at an inline-block boundary), so the negative margin
     here does that job instead — pulling the boxes' fixed-width padding
     back in without touching the actual digit widths. */
  .clock-digit{
    display:inline-block;
    width:0.7em;
    margin-right:-2px;
    text-align:center;
  }

  .quick-log-box{
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:20px;
    padding:16px;
    margin-bottom:14px;
    box-shadow:0 8px 24px -8px var(--shadow-b);
  }
  /* The snap trigger: fixed above the bottom dock on the Snap tab so it's
     always reachable without scrolling. Same "clear the dock" offset the
     toast (below) already uses: the dock's own 14px + 57px + 10px of
     breathing room. Its own content is constant (see buildSnapTriggerHtml),
     so unlike the old fixed panel this never needs to grow — the confirm
     and manual forms open against the selected watch's card instead. */
  #snapDock{
    position:fixed;
    left:8px;
    right:8px;
    bottom:calc(81px + var(--safe-bottom));
    max-width:444px;
    margin:0 auto;
    z-index:29;
  }
  #snapDock .quick-log-box{
    margin-bottom:0;
    background:var(--accent);
    border-color:var(--accent);
  }
  #snapDock .quick-log-box .hint{
    color:rgba(255,255,255,0.85);
  }
  #snapDock .manual-link{
    color:#fff;
  }
  /* Camera-flash on the panel that opens when a reading is captured. Two
     layers on one overlay: fully white light blown in from the edges, which
     carries the effect, over a faint white wash across the whole rectangle
     that lifts it without washing out the content. An overlay rather than a
     filter on the panel itself, so it fades independently of the panel's own
     shadow — and pointer-events:none so it never swallows a tap on the
     fields underneath while it's still fading. */
  .snap-flash{position:relative;}
  .snap-flash::after{
    content:'';
    position:absolute;
    inset:0;
    border-radius:inherit;
    pointer-events:none;
    background:rgba(255,255,255,0.15);
    /* The first shadow has no blur: a hard 2px ring that is literally 100%
       white at the edge. Blurred shadows never reach full white however
       bright you make them — the blur averages them against what's behind.
       The other two feather that ring inwards so it reads as light spilling
       in rather than as a drawn border. */
    box-shadow:
      inset 0 0 0 2px #fff,
      inset 0 0 16px 0 rgba(255,255,255,0.95),
      inset 0 0 42px 6px rgba(255,255,255,0.7);
    animation:snap-flash 1.35s ease-out forwards;
  }
  /* The shape of a real flash firing: 0.1s ramp to fully white (7.4% of
     1.35s), held there to 0.25s (18.5%), then a one-second fall. */
  @keyframes snap-flash{
    0%{opacity:0;}
    7.4%{opacity:1;}
    18.5%{opacity:1;}
    100%{opacity:0;}
  }
  @media (prefers-reduced-motion: reduce){
    .snap-flash::after{animation:none; opacity:0;}
  }
  .quick-log-box .hint{margin-top:0;margin-bottom:12px;text-align:center;}
  .quick-btns{
    display:grid;
    grid-template-columns:repeat(4,1fr);
    gap:8px;
  }
  .quick-btn{
    background:var(--surface-2);
    border:1px solid var(--line);
    color:var(--accent);
    border-radius:16px;
    padding:16px 0;
    font-family:'Inter',sans-serif;
    font-size:16px;
    font-weight:600;
  }
  .quick-btn:active{background:var(--accent);color:#fff;border-color:var(--accent);}
  /* On the blue snap-menu background, the buttons' own accent-blue numbers
     would barely show — white keeps them readable against it. */
  #snapDock .quick-btn{color:#fff;}
  .manual-link{
    background:none;
    border:none;
    color:var(--grey);
    text-decoration:underline;
    font-size:11.5px;
    padding:0;
    margin:12px auto 0;
    display:block;
  }
  .confirm-time{
    font-family:'Inter',sans-serif;
    font-size:26px;
    font-weight:600;
    text-align:center;
    /* Coloured by which side of true time the watch is on, the same green
       and red the rates use elsewhere — which also sets this frozen reading
       apart from the live white clocks around it. */
    color:var(--accent);
    margin-bottom:4px;
    /* Plays once when the panel appears — the timestamp snaps down onto the
       panel like a shutter closing on the moment it just froze. Ease-out
       expo: almost all of the travel happens in the first third, so it
       reads as a snap landing rather than a slow zoom. */
    animation:confirm-snap 280ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  @keyframes confirm-snap{
    0%{transform:scale(1.5); opacity:0;}
    45%{opacity:1;}
    100%{transform:scale(1); opacity:1;}
  }
  @media (prefers-reduced-motion: reduce){
    .confirm-time{animation:none;}
  }
  .confirm-time.ahead,
  .confirm-offset.ahead{color:var(--good);}
  .confirm-time.behind,
  .confirm-offset.behind{color:var(--bad);}
  .confirm-offset{font-weight:600;}
  .confirm-sub{
    font-family:'Inter',sans-serif;
    text-align:center;
    font-size:11.5px;
    color:var(--grey);
    padding-bottom:12px;
    margin-bottom:12px;
    /* Separates the frozen reading from the fields below it — the panel
       reads as "here's what got captured" over "here's what to fill in",
       rather than one undifferentiated block. */
    border-bottom:1px solid var(--line);
  }
  /* The timestamp itself carries the information; "captured … " is just the
     sentence around it, so only the figure is lifted out. */
  .confirm-sub b{
    color:var(--ink);
    font-weight:600;
    font-variant-numeric:tabular-nums;
  }
  /* A pill input with a small icon standing in for a visible label — this
     field is optional and low-stakes, so the placeholder alone carries
     enough meaning that a "Note (optional)" label above it was just extra
     height for no extra clarity. */
  .note-input-wrap{
    position:relative;
    display:flex;
    align-items:center;
  }
  .note-input-icon{
    position:absolute;
    left:13px;
    color:var(--grey);
    pointer-events:none;
  }
  .note-input{
    padding-left:36px !important;
  }

  /* History */
  .history-wrap{
    position:relative;
  }
  .history-scroll{
    max-height:340px;
    overflow-y:auto;
    overflow-x:hidden;
    -webkit-overflow-scrolling:touch;
    padding-right:14px;
    scrollbar-width:none;
    -ms-overflow-style:none;
  }
  /* While a row is open for editing the list grows to fit it, so the form is
     never trapped inside a scrolling box — the page scrolls instead. */
  .history-scroll.editing{
    max-height:none;
    overflow:visible;
  }
  .history-scroll.editing + .history-scrollbar-track{
    display:none;
  }
  .history-scroll::-webkit-scrollbar{
    display:none;
  }
  .custom-scrollbar-track{
    position:relative;
    background:rgba(255,255,255,0.08);
    border-radius:2px;
  }
  .history-scrollbar-track{
    position:absolute;
    top:0;
    right:0;
    width:4px;
    /* follows the list's height rather than repeating its max-height */
    height:100%;
    pointer-events:none;
  }
  .custom-scrollbar-thumb{
    position:absolute;
    background:var(--grey);
    border-radius:2px;
    pointer-events:auto;
    cursor:grab;
    touch-action:none;
  }
  .custom-scrollbar-thumb:active{
    cursor:grabbing;
    background:var(--ink);
  }
  .history-scrollbar-thumb{
    left:0;
    width:4px;
  }
  .custom-scrollbar-thumb.horizontal{
    top:0;
    height:4px;
  }
  .custom-scrollbar-track:not(.history-scrollbar-track){
    height:4px;
    margin-top:6px;
  }
  .history-item{
    display:block;
    padding:12px 0;
    border-bottom:1px solid var(--line);
    font-size:13px;
    cursor:pointer;
  }
  .hist-main{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    gap:12px;
  }
  /* Each row is now its own .swipe-row (see the History markup in app.js),
     so .history-item is never actually the scroll list's last child even
     when its row is — the border has to come off based on the row instead. */
  .swipe-row:last-child .history-item{border-bottom:none;}
  /* .history-scroll reserves 14px on the right for its scrollbar thumb
     (below), which used to leave every row's own border-bottom — and the
     red delete panel — stopping 14px short of the width every other section
     on the page uses. Bleeding the row out by that same 14px and pushing it
     back in as padding on the sliding card keeps the border/panel flush
     with everything else while still keeping the row's text clear of the
     thumb. */
  .history-swipe-row{
    margin-right:-14px;
  }
  /* Slides left off the delete panel underneath it, the same swipe-to-delete
     interaction as the Collection tab's cards (see wireCollectionSwipe in
     collection.js, reused here for history rows too). */
  .swipe-row .history-item{
    position:relative;
    z-index:1;
    padding-right:14px;
    background:var(--bg);
    touch-action:pan-y;
    transition:transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
    -webkit-user-select:none;
    user-select:none;
  }
  .history-item.history-edit-row{
    display:block;
    cursor:default;
    padding:12px 0;
  }
  .reset-check-row{
    display:flex;
    align-items:flex-start;
    gap:8px;
    margin-top:10px;
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey);
    cursor:pointer;
  }
  .reset-check-row input{
    width:auto;
    margin-top:2px;
    flex-shrink:0;
  }
  .hist-date{
    font-family:'Inter',sans-serif;
    color:var(--grey);
    font-size:12px;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  }
  .hist-rate{
    font-family:'Inter',sans-serif;
    font-weight:600;
    font-size:13px;
    white-space:nowrap;
    flex-shrink:0;
  }
  .hist-rate.fast{color:var(--bad);}
  .hist-rate.slow{color:var(--good);}
  .hist-note{
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey);
    margin-top:3px;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  }
  .empty-note{
    font-family:'Inter',sans-serif;
    font-size:13px;
    color:var(--grey);
    padding:6px 0 0;
  }
  .footer-row{
    margin-top:30px;
    display:flex;
    justify-content:space-between;
    align-items:center;
  }
  .reset-link{
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey);
    background:none;
    border:none;
    text-decoration:underline;
    padding:0;
  }
  /* The back link sits where a heading would and reads as this view's first
     line, so it takes .section-title's size and weight. It keeps the grey of
     a secondary control rather than the ink of a real title — it's a way out
     of the view, not a name for it. */
  .reset-link.back-link{
    font-size:15px;
    font-weight:600;
  }
  .status{
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey);
  }
  .status.err{color:var(--bad);}

  /* --- auth screen (login/signup) --- */
  #authScreen{
    display:flex;
    align-items:center;
    justify-content:center;
    min-height:100vh;
    padding:24px;
  }
  .auth-box{
    width:100%;
    max-width:340px;
    text-align:center;
  }
  .auth-box h1{
    font-family:'Inter',sans-serif;
    font-weight:900;
    font-size:22px;
    color:var(--ink);
    margin:0 0 6px;
  }
  .auth-box p{
    font-family:'Inter',sans-serif;
    font-size:13px;
    color:var(--grey);
    margin:0 0 20px;
  }
  .auth-box .field{
    text-align:left;
    margin-bottom:14px;
  }
  .auth-box .btn-primary{
    width:100%;
  }
  .auth-status{
    font-family:'Inter',sans-serif;
    font-size:12px;
    color:var(--grey);
    margin-top:12px;
    min-height:16px;
  }
  .auth-toggle-link{
    display:block;
    width:100%;
    background:none;
    border:none;
    font-family:'Inter',sans-serif;
    font-size:12px;
    color:var(--accent);
    padding:10px 0 0;
    cursor:pointer;
  }

  /* --- sign out button, tucked into the title block --- */
  .signout-btn{
    font-family:'Inter',sans-serif;
    font-size:11px;
    color:var(--grey);
    background:none;
    border:1px solid var(--line);
    border-radius:999px;
    padding:4px 12px;
    margin-top:6px;
    cursor:pointer;
  }
  .signout-btn:active{color:var(--ink);}

  /* --- Collection tab --- */
  .collection-list{
    display:flex;
    flex-direction:column;
    gap:12px;
  }
  .collection-add-btn{
    display:block;
    width:100%;
    background:none;
    border:1px dashed var(--line);
    border-radius:18px;
    padding:16px;
    font-family:'Inter',sans-serif;
    font-size:14px;
    font-weight:500;
    color:var(--accent);
  }
  /* The Snap tab's own "+ Add watch", styled quieter than the Collection
     tab's — it's a secondary way in from here, not this page's main action. */
  .data-add-watch-btn{
    color:var(--grey);
  }
  .collection-card{
    position:relative;
    display:flex;
    align-items:center;
    gap:14px;
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:18px;
    padding:12px;
    cursor:pointer;
  }

  /* Year-at-a-glance wear tracker on the Collection detail page: one real,
     weekday-aligned month at a time, swiped or arrow-stepped between (see
     attachWearCalendarHandlers in collection.js). scroll-snap plus native
     touch scrolling gives the swipe its own smoothness for free; the nav
     buttons drive the same scroller with scrollTo({behavior:'smooth'}). */
  .wear-calendar-controls{
    display:flex;
    align-items:center;
    gap:6px;
    margin-bottom:14px;
  }
  .wear-nav-btn{
    flex-shrink:0;
  }
  .wear-calendar-select-slot{
    flex:1;
    min-width:0;
  }
  .wear-calendar-select-slot .select-wrap{
    width:100%;
  }
  .wear-calendar-select-slot .condition-select{
    width:100%;
  }
  .wear-calendar-wrap{
    display:flex;
    flex-direction:column;
    align-items:center;
  }
  .wear-month-page{
    display:flex;
    flex-direction:column;
    align-items:center;
    width:100%;
  }
  .wear-weekday-row{
    display:grid;
    grid-template-columns:repeat(7, 1fr);
    width:100%;
    max-width:280px;
    margin-bottom:6px;
  }
  .wear-weekday-row span{
    font-family:'Inter',sans-serif;
    font-size:10px;
    color:var(--grey);
    text-align:center;
  }
  .wear-days-grid{
    display:grid;
    grid-template-columns:repeat(7, 1fr);
    gap:5px;
    width:100%;
    max-width:280px;
  }
  .wear-day{
    aspect-ratio:1;
    width:100%;
    padding:0;
    border:none;
    border-radius:8px;
    background:var(--surface-2);
    color:var(--grey);
    font-family:'Inter',sans-serif;
    font-size:11px;
    font-weight:500;
    display:flex;
    align-items:center;
    justify-content:center;
    cursor:pointer;
  }
  .wear-day.filled{
    background:var(--accent);
    color:#fff;
  }
  .wear-day.future{
    opacity:0.35;
    cursor:default;
  }
  .collection-card-actions{
    flex-shrink:0;
    align-self:center;
    display:flex;
    flex-direction:column;
    gap:6px;
  }

  /* Confirms a wind: a band of green travelling across the card, left to
     right, under a green rim that lights and fades. No white flash here —
     that one belongs to the capture snapshot, and this is a different act.
     Both layers are pointer-events:none so neither eats a tap mid-animation. */
  .collection-card.wound-flash::before,
  .collection-card.wound-flash::after{
    content:'';
    position:absolute;
    inset:0;
    border-radius:inherit;
    pointer-events:none;
    z-index:2;
  }
  .collection-card.wound-flash::before{
    /* The band sits at the middle of an image three times the card's width,
       so sliding the background from its right third to its left carries the
       band across from left to right. */
    background-image:linear-gradient(90deg,
      rgba(34,197,94,0) 30%,
      rgba(34,197,94,0.26) 44%,
      rgba(34,197,94,0.50) 50%,
      rgba(34,197,94,0.26) 56%,
      rgba(34,197,94,0) 70%);
    background-size:300% 100%;
    background-repeat:no-repeat;
    animation:wound-sweep 850ms ease-out forwards;
  }
  .collection-card.wound-flash::after{
    box-shadow:
      inset 0 0 0 2px var(--good),
      inset 0 0 16px 0 rgba(34,197,94,0.85),
      inset 0 0 42px 6px rgba(34,197,94,0.5);
    animation:wound-glow 850ms ease-out forwards;
  }
  @keyframes wound-sweep{
    0%{background-position:100% 0; opacity:1;}
    80%{opacity:1;}
    100%{background-position:0% 0; opacity:0;}
  }
  @keyframes wound-glow{
    0%{opacity:0;}
    12%{opacity:1;}
    40%{opacity:1;}
    100%{opacity:0;}
  }
  @media (prefers-reduced-motion: reduce){
    .collection-card.wound-flash::before,
    .collection-card.wound-flash::after{animation:none; opacity:0;}
  }

  /* Swipe to delete. The delete panel is a fixed layer underneath; the card
     is opaque and slides left off it. */
  .swipe-row{
    position:relative;
    border-radius:18px;
    overflow:hidden;
  }
  /* Spans the whole row, not just the strip the swipe reveals — the card's
     own 18px corner arc leaves a notch at each end, and with the panel
     starting exactly at the card's edge that notch had nothing behind it and
     read as a dark gap. Underlapping the card fills it with red instead. */
  .swipe-delete{
    position:absolute;
    inset:0;
    display:flex;
    align-items:center;
    justify-content:flex-end;
    background:var(--bad);
    border-radius:18px;
    /* Hidden until a swipe actually starts. opacity alone still paints this
       layer, and antialiasing along its rounded corner can then let a sliver
       of red bleed through the card's own rounded clip at rest — visibility
       removes it from painting entirely instead of just making it
       transparent, which a mere opacity of 0 doesn't. The transition is
       delayed on the way out so closing still fades rather than snapping. */
    opacity:0;
    visibility:hidden;
    pointer-events:none;
    transition:opacity 140ms linear, visibility 0s linear 140ms;
  }
  .swipe-row.swiping .swipe-delete,
  .swipe-row.open .swipe-delete{
    opacity:1;
    visibility:visible;
    pointer-events:auto;
    transition:opacity 140ms linear;
  }
  /* The icon grows into place as the panel is revealed, so the panel doesn't
     just appear fully formed the instant the card moves a pixel. */
  .swipe-delete-btn svg{
    transform:scale(0.55);
    opacity:0.7;
    transition:transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms linear;
  }
  .swipe-row.open .swipe-delete-btn svg{
    transform:scale(1);
    opacity:1;
  }
  .swipe-delete-btn{
    /* Only as wide as the strip the swipe actually reveals, so the tap target
       matches what you can see rather than the whole hidden panel. */
    width:84px;
    height:100%;
    border:none;
    background:none;
    color:#fff;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:0;
  }
  /* History's version of the same full-bleed red panel, just a bit narrower
     than the Collection tab's. */
  .history-swipe-row .swipe-delete-btn{
    width:70px;
  }
  /* .swipe-row's 18px corner radius (below) is sized for the Collection
     tab's much taller cards — on History's ~40px-tall rows that same radius
     eats a large share of the row's edge, both bending the divider line
     where it meets the rounded corner and letting a sliver of the red panel
     show through the curve. History's rows were always flat list items, not
     cards, so there's no shape to preserve here — just square them off. */
  .history-swipe-row{
    border-radius:0;
  }
  /* The red panel itself (not the row, which stays a flat rectangle so the
     divider lines are unaffected) rounds only its two right corners —
     matching the Collection tab's card shape there, at the row's own right
     edge, without rounding the left corners that sit inside the row. */
  .history-swipe-row .swipe-delete{
    border-radius:0 18px 18px 0;
  }
  .swipe-row .collection-card{
    position:relative;
    z-index:1;
    /* pan-y hands vertical scrolling back to the browser and keeps the
       horizontal movement for the swipe, so the gesture never fights the
       page and the listeners can stay passive. */
    touch-action:pan-y;
    transition:transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
    /* A press that turns into a swipe would otherwise drag-select the card's
       text, painting a blue selection band across it mid-gesture. */
    -webkit-user-select:none;
    user-select:none;
  }
  /* Bigger than the chart steppers it borrows its shape from — this one is a
     primary action on the card, not a nudge control tucked in a header. */
  .collection-wind-btn{
    width:36px;
    height:36px;
  }
  .collection-wind-btn svg{
    width:25px;
    height:25px;
  }
  .data-worn-btn{
    width:36px;
    height:36px;
  }
  .data-worn-btn svg{
    width:22px;
    height:22px;
  }
  .data-worn-btn.active{
    background:var(--accent);
    border-color:var(--accent);
    color:#fff;
  }
  .data-menu-btn{
    width:36px;
    height:36px;
  }
  .data-menu-btn svg{
    width:16px;
    height:16px;
  }
  /* Wind and worn-today sit side by side on the Data tab card, rather than
     stacked the way a single action (the wind button alone) sits on the
     Collection list's cards. */
  .data-watch-card-actions{
    flex-direction:row;
  }
  /* No model/reference line here (see buildDataWatchCardHtml), so the card
     doesn't need as much vertical room as the Collection list's. */
  .data-watch-card{
    padding-top:9px;
    padding-bottom:9px;
  }
  /* The plain "this is who a snap will apply to" state — no pop-up open,
     just the card's own stroke. */
  .data-watch-card.selected{
    border-color:var(--accent);
  }

  /* Once a snap is under way, the selected watch's card + its pop-up panel
     wrap as one connected, blue-stroked block (see buildDataWatchGroupHtml
     in app.js) — the border lives on the group, not the card, so the shared
     edge between the two reads as one continuous shape instead of two
     stacked boxes. */
  .data-watch-group{
    position:relative;
    border:1.5px solid var(--accent);
    border-radius:18px;
    overflow:hidden;
  }
  .data-watch-card.connected{
    border:none;
    border-radius:0;
  }
  .data-watch-snap-panel{
    border-top:1px solid var(--line);
    padding:14px;
    background:var(--surface);
  }
  .data-watch-snap-panel .quick-log-box{
    background:none;
    border:none;
    border-radius:0;
    padding:0;
    margin-bottom:0;
    box-shadow:none;
  }

  /* Power reserve */
  .reserve-row{
    display:flex;
    align-items:center;
    gap:8px;
    margin-top:6px;
  }
  .reserve-track{
    position:relative;
    flex:1;
    min-width:0;
    height:5px;
    border-radius:3px;
    background:var(--surface-2);
    overflow:hidden;
  }
  /* The low-amplitude tail, drawn under the fill so it only shows once the
     fill has retreated into it — a standing marker, not a warning. */
  .reserve-low-zone{
    position:absolute;
    left:0;
    top:0;
    bottom:0;
    background:rgba(248,113,113,0.22);
  }
  .reserve-fill{
    position:absolute;
    left:0;
    top:0;
    bottom:0;
    border-radius:3px;
    background:var(--good);
    /* Refills rather than jumping, so winding reads as the bar being filled
       up. The 30-second tick moves it by a hair, where this is invisible. */
    transition:width 650ms cubic-bezier(0.22, 1, 0.36, 1);
  }
  .reserve-fill.low{background:#FBBF24;}
  .reserve-fill.empty{background:transparent;}
  .reserve-label{
    flex-shrink:0;
    font-family:'Inter',sans-serif;
    font-size:10.5px;
    color:var(--grey);
    font-variant-numeric:tabular-nums;
  }
  .reserve-hint{color:var(--grey-light);}
  .reserve-label.low{color:#FBBF24;}
  .reserve-label.empty{color:var(--bad);}
  .collection-card-edit{
    flex-direction:column;
    align-items:stretch;
    gap:12px;
    cursor:default;
  }
  .collection-photo{
    width:56px;
    height:56px;
    border-radius:12px;
    object-fit:cover;
    flex-shrink:0;
    background:var(--surface-2);
  }
  .collection-photo-empty{
    display:flex;
    align-items:center;
    justify-content:center;
    color:var(--grey);
    font-size:20px;
  }
  .collection-card-body{flex:1;min-width:0;}
  .collection-card-name{
    font-family:'Inter',sans-serif;
    font-size:14px;
    font-weight:600;
    display:flex;
    align-items:baseline;
    min-width:0;
  }
  /* The name is the only part that gives way. It needs to be its own element
     to do that: ellipsis doesn't apply to a bare text node inside a flex
     container, so a long name would overflow instead of truncating. */
  .card-name-text{
    min-width:0;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  }
  .collection-card-value{
    font-family:'Inter',sans-serif;
    font-size:13px;
    color:var(--grey);
    margin-top:2px;
  }
  .collection-card-note{
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey-light);
    margin-top:2px;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  }
  /* Rate and spec sit inline after the name, small — the name can shrink
     and ellipsise, but these two never do: a truncated "-1.0 s/d…" would be
     worse than a shorter name. */
  .card-rate,
  .card-spec{
    font-family:'Inter',sans-serif;
    font-size:10.5px;
    font-weight:600;
    white-space:nowrap;
    flex-shrink:0;
    margin-left:7px;
  }
  .card-rate.good{color:var(--good);}
  .card-rate.bad{color:var(--bad);}
  .card-spec{
    font-weight:500;
    color:var(--accent);
  }

  /* Transient messages, floating clear of the layout so showing one never
     shifts anything. Sits just above the bottom dock: 14px gap below the
     dock + the dock's own 57px + 10px of clearance. */
  .toast{
    position:fixed;
    left:18px;
    right:18px;
    bottom:calc(81px + var(--safe-bottom));
    max-width:444px;
    margin:0 auto;
    z-index:40;
    padding:11px 14px;
    border-radius:14px;
    font-family:'Inter',sans-serif;
    font-size:12.5px;
    line-height:1.45;
    text-align:center;
    background:var(--surface-2);
    color:var(--ink);
    border:1px solid var(--line);
    box-shadow:0 12px 30px -10px var(--shadow-d);
    opacity:0;
    transform:translateY(8px);
    /* Never intercepts a tap, shown or not — it's a notice, not a control. */
    pointer-events:none;
    transition:opacity 200ms ease, transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
  }
  .toast.show{
    opacity:1;
    transform:none;
  }
  .toast-error{
    background:rgba(248,113,113,0.14);
    border-color:rgba(248,113,113,0.45);
    color:var(--bad);
  }

  /* Phone-sized screens: halve the vertical whitespace around the title so
     the clock starts higher. The notch inset itself is untouched — that's
     physical clearance, not padding. Trimmed a further 30% on top of that
     (7px/5px/4px/7px down to 5px/3px/3px/5px) so the capture panel below has
     enough headroom to appear without the page needing to scroll to reach
     it — see pinCapturePanel in app.js, which now only scrolls if it
     actually doesn't fit. */
  @media (max-width:480px){
    .app{padding-top:calc(var(--safe-top) + 5px);}
    .app-title{margin-top:3px;margin-bottom:3px;}
    .signout-btn{margin-top:3px;}
    #stickyHeader{padding-top:5px;}
  }


  /* --- profile tab: a normal tab now (see js/profile.js), not an overlay.
     Just a centered title convention and a small "back to menu" link for
     its sub-views (Account/FAQ/About aren't reachable from the bottom bar
     directly, so they still need a way back to the top-level menu). --- */
  .profile-centered-title{
    text-align:center;
  }
  .profile-back-btn{
    font-size:30px;
    width:36px;
    height:36px;
  }
  .profile-section-label{
    font-family:'Inter',sans-serif;
    font-size:11px;
    font-weight:700;
    text-transform:uppercase;
    letter-spacing:0.04em;
    color:var(--grey);
    margin:20px 0 10px;
  }
  .profile-section-label:first-of-type{
    margin-top:4px;
  }
  .profile-subtab-row{
    display:flex;
    gap:6px;
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:12px;
    padding:4px;
    margin-bottom:16px;
  }
  .profile-subtab-btn{
    flex:1;
    font-family:'Inter',sans-serif;
    font-size:13px;
    font-weight:500;
    color:var(--grey);
    background:none;
    border:none;
    border-radius:9px;
    padding:9px 6px;
  }
  .profile-subtab-btn.active{
    color:var(--ink);
    background:var(--surface-2);
  }
  .profile-field-row{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:10px;
  }
  .profile-field-view label{
    display:block;
    font-family:'Inter',sans-serif;
    font-size:11.5px;
    color:var(--grey);
    margin-bottom:3px;
  }
  .profile-field-value{
    font-family:'Inter',sans-serif;
    font-size:14px;
    color:var(--ink);
  }
  .profile-edit-icon-btn{
    flex-shrink:0;
    width:32px;
    height:32px;
    border-radius:50%;
    border:1px solid var(--line);
    background:var(--surface-2);
    color:var(--grey);
    display:flex;
    align-items:center;
    justify-content:center;
  }
  .profile-edit-icon-btn.active{
    color:var(--accent);
    border-color:var(--accent);
  }
  input[disabled]{
    opacity:0.6;
  }


  /* --- profile menu list + FAQ (see js/profile.js) --- */
  .profile-menu-list{
    display:flex;
    flex-direction:column;
    gap:8px;
  }
  .profile-theme-row{
    display:flex;
    align-items:center;
    justify-content:space-between;
    font-family:'Inter',sans-serif;
    font-size:14px;
    font-weight:500;
    color:var(--ink);
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:14px;
    padding:12px 16px;
  }
  .profile-switch{
    position:relative;
    display:inline-block;
    width:44px;
    height:26px;
    flex-shrink:0;
  }
  .profile-switch input{
    position:absolute;
    opacity:0;
    width:100%;
    height:100%;
    margin:0;
    cursor:pointer;
  }
  .profile-switch-track{
    position:absolute;
    inset:0;
    background:var(--surface-2);
    border:1px solid var(--line);
    border-radius:999px;
    transition:background 0.15s ease;
  }
  .profile-switch-thumb{
    position:absolute;
    top:2px;
    left:2px;
    width:20px;
    height:20px;
    border-radius:50%;
    background:var(--grey);
    transition:transform 0.15s ease, background 0.15s ease;
  }
  .profile-switch input:checked + .profile-switch-track{
    background:var(--accent);
    border-color:var(--accent);
  }
  .profile-switch input:checked + .profile-switch-track .profile-switch-thumb{
    transform:translateX(18px);
    background:#FFFFFF;
  }
  .profile-menu-item{
    display:flex;
    align-items:center;
    justify-content:space-between;
    width:100%;
    font-family:'Inter',sans-serif;
    font-size:14px;
    font-weight:500;
    color:var(--ink);
    background:var(--surface);
    border:1px solid var(--line);
    border-radius:14px;
    padding:14px 16px;
  }
  .profile-menu-chevron{
    color:var(--grey);
    font-size:16px;
  }
  .profile-faq-list{
    display:flex;
    flex-direction:column;
    gap:16px;
  }
  .profile-faq-q{
    font-family:'Inter',sans-serif;
    font-size:13.5px;
    font-weight:600;
    color:var(--ink);
    margin-bottom:4px;
  }
  .profile-faq-a{
    font-family:'Inter',sans-serif;
    font-size:12.5px;
    color:var(--grey);
    line-height:1.5;
  }
