  /* ============ design tokens - Apple dark system palette ============ */
  * { margin: 0; box-sizing: border-box; }
  :root {
    --bg: #07090f;
    --ink: rgba(255,255,255,.92); --ink-2: rgba(255,255,255,.55); --ink-3: rgba(255,255,255,.32);
    --glass: rgba(255,255,255,.04); --glass-2: rgba(255,255,255,.07);
    --hair: rgba(255,255,255,.09); --hair-2: rgba(255,255,255,.16);
    --accent: #0A84FF; --red: #FF453A; --green: #30D158;
    /* Apple system colors, fixed slot order (chart pair validated for CVD) */
    --c1:#0A84FF; --c2:#30D158; --c3:#FF9F0A; --c4:#BF5AF2;
    --c5:#FF375F; --c6:#64D2FF; --c7:#FFD60A; --c8:#5E5CE6;
    --ease: cubic-bezier(.22,1,.36,1);
  }
  /* light theme - toggled via #themetoggle (app.js), persisted to localStorage, applied as early
     as possible by an inline bootstrap script at the top of index.html so there's no flash of dark
     before this stylesheet's default takes hold. Every color pair below is Apple's own published
     light/dark equivalent for the same system color, keeping this consistent with the dark
     palette's own "Apple system palette" origin rather than inventing new hues. --glass/--glass-2
     flip from white-based to white-based-but-more-opaque (not black-based) - Apple's own light
     "materials" are still white/frosted cards sitting on a light gray page, not dark overlays. */
  :root[data-theme="light"] {
    /* a bit more gray than the first pass - --glass at 70-85% white was barely distinguishable
       from a page background this close to white, so cards read as flat grey instead of clearly
       elevated white cards. Widening the gap on both ends (a slightly darker page, a more opaque
       card) instead of moving just one, so neither pure white nor a much darker gray. */
    --bg: #e9ebf1;
    --ink: rgba(0,0,0,.88); --ink-2: rgba(0,0,0,.55); --ink-3: rgba(0,0,0,.35);
    --glass: rgba(255,255,255,.88); --glass-2: rgba(255,255,255,.97);
    --hair: rgba(0,0,0,.1); --hair-2: rgba(0,0,0,.18);
    --accent: #007AFF; --red: #FF3B30; --green: #34C759;
    --c1:#007AFF; --c2:#34C759; --c3:#FF9500; --c4:#AF52DE;
    --c5:#FF2D55; --c6:#32ADE6; --c7:#FFCC00; --c8:#5856D6;
    /* a handful of progress-bar/timeline TRACK backgrounds share this exact value - one token so
       they're consistent, not because every rgba(255,255,255,.07) in the file is this token
       (most aren't - see the contrast-pass note further down). */
    --track: rgba(0,0,0,.08);
  }
  html[data-theme="light"] { color-scheme: light; }
  /* Light-mode contrast pass (2026-09-01): these components were all written with a literal
     rgba(255,255,255,X) instead of a --token, so they stayed white-tinted regardless of theme -
     invisible or near-invisible against an already-light page (a translucent WHITE overlay on a
     background that's already close to white has almost no contrast to see). Each gets an
     explicit light-mode override here instead of being converted to a shared token, so dark
     mode's already-tuned exact values are untouched. This is a targeted pass on what was actually
     visibly broken (the clock, the ring track, the timeline, progress-bar tracks, the icon
     buttons) - not an exhaustive audit of every rgba(255,255,255,..) literal in this file. There
     are more like it on screens not checked yet; flagged in TODO.md. */
  :root[data-theme="light"] #clock {
    /* background-IMAGE specifically, not the background shorthand - the shorthand implicitly
       resets background-clip/background-size to their defaults too, which is exactly what broke
       this the first time (background-clip:text reverted to border-box, so the gradient painted
       as a solid box instead of clipping to the digits, and the now-invisible color:transparent
       text had nothing left to make it visible). */
    background-image: linear-gradient(110deg, #1a1a2e 15%, #2a3f8f 40%, #6a3a9e 60%, #b34a1e 78%, #1a1a2e 100%);
  }
  :root[data-theme="light"] #track { stroke: rgba(0,0,0,.09); }
  :root[data-theme="light"] #ticks { stroke: rgba(0,0,0,.18); }
  :root[data-theme="light"] #tlaxis { background: rgba(0,0,0,.08); }
  :root[data-theme="light"] #tline b { background: rgba(0,0,0,.5); box-shadow: 0 0 4px rgba(0,0,0,.3); }
  :root[data-theme="light"] #tlticks i { background: rgba(0,0,0,.16); }
  :root[data-theme="light"] #tlticks i.maj { background: rgba(0,0,0,.3); }
  :root[data-theme="light"] #tcbar, :root[data-theme="light"] #stbar,
  :root[data-theme="light"] #snbar, :root[data-theme="light"] #fsnbar,
  :root[data-theme="light"] #ftcbar, :root[data-theme="light"] .pbar,
  :root[data-theme="light"] .wppbar { background: var(--track); }
  :root[data-theme="light"] #tuthelp, :root[data-theme="light"] #adminbtn,
  :root[data-theme="light"] #installbtn, :root[data-theme="light"] #feedbackbtn,
  :root[data-theme="light"] #feedbackMailFallback, :root[data-theme="light"] #themetoggle {
    /* a near-white circle on an already near-white page has almost no color contrast to read by -
       a visible shadow gives it an edge to read against regardless of the page tint underneath */
    background: rgba(255,255,255,.95); border-color: rgba(0,0,0,.16);
    box-shadow: 0 1px 4px rgba(0,0,0,.12);
  }
  /* task/subtask checkboxes use appearance:none (fully custom circular check), so their border is
     the ONLY thing drawing the circle at all - a white one was completely invisible against a
     light page, not just faint. */
  :root[data-theme="light"] input[type=checkbox] { border-color: rgba(0,0,0,.3); }
  :root[data-theme="light"] input[type=checkbox]:hover { border-color: rgba(0,0,0,.55); }
  /* the previous pass's border, and then a soft shadow in its place, both still read as a visible
     "boxy" outline around every card/tile - dropping both entirely. The --bg/--glass gap above
     already gives cards a real, visible edge against the page on its own; no border or shadow
     line needed on top of it. */
  :root[data-theme="light"] .panel {
    border-color: transparent;
    box-shadow: none;
  }
  /* the ambient blobs and the vignette are both tuned for a near-black backdrop (soft glow, edge
     darkening) - at full strength on a light page they'd read as flat colored blotches and an
     odd dark smudge in the corners rather than atmosphere, so both get toned down a lot rather
     than removed outright. */
  :root[data-theme="light"] .blob { opacity: calc(.045 + var(--energy, 0) * .014); }
  :root[data-theme="light"] body::before { background: radial-gradient(ellipse at 50% 42%, transparent 55%, rgba(0,0,0,.06)); }
  html { color-scheme: dark;
    /* fluid UI scale: a straight vw/vh proportion (1rem=16px at the 2560x1440 reference display)
       has one fatal property - EVERY viewport below the point where 0.625vw/1.11111vh reaches 14px
       (roughly 2240x1260) computes to less than that, so a flat floor to keep things legible below
       that point makes every one of those screens - a 1244x846 tablet, a 1920x1080 monitor, a
       1366x768 laptop window, all wildly different real sizes - land on the exact same font-size.
       That's what broke the nav (vs #topRightBar) and #fmain's scale(1.3): components got tuned
       assuming "big enough to be off the floor" meant real room to spare, but a window barely
       above the floor and one deep in the floor look identical here, so there's no signal left to
       design against. Fixed by interpolating between two REAL anchor points instead of one flat
       floor - true "CSS locks" fluid typography, not a proportion: 10px at 1244x846 (this app's
       actual tablet) up to 16px at 2560x1440 (the original 0.625vw/1.11111vh reference target,
       unchanged). Everything in between - every common laptop width - gets its own graduated value
       instead of sharing the tablet's exact number; below 1244 it keeps gently decreasing rather
       than hitting a wall. min() of a width-term and a height-term still handles a viewport that's
       normal-width but short on room (a windowed, non-fullscreen browser losing space to its own
       chrome) by shrinking to fit. 8px is a backstop for near-zero widths, not a real target - the
       formula itself never gets that low at any width worth supporting. Ceiling unchanged at 24px
       for 4K+ displays beyond the reference. */
    font-size: clamp(8px, min(
      calc(10px + 6 * (100vw - 1244px) / 1316),
      calc(10px + 6 * (100vh - 846px) / 594)
    ), 24px);
  }
  body {
    height: 100vh; overflow: hidden; background: var(--bg); color: var(--ink);
    font-family: "Segoe UI Variable Display", "Segoe UI", system-ui, sans-serif;
    -webkit-font-smoothing: antialiased; user-select: none; -webkit-touch-callout: none;
    /* stops the browser's own edge-swipe back/forward-navigation gesture from competing with
       the task row's horizontal swipe-to-reveal - a different mechanism than touch-action,
       and the most likely cause of a swipe visibly starting then springing back uncommitted */
    overscroll-behavior-x: none;
  }
  body.idle { cursor: none; }
  body.idle #hint { opacity: 0; }
  input { user-select: text; }
  ::selection { background: rgba(10,132,255,.4); }
  ::-webkit-scrollbar { width: 8px; }
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 4px; }
  @media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

  /* ============ atmosphere ============ */
  /* energy meter: --energy (0-4, from days of hitting the Main 3) brightens and saturates the blobs, no new panel needed */
  .blob { position: fixed; width: 70vmax; height: 70vmax; border-radius: 50%; z-index: 0;
          opacity: calc(.11 + var(--energy, 0) * .032); filter: blur(150px) saturate(calc(1 + var(--energy, 0) * .18));
          transition: background 3.5s, opacity 2.1s, filter 2.1s; }
  /* ambient state hues: cooler while focusing, brief warm wash on completing the Main 3 */
  body.focusing .b1 { background: #2440d6; }
  body.focusing .b2 { background: #0a4a6e; }
  body.focusing .b3 { background: #3a1e6e; }
  body.celebrate .b1, body.celebrate .b2, body.celebrate .b3 { background: #c56a25; transition: background 0.7s; }
  .b1 { background: #3c3ec7; top: -30vmax; left: -18vmax; animation: d1 93.8s ease-in-out infinite alternate; }
  .b2 { background: #085a80; bottom: -35vmax; right: -18vmax; animation: d2 116.2s ease-in-out infinite alternate; }
  .b3 { background: #7a1b52; top: 45vh; left: 60vw; width: 42vmax; height: 42vmax; animation: d3 141.4s ease-in-out infinite alternate; }
  @keyframes d1 { to { transform: translate(26vmax, 16vmax); } }
  @keyframes d2 { to { transform: translate(-20vmax, -12vmax); } }
  @keyframes d3 { to { transform: translate(-28vmax, -18vmax) scale(1.25); } }
  body::before { /* vignette */
    content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
    background: radial-gradient(ellipse at 50% 42%, transparent 50%, rgba(0,0,0,.5));
  }
  body::after { /* film grain */
    content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .035;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  /* nav is a floating pill with a translucent glass background, not a full-width bar - scrolled
     content (a tall Tasks column, in particular) could slide up and show through/around it. This
     sits behind both nav (z-index 4) and #topRightBar (z-index 5) but above .view's scrolling
     content (z-index 1), fully opaque where they actually sit and fading out below that so it
     doesn't read as a hard-edged bar cutting across the page. */
  #topbarBg { position: fixed; top: 0; left: 0; right: 0; height: 5.5rem; z-index: 2;
              pointer-events: none;
              background: linear-gradient(to bottom, var(--bg) 0%, var(--bg) 65%, transparent 100%); }
  /* ============ segmented nav ============ */
  nav { position: fixed; top: 1.3rem; left: 50%; transform: translateX(-50%); z-index: 4;
        display: flex; padding: .1875rem; border-radius: 999px;
        background: rgba(255,255,255,.05); border: 1px solid var(--hair);
        backdrop-filter: blur(24px); transition: opacity 0.7s; }
  nav .thumb { position: absolute; top: .1875rem; bottom: .1875rem; border-radius: 999px;
               background: rgba(255,255,255,.12); box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
               transition: left 0.49s var(--ease), width 0.49s var(--ease); }
  nav button { position: relative; background: none; border: none; cursor: pointer;
               font: inherit; font-size: .84rem; font-weight: 500; color: var(--ink-2);
               padding: .42rem 1.15rem; border-radius: 999px; transition: color 0.42s; }
  nav button.on, nav button:hover { color: var(--ink); }
  /* countdown badge on the Focus tab: tabular-nums so digits never jitter mid-tick, and a reserved
     min-width so the nav only resizes once (on start/stop), not continuously while ticking */
  #navtime { display: inline-block; min-width: 0; font-variant-numeric: tabular-nums;
             color: var(--ink-2); opacity: .85; }
  #navtime:not(:empty) { margin-left: .5rem; min-width: 2.9em; }
  #navtime.brk { color: var(--green); }
  /* nav (centered) and #topRightBar (right-anchored) are two independent fixed-position
     elements with no collision-avoidance between them - fine while there's a wide gap, but
     the root font-size floor (see the clamp() above) means nav's pixel width stops shrinking
     with the viewport well before ~1280px, so a narrower window/tablet can close that gap
     entirely. Shrinking nav's own font/padding here (independent of the root clamp) and
     collapsing #acct's signed-out "Sign in with Google" text down to its icon - matching the
     already icon-only feedback/tutorial/admin buttons right next to it - are the two levers
     that actually control each side's width, so this is where the fix has to happen. */
  @media (max-width: 1280px) {
    nav button { font-size: .74rem; padding: .36rem .8rem; }
    #acct:not(.on) { width: 1.875rem; padding: 0; justify-content: center; }
    #acct:not(.on) #acctLabel { display: none; }
  }

  /* ============ views ============ */
  .view { position: fixed; inset: 0; z-index: 1; overflow-y: auto; padding: 5.6rem 1.5rem 2.5rem;
          opacity: 0; transform: translateY(16px) scale(.995); pointer-events: none;
          transition: opacity 0.56s var(--ease), transform 0.56s var(--ease); }
  .view.on { opacity: 1; transform: none; pointer-events: auto; }
  /* grid-template-columns explicitly caps the track's minimum at 0 (grid's default "auto" track
     otherwise sizes to its content's min-content width, the same overflow trap flex items have by
     default) - without this, a wide child like #plist's section columns can stretch .wrap past the
     viewport instead of shrinking/wrapping, forcing .view's horizontal scrollbar to appear even
     though every column inside already has its own min-width:0 and text-wrapping/ellipsis rules */
  .wrap { max-width: 48.75rem; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.1rem; }
  .view.on .anim { animation: rise 0.77s var(--ease) both; }
  .view.on .anim:nth-child(2) { animation-delay: .07s; }
  .view.on .anim:nth-child(3) { animation-delay: .14s; }
  .view.on .anim:nth-child(4) { animation-delay: .21s; }
  .view.on .anim:nth-child(5) { animation-delay: .28s; }
  @keyframes rise { from { opacity: 0; transform: translateY(14px); } }

  /* ============ focus ============ */
  #focus { display: flex; align-items: center; justify-content: center; gap: 3.2rem; flex-wrap: wrap; }
  /* #fsn is pinned to the viewport's own bottom edge (see its rule below), independent of how
     tall the centered #fmain column happens to be - on a short viewport (a windowed, non-fullscreen
     browser eating vertical space on its title/tab bar) the centered column and the pinned panel
     can collide. Reserving extra bottom room - only while #fsn actually has something to show -
     shifts the centered column up enough to clear it at realistic window-chrome heights, without
     permanently shrinking the centered composition on a fresh account where #fsn stays hidden. */
  #focus:has(#fsn:not([hidden])) { padding-bottom: 2rem; }
  #fmain { display: grid; justify-items: center; text-align: center; }
  /* the 1.3x boost only has room on genuinely spacious displays - #fside and #fsn are both
     position:absolute (they opt out of #focus's flex layout entirely, so nothing stops #fmain
     growing into them). At the root font-size floor (see the clamp() above; anything narrower
     than ~2240x1260 sits there) #tline alone comes out to ~717px unscaled - at 1.3x that's
     ~933px, wide enough to overlap #fside's fixed-width column by well over 100px on a realistic
     tablet/laptop-window size. Gating the scale to the same width/height this app's font clamp
     already treats as "enough room" keeps the dramatic large-display look without inflating the
     one screen where three independently-positioned pieces (#fmain, #fside, #fsn) have to share
     space with no collision-avoidance between them. */
  @media (min-width: 2240px) and (min-height: 1260px) {
    #fmain { transform: scale(1.3); }
  }
  #clock { font-size: clamp(4.4rem, 11.5vw, 9.2rem); font-weight: 700; line-height: 1; letter-spacing: -.03em;
           padding: 0 .06em; /* background-clip:text clips glyph overhang at the box edge */
           background: linear-gradient(110deg, #fff 15%, #b8c9ff 40%, #e5b8ff 60%, #ffc4b0 78%, #fff 100%);
           background-size: 250% 100%; -webkit-background-clip: text; background-clip: text; color: transparent;
           animation: shimmer 22.4s linear infinite;
           filter: drop-shadow(0 0 44px rgba(150,140,255,.25)); }
  @keyframes shimmer { to { background-position: -250% 0; } }
  #clock small { font-size: .34em; font-weight: 500; letter-spacing: .02em;
                 -webkit-text-fill-color: rgba(255,255,255,.85); }
  #date { margin-top: .7rem; font-size: .95rem; color: var(--ink-2); margin-bottom: .9rem; }
  #modesw { position: relative; display: inline-flex; padding: .1875rem; border-radius: 999px;
            background: rgba(255,255,255,.05); border: 1px solid var(--hair); margin-bottom: 2.5rem; }
  #modesw .thumb { position: absolute; top: .1875rem; bottom: .1875rem; border-radius: 999px;
                   background: rgba(255,255,255,.12); box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
                   transition: left 0.49s var(--ease), width 0.49s var(--ease); }
  #modesw button { position: relative; background: none; border: none; cursor: pointer;
                   font: inherit; font-size: .72rem; font-weight: 500; color: var(--ink-2);
                   padding: .32rem .85rem; border-radius: 999px; transition: color 0.42s; }
  #modesw button.on, #modesw button:hover { color: var(--ink); }
  #minswrap.dim { opacity: .35; pointer-events: none; }
  #ringbox { position: relative; width: clamp(15rem, 38vh, 20.625rem); aspect-ratio: 1; display: grid; place-items: center; }
  /* overflow:visible - svg's own UA default is hidden (unlike every other element, whose default
     is visible), which was hard-clipping #prog/#ticks's drop-shadow glow at the svg's own square
     canvas edge instead of letting it fade out smoothly - that flat clipped edge is what read as a
     square box around the ring. */
  #ringbox svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg);
                 overflow: visible; }
  #track { fill: none; stroke: rgba(255,255,255,.055); stroke-width: 5; }
  #ticks { fill: none; stroke: rgba(255,255,255,.13); stroke-width: 7; stroke-dasharray: 1.2 9.69; }
  #prog  { fill: none; stroke: url(#gFocus); stroke-width: 5; stroke-linecap: round;
           stroke-dasharray: 741.4; transition: stroke-dashoffset 0.42s linear;
           filter: drop-shadow(0 0 16px rgba(191,90,242,.45)); }
  #prog.break { stroke: url(#gBreak); filter: drop-shadow(0 0 16px rgba(48,209,88,.45)); }
  #ringbox.pulse { animation: pulse 1.26s var(--ease); }
  @keyframes pulse { 30% { transform: scale(1.05); } }
  #left { font-size: 3.7rem; font-weight: 100; font-variant-numeric: tabular-nums; line-height: 1; letter-spacing: -.02em; }
  #mode { margin: .9rem auto 0; display: inline-flex; align-items: center; gap: .45rem;
          font-size: .74rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
          color: var(--ink-2); padding: .3rem .85rem; border-radius: 999px;
          background: var(--glass); border: 1px solid var(--hair); }
  #mode i { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); transition: background 0.42s; }
  #mode.run i { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
  #mode.run.brk i { background: var(--green); box-shadow: 0 0 8px var(--green); }
  #controls { margin-top: 2.3rem; display: flex; gap: .7rem; }
  .pill { font: inherit; font-size: .92rem; font-weight: 500; color: var(--ink);
          background: var(--glass); border: 1px solid var(--hair-2); border-radius: 999px;
          padding: .55rem 1.5rem; cursor: pointer;
          transition: background 0.35s, transform 0.21s var(--ease); }
  .pill:hover { background: var(--glass-2); transform: translateY(-1px); }
  .pill:active { transform: scale(.96); }
  .pill.on { color: var(--accent); border-color: rgba(10,132,255,.5); }
  .minpill { display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
             cursor: text; padding: .55rem 1.3rem; }
  .minpill:focus-within { border-color: rgba(10,132,255,.5); }
  #smins { width: 2.2rem; background: none; border: none; padding: 0; text-align: right;
           font: inherit; color: var(--ink); font-variant-numeric: tabular-nums; }
  #smins:focus { outline: none; }
  #smins::-webkit-inner-spin-button, #smins::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
  #smins { -moz-appearance: textfield; }
  .minpill .unit { font-size: .78rem; color: var(--ink-3); }
  #startpause { min-width: 7.5rem; border: none; font-weight: 600;
    background: linear-gradient(180deg, #2b95ff, #0071e3);
    box-shadow: 0 6px 20px rgba(10,132,255,.35), inset 0 1px 0 rgba(255,255,255,.25); }
  #startpause:hover { background: linear-gradient(180deg, #3ea0ff, #0a7cf0); }
  #dots { margin-top: 2rem; display: flex; align-items: center; gap: .5rem; min-height: 1rem; }
  #dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
            box-shadow: 0 0 8px rgba(10,132,255,.6); animation: rise 0.56s var(--ease) both; }
  #dots i.hollow { background: none; border: 1px solid var(--ink-3); box-shadow: none; }
  #dots span { font-size: .74rem; color: var(--ink-3); margin-left: .3rem; cursor: pointer; transition: color 0.35s; }
  #dots span:hover { color: var(--ink); }

  /* today timeline: a thick 24h axis holding session blocks and task-completion ticks directly */
  #tline { position: relative; width: min(51.25rem, 85vw); height: 3.875rem; margin-top: 1.6rem; }
  #tlaxis { position: absolute; top: 1.25rem; left: 0; right: 0; height: .75rem; border-radius: 6px;
            background: rgba(255,255,255,.06); overflow: hidden; }
  #tlaxis i.night { position: absolute; top: 0; bottom: 0; background: rgba(94,92,230,.25); }
  #tlaxis i.sess { position: absolute; top: 0; bottom: 0; border-radius: 2px; }
  #tlaxis i.sess.c { background: var(--accent); box-shadow: 0 0 6px rgba(10,132,255,.6); }
  #tlaxis i.sess.a { background: var(--red); box-shadow: 0 0 6px rgba(255,69,58,.6); }
  /* task completions marked directly on the axis, alongside the session blocks */
  #tlaxis i.tick { position: absolute; top: 0; bottom: 0; width: 3px; border-radius: 1px;
                   background: var(--green); box-shadow: 0 0 6px var(--green); }
  #tline b { position: absolute; top: 0; bottom: 0; width: 2px; border-radius: 1px;
             background: rgba(255,255,255,.55); box-shadow: 0 0 4px rgba(255,255,255,.4); }
  /* hour markers under the axis - thin ticks plus a handful of labels, so the strip reads as an
     actual time scale rather than just a proportional bar with no reference points */
  #tlticks { position: absolute; top: 2.125rem; left: 0; right: 0; height: .3125rem; }
  #tlticks i { position: absolute; top: 0; width: 1px; height: 100%; background: rgba(255,255,255,.12); }
  #tlticks i.maj { background: rgba(255,255,255,.22); height: 100%; }
  #tllabels { position: absolute; top: 2.625rem; left: 0; right: 0; height: 1rem; }
  #tllabels span { position: absolute; top: 0; transform: translateX(-50%); font-size: .66rem;
                    color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
  #tllabels span:first-child { transform: none; }
  #tllabels span:last-child { transform: translateX(-100%); }

  /* focus dim mode: while the timer runs, only the ring and the task card stay lit */
  #clock, #date, #fsn, #dots, #tline { transition: opacity 1.12s var(--ease); }
  body.focusing #clock, body.focusing #date, body.focusing #fsn,
  body.focusing #dots, body.focusing #tline, body.focusing #hint { opacity: .15; }

  /* focus side card, pinned top-right→middle */
  #fside { position: absolute; top: 5.6rem; bottom: 50vh; width: 18.75rem; right: 1.5rem;
           display: flex; flex-direction: column; }
  .fgroup { font-size: .7rem; font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
            color: var(--ink-2); margin: .9rem 0 .15rem; }
  .fgroup:first-child { margin-top: 0; }
  .fsecgroup { font-size: .92rem; font-weight: 700; letter-spacing: -.01em; color: var(--ink);
               margin: 1.2rem 0 0; }
  .fsecgroup:first-child { margin-top: 0; }
  /* quick-add: a second, more discoverable path to Main 3 alongside the existing right-click/swipe
     on a Tasks-screen row - #main3results overlays #flist below it rather than pushing it down,
     since it only ever appears transiently while searching. */
  #main3add { position: relative; flex: none; margin-bottom: .9rem; }
  #main3search { width: 100%; font: inherit; font-size: .84rem; color: var(--ink);
                 background: rgba(255,255,255,.05); border: 1px solid var(--hair); border-radius: 9px;
                 padding: .5rem .7rem; transition: border-color 0.2s; }
  #main3search:focus { border-color: rgba(10,132,255,.5); }
  #main3search:disabled { opacity: .5; cursor: default; }
  #main3results { position: absolute; z-index: 3; top: calc(100% + .3rem); left: 0; right: 0;
                  background: rgba(20,25,38,.96); border: 1px solid var(--hair-2); border-radius: 10px;
                  padding: .3rem; backdrop-filter: blur(18px); box-shadow: 0 14px 40px rgba(0,0,0,.4);
                  max-height: 15rem; overflow-y: auto; }
  .main3result { all: unset; box-sizing: border-box; display: block; width: 100%; cursor: pointer;
                 padding: .4rem .6rem; border-radius: 7px; }
  .main3result:hover { background: rgba(255,255,255,.08); }
  .m3text { display: block; font-size: .84rem; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .m3ctx { display: block; font-size: .7rem; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #flist { overflow-y: auto; flex: 1; min-height: 0; }
  #flist .frow, #sntoday .frow { display: flex; align-items: center; gap: .6rem; padding: .42rem 0;
                 border-bottom: 1px solid rgba(255,255,255,.05); font-size: .88rem; animation: rise 0.42s var(--ease); }
  #flist .frow:last-child, #sntoday .frow:last-child { border-bottom: none; }
  #flist .frow.sub { padding-left: 1.7rem; font-size: .82rem; }
  #flist .frow.sub input[type=checkbox] { width: .9375rem; height: .9375rem; }
  #flist .frow.sub input[type=checkbox]:checked::after { left: .23125rem; top: .075rem; width: .225rem; height: .4375rem; }

  /* ============ panels ============ */
  .panel { position: relative; background: var(--glass); border: 1px solid var(--hair);
           border-radius: 20px; padding: 1.35rem 1.5rem; backdrop-filter: blur(20px);
           box-shadow: 0 20px 50px rgba(0,0,0,.25), inset 0 1px 0 rgba(255,255,255,.05); }
  /* #authGate/#recap/#feedbackModal each sit on their own full-screen blurred scrim (see those
     rules below) - .panel's OWN blur on top of that is pure double-compositing waste, recomputed
     every frame of its entrance/exit animation, since the scrim already blurs the whole page
     underneath. #fside (the only other .panel user) has no scrim behind it, so its blur stays. */
  #authGate .panel, #recap .panel, #feedbackModal .panel { backdrop-filter: none; }
  .phead { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: .5rem; gap: 1rem; }
  .phead h2 { font-size: .78rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2); }
  .phead .meta { font-size: .78rem; color: var(--ink-3); }
  .empty { color: var(--ink-3); font-size: .9rem; line-height: 1.6; padding: .8rem 0; }

  /* ============ tasks ============ */
  #tasks .wrap, #calendar .wrap, #willpower .wrap, #stats .wrap { max-width: none; }
  #tcbar, #stbar { display: flex; height: .75rem; border-radius: 6px; overflow: hidden;
           background: rgba(255,255,255,.07); margin-top: 1rem; }
  #tcbar i, #stbar i { transition: width 0.91s var(--ease); }
  #tcopen, #stopenb { background: var(--c4); }
  #tcdone, #stdoneb { background: var(--accent); }
  input:focus, select:focus { outline: none; }
  ::placeholder { color: var(--ink-3); }
  .tasks-toolbar { display: flex; justify-content: flex-end; gap: .6rem; margin-bottom: -.2rem; }
  /* at most 5 section columns per row - a 20% flex-basis means a 6th has no room left on the
     line and wraps below, where it (and any more) grow to fill that new row on their own */
  #plist { display: flex; flex-wrap: wrap; width: 100%; align-items: flex-start; row-gap: 2rem; }
  .section-col { flex: 1 1 20%; min-width: 0; display: flex; flex-direction: column; gap: 1.1rem;
                 padding: 0 1.4rem; }
  .section-col:first-child { padding-left: 0; }
  .section-col:last-child { padding-right: 0; }
  .section-col:not(:last-child) { border-right: 1px solid var(--hair); }
  .section-head { display: flex; align-items: center; gap: .5rem; padding: 0 .2rem; }
  .section-head b, .section-head input.secname { font-size: 1.2rem; font-weight: 700; letter-spacing: -.01em;
    color: var(--ink); }
  /* section titles had no truncation at all (unlike project names above), plus the same
     missing min-width:0 - a long section name would push its column, and the whole row, wider
     than the window with no way to shrink */
  /* flex-grow:0 (not 1) - it only needs to shrink for a long name, not actively expand for a short
     one. flex:1 here pushed the hide-completed eye icon (and edit/archive) far away from a short
     name like "Work" instead of sitting right next to it; min-width:0 alone is what lets a long
     name still shrink and ellipsis-truncate. */
  .section-head b { min-width: 0; flex: 0 1 auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .section-head input.secname { font-family: inherit; background: rgba(255,255,255,.05);
    border: 1px solid rgba(10,132,255,.5); border-radius: 8px; padding: .1rem .5rem; min-width: 0; flex: 1 1 auto; }
  /* unlike rename/delete this is a state toggle, not an action - stays visible without hovering so you
     can tell at a glance whether a section is currently hiding its completed tasks */
  .section-head .hidedone { opacity: .5; }
  .section-head .hidedone.on { opacity: .95; color: var(--accent); }
  .section-head .archsec.on, .proj header .arch.on { opacity: .95; color: var(--accent); }
  /* capped so a section with many projects scrolls internally instead of pushing its "+ Add project"
     input (and every other section's alignment) further down the page */
  .section-projects { display: grid; gap: 1.1rem; min-width: 0; max-height: 62.5rem; overflow-y: auto;
    overflow-x: hidden; padding-right: .4rem; margin-right: -.4rem; }
  /* .panel (shared with every other panel in the app) deliberately has no overflow:hidden, since
     it would clip its own outer box-shadow - scoped here instead of there so only project cards
     lose that shadow. Without this, header content that doesn't fully fit (a long name once
     min-width:0 lets it shrink as far as it can, still short of the icons+badges' fixed width)
     rendered past the card's own straight-edge box instead of the rounded corner, which is what
     visually looked like the corner getting "cut" by the overflowing content. */
  .proj { overflow: hidden; transition: border-color 0.42s, background 0.42s; }
  .proj:hover { border-color: var(--hair-2); background: var(--glass-2); }
  /* -webkit-touch-callout suppresses iOS Safari's native long-press callout (copy/share sheet),
     which otherwise races our own JS press-and-hold drag/context-menu logic below and cancels it
     mid-gesture; user-select stops the OS also treating the hold as a text-selection attempt.
     Neither property affects scrolling (unlike touch-action), so swipe-to-scroll is untouched. */
  .proj header { display: flex; align-items: center; gap: .65rem; cursor: grab;
                 -webkit-touch-callout: none; user-select: none; }
  .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; box-shadow: 0 0 10px currentColor; }
  /* min-width:0 is what actually lets the ellipsis above apply - a flex item floors at its own
     content's full width otherwise (same bug as the .section-head b fix below), which is what let
     one long project name force this whole column (and the section-columns row) wider than the
     window instead of truncating. flex-grow is 0, not 1 - it only needs to shrink for a long name,
     not actively expand for a short one, which pushed the edit/archive icons far away from a short
     name like "Launch" instead of sitting right next to it. */
  .proj header b { font-weight: 600; font-size: 1.08rem; letter-spacing: -.01em; min-width: 0; flex: 0 1 auto;
                   white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .proj header input.pname { font: inherit; font-weight: 600; font-size: 1.08rem; letter-spacing: -.01em;
    color: var(--ink); background: rgba(255,255,255,.05); border: 1px solid rgba(10,132,255,.5);
    border-radius: 8px; padding: .1rem .5rem; min-width: 0; flex: 1 1 auto; }
  .proj header .count, .section-head .count { color: var(--ink-2); font-size: .74rem; margin-left: auto; flex: none;
                        font-variant-numeric: tabular-nums; padding: .18rem .6rem;
                        border-radius: 999px; background: var(--glass-2); border: 1px solid var(--hair); }
  .proj header .count.full { color: var(--green); background: rgba(48,209,88,.1);
                             border-color: rgba(48,209,88,.35); }
  /* collapse/expand chevron for an individual project - always visible (not hover-gated like
     rename/delete) so you can tell at a glance which projects are currently collapsed */
  .proj header .collapse { opacity: .6; transition: transform 0.35s var(--ease), opacity 0.28s, color 0.28s; }
  .proj header .collapse:hover { opacity: 1; }
  .proj header .collapse.collapsed { transform: rotate(-90deg); }
  .pbar { height: 3px; border-radius: 2px; background: rgba(255,255,255,.07); overflow: hidden; margin: .8rem 0 .4rem; }
  .pbar i { display: block; height: 100%; border-radius: 2px; transition: width 0.84s var(--ease); }
  .proj ul { list-style: none; padding: 0; }
  .proj li { margin: 0 -.45rem; border-radius: 9px; transition: background 0.28s;
             border-bottom: 1px solid rgba(255,255,255,.05); animation: rise 0.49s var(--ease); }
  .proj li:hover { background: rgba(255,255,255,.035); }
  .proj li:last-of-type { border-bottom: none; }
  /* a gap before each new task (and its subtasks) sets that whole group apart from the one before.
     position:relative + overflow:hidden clips .row-swipe-actions until swiped into view; the row's
     own flex layout/padding lives on .row-content now (the child that actually slides) for both
     task and subtask rows - only the drag gesture (cursor:grab, reordering) stays task-only below,
     since subtasks can swipe to reveal actions but can't yet be reordered (see beginDragWatch()). */
  .proj li:not(.sub) { margin-top: .6rem; cursor: grab; -webkit-touch-callout: none; user-select: none;
                        position: relative; overflow: hidden; }
  .proj li:not(.sub):first-child { margin-top: 0; }
  /* the sliding row content and the actions revealed behind it on a touch swipe-left - see
     beginDragWatch()'s swipe-reveal branch. .row-swipe-actions sits just past the row's right edge
     (invisible, clipped by the li's own overflow:hidden) until both are translated left together. */
  .row-content { display: flex; align-items: center; gap: .7rem; padding: .48rem .45rem;
                 box-sizing: border-box; width: 100%; transition: transform 0.25s var(--ease); }
  .row-swipe-actions { position: absolute; top: 0; left: 100%; bottom: 0; width: 8.4rem; display: flex;
                        transition: transform 0.25s var(--ease); }
  .row-swipe-actions button { all: unset; flex: 1; box-sizing: border-box; display: flex;
    align-items: center; justify-content: center; cursor: pointer; font-size: 1.05rem;
    color: var(--ink-2); background: var(--glass-2); }
  .row-swipe-actions button + button { border-left: 1px solid var(--hair); }
  .row-swipe-actions button.danger { background: rgba(255,69,58,.18); color: var(--red); }
  .proj li.dragging, .proj.dragging { opacity: .4; }
  /* the floating clone that follows the cursor during a pointer-based drag, and the highlight
     on whichever row/card is currently the drop target - both driven by beginDragWatch() */
  .drag-ghost { position: fixed; z-index: 40; pointer-events: none; opacity: .92; overflow: hidden;
                transform: rotate(-1.5deg); box-shadow: 0 14px 34px rgba(0,0,0,.5);
                background: rgba(20,25,38,.96); border-radius: 9px; }
  .drop-hint { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 9px; }
  /* classic outline/tree connector: a vertical trunk with a horizontal elbow branching into each
     subtask's checkbox. The trunk sits at left:0, which lands exactly under the MAIN task's own
     checkbox center - not the subtask's: a non-sub li's checkbox center is 10px from the <ul>'s
     left edge (margin -.45rem + padding .45rem cancel out), while a .sub li's own left edge sits
     9.6px from the <ul> (its margin-left: .6rem) - so left:0 in the sub li's own coordinate space
     already lands ~10px from the ul, matching the main task's checkbox, not the sub's own (which
     is further right, at padding-left 1.5rem). The trunk runs the full row height (T-junction)
     when another subtask follows, and stops halfway (L-junction) on the last one in the group. */
  /* flex/gap/padding live on .row-content (below) now, same as a top-level task row - only the
     left inset is different, to leave room for the ::before/::after connector drawn on this li
     itself. left:0 on those pseudo-elements anchors to the li's own edge regardless of how much
     padding the li (or its .row-content child) declares, so moving the padding onto .row-content
     doesn't shift the connector - it only had to move so subtasks could gain a sliding wrapper. */
  .proj li.sub { position: relative; overflow: hidden; margin-left: .6rem; border-bottom: none;
                 -webkit-touch-callout: none; user-select: none; }
  .proj li.sub > .row-content { padding-left: 1.5rem; }
  /* whole-pixel thickness (not 1.5px) so it renders crisply and consistently across every row -
     a fractional width forces the browser to anti-alias, and the blend shifts slightly depending
     on each row's exact sub-pixel offset, making some lines look thicker/blurrier than others */
  .proj li.sub::before { content: ''; position: absolute; left: 0; top: 0; height: 50%;
    width: 2px; background: rgba(255,255,255,.25); }
  .proj li.sub::after { content: ''; position: absolute; left: 0; top: 50%; width: 20px;
    height: 2px; background: rgba(255,255,255,.25); }
  .proj li.sub:has(+ li.sub)::before { height: 100%; }
  .proj li.sub input[type=checkbox] { width: 1rem; height: 1rem; }
  /* circular check, Reminders-style */
  input[type=checkbox] { appearance: none; width: 1.25rem; height: 1.25rem; flex: none; cursor: pointer;
    border: 1.5px solid rgba(255,255,255,.28); border-radius: 50%; position: relative;
    transition: border-color 0.28s, background 0.35s var(--ease); }
  input[type=checkbox]:hover { border-color: rgba(255,255,255,.55); }
  input[type=checkbox]:checked { background: var(--accent); border-color: var(--accent); animation: pop 0.42s var(--ease); }
  input[type=checkbox]:checked::after { content: ''; position: absolute; left: .34375rem; top: .15625rem;
    width: .3125rem; height: .5625rem; border: solid #fff; border-width: 0 1.7px 1.7px 0; transform: rotate(43deg); }
  .proj li.sub input[type=checkbox]:checked::after { left: .25rem; top: .09375rem; width: .25rem; height: .5rem; }
  @keyframes pop { 50% { transform: scale(1.18); } }
  /* native text-decoration, not a background/pseudo-element trick - browsers always paint it per wrapped
     line correctly regardless of box context (a background line-based effect can't: label is a flex item,
     which the flex spec blockifies into a single box, so box-decoration-break never got real per-line
     fragments to clone across - it just drew one line near the first row and skipped the rest) */
  label { position: relative; cursor: pointer; text-decoration-line: line-through; text-decoration-color: transparent;
    transition: color 0.49s, text-decoration-color 0.49s var(--ease); }
  .done label { color: var(--ink-3); text-decoration-color: var(--ink-3); }
  .ghost { background: none; border: none; color: var(--ink-3); font-size: 1.05rem; cursor: pointer;
           padding: 0 .3rem; opacity: 0; transition: opacity 0.35s, color 0.28s; line-height: 1; }
  .proj li:hover .ghost, .proj header:hover .ghost, .section-head:hover .ghost, .phead .ghost,
  .wprow:hover .ghost { opacity: 1; }
  .phead .ghost { font-size: .82rem; opacity: .7; }
  .ghost:hover { color: var(--ink); }
  /* touch/stylus input has no real hover state, so an opacity:0-until-hover control (every
     rename/delete/date/flag icon on a project, section, or task row, plus the Stats-page
     session-log and Signal/Noise delete buttons - all share this .ghost class) would be
     permanently unreachable on a tablet or phone. Force it visible whenever the device can't
     hover or only has a coarse (finger) pointer; mouse/trackpad users keep the original
     declutter-until-hover behavior untouched. */
  @media (hover: none), (pointer: coarse) {
    .ghost { opacity: 1; }
  }
  /* a past session deliberately slowed every transition/animation app-wide by 1.4x "for a
     smoother feel" (see SESSION_LOG.md) - tuned against desktop hardware. On tablets, a longer
     animation just means more frames where the (already GPU-heavy, see .panel above) backdrop-blur
     compositing has to keep up, which is what reads as lag. Reversing just that 1.4x factor here,
     scoped to touch/coarse-pointer devices, leaves the desktop feel untouched. Everything below is
     safe to touch on its own (no JS reads these durations); #ringbox.pulse/.view/#tutSpot/#tutCard
     just below are DIFFERENT - each has a setTimeout (focus.js/tutorial.js) hand-tuned to match, so
     `IS_TOUCH` (core.js, same media-query condition) branches those in JS too - check there before
     changing either side of one of those pairs, or they'll desync (fire before/after the CSS
     transition actually finishes). The achievement toast and Signal/Noise "just finished" pulse
     were checked too and turned out NOT to be tightly coupled - their setTimeouts are plain
     display-duration timers (comfortably longer than the animation either way), not a matched
     pair, so they're intentionally left alone rather than added here for no benefit. */
  @media (hover: none), (pointer: coarse) {
    .view.on .anim { animation-duration: 0.55s; }
    #dots i { animation-duration: 0.4s; }
    #flist .frow, #sntoday .frow { animation-duration: 0.3s; }
    .proj li { animation-duration: 0.35s; }
    #recap .panel, #authGate .panel, #feedbackModal .panel { animation-duration: 0.5s; }
    #installPrompt, #sessionNamePrompt { animation-duration: 0.43s; }
    /* paired with a setTimeout - see focus.js/tutorial.js's IS_TOUCH branches and the comment above */
    #ringbox.pulse { animation-duration: 0.9s; }
    .view { transition-duration: 0.4s; }
    /* order matches the base rule's own left/top/width/height/opacity/border-radius list -
       transition-duration maps positionally, so all six must be restated even though only the
       5th (opacity) is the one a setTimeout actually waits on */
    #tutSpot { transition-duration: 0.357s, 0.357s, 0.357s, 0.357s, 0.214s, 0.357s; }
    /* not itself paired with a setTimeout, but fades alongside #tutSpot during the same tutorial
       transitions - reversed too so the two don't visibly finish at different speeds */
    #tutCard { transition-duration: 0.357s, 0.357s, 0.25s; }
  }
  /* priority flag stays visible without hovering, unlike the other row actions, so flagged
     tasks are still identifiable at a glance once you move on to the next row */
  .proj li .ghost.flag.on { opacity: .9; color: var(--c3); }
  /* one right-aligned group (due pill + every action icon), instead of margin-left:auto on
     "whichever button happens to come first" - that broke as soon as a non-button element
     (the due pill) needed to lead the group */
  /* the '+' glyph renders high in its own em-box relative to the label's lowercase text -
     nudge it down so it lines up with the task text now that it sits right beside it */
  .proj li .row-content > button.ghost { position: relative; top: .1875rem; }
  .proj li .row-actions { margin-left: auto; display: flex; align-items: center; gap: .7rem; }
  /* right-click menu for a task row's edit/delete - keeps those two off the row itself */
  .ctxmenu { position: fixed; z-index: 10; min-width: 8.125rem; padding: .3rem; border-radius: 12px;
             background: rgba(20,25,38,.92); border: 1px solid var(--hair-2); backdrop-filter: blur(18px);
             box-shadow: 0 14px 40px rgba(0,0,0,.5); display: flex; flex-direction: column; gap: .1rem;
             animation: ctxIn 0.15s var(--ease); }
  .ctxmenu button { all: unset; box-sizing: border-box; width: 100%; cursor: pointer; padding: .42rem .65rem;
                    border-radius: 8px; font: inherit; font-size: .82rem; color: var(--ink-2);
                    transition: background 0.15s, color 0.15s; }
  .ctxmenu button:hover { background: rgba(255,255,255,.08); color: var(--ink); }
  .ctxmenu button.danger:hover { background: rgba(255,69,58,.15); color: var(--red); }
  @keyframes ctxIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
  /* line-height:1 is the actual fix here, not a nudge: the date text had no explicit line-height,
     so it inherited the ambient 'normal' value - which is font-metric-dependent, not just
     font-size-dependent, and Segoe UI Variable's "normal" line box runs unusually tall/asymmetric
     compared to a typical fallback font. That's invisible in a test environment without the real
     font installed, but on a real Windows box it pushes the visible glyph well off the pill's
     flex-centered middle. Tight line-height makes centering deterministic regardless of font. */
  .due-pill { font-size: .68rem; padding: .1rem .5rem; border-radius: 999px; flex: none;
              background: var(--glass-2); border: 1px solid var(--hair); color: var(--ink-3); white-space: nowrap;
              display: inline-flex; align-items: center; line-height: 1; }
  /* Tasks-page pill carries a .pill-x clear button and becomes the due-date control itself
     (click to change, × to clear); the Focus-screen pill is plain read-only text, no :has() match */
  .due-pill:has(.pill-x) { cursor: pointer; gap: .3rem; padding-right: .35rem; }
  .due-pill.overdue { color: var(--red); background: rgba(255,69,58,.12); border-color: rgba(255,69,58,.35); }
  .due-pill.today { color: #FF9F0A; background: rgba(255,159,10,.12); border-color: rgba(255,159,10,.35); }
  /* the '×' glyph renders high in its own em-box, same font-metrics quirk as the '+' add-subtask
     button - nudge it down a couple px so it lines up with the pill's date text beside it */
  .due-pill .pill-x { all: unset; position: relative; top: .125rem; cursor: pointer; font-size: .85rem;
                       line-height: 1; color: inherit; opacity: .6; transition: opacity 0.15s; }
  .due-pill .pill-x:hover { opacity: 1; }
  /* custom due-date calendar popup, replacing the native <input type=date> picker */
  .datepop { position: fixed; z-index: 10; width: 13.75rem; padding: .7rem; border-radius: 14px;
             background: rgba(20,25,38,.94); border: 1px solid var(--hair-2); backdrop-filter: blur(18px);
             box-shadow: 0 14px 40px rgba(0,0,0,.5); animation: ctxIn 0.15s var(--ease); }
  .datepop .dp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: .5rem; }
  .datepop .dp-head span { font-size: .8rem; font-weight: 600; color: var(--ink); }
  .datepop .dp-head button { all: unset; cursor: pointer; font-size: .95rem; padding: .1rem .5rem;
                              border-radius: 6px; color: var(--ink-2); transition: background 0.15s, color 0.15s; }
  .datepop .dp-head button:hover { background: rgba(255,255,255,.08); color: var(--ink); }
  .datepop .dp-dow { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; margin-bottom: .2rem; }
  .datepop .dp-dow span { font-size: .6rem; color: var(--ink-3); }
  .datepop .dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .125rem; }
  .datepop .dp-day { all: unset; box-sizing: border-box; width: 100%; aspect-ratio: 1; display: flex;
                      align-items: center; justify-content: center; font-size: .76rem; color: var(--ink-2);
                      border-radius: 8px; cursor: pointer; transition: background 0.15s, color 0.15s; }
  .datepop .dp-day:hover { background: rgba(255,255,255,.08); color: var(--ink); }
  .datepop .dp-day.today { color: var(--accent); font-weight: 600; }
  .datepop .dp-day.sel { background: var(--accent); color: #fff; }
  .datepop .dp-foot { display: flex; justify-content: space-between; margin-top: .5rem; padding-top: .5rem;
                       border-top: 1px solid var(--hair); }
  .datepop .dp-foot button { all: unset; cursor: pointer; font-size: .74rem; color: var(--ink-2);
                              padding: .25rem .5rem; border-radius: 6px; transition: background 0.15s, color 0.15s; }
  .datepop .dp-foot button:hover { background: rgba(255,255,255,.08); color: var(--ink); }
  /* weekly routine grid - see renderCalendar()/openCalPop(). Every cell/slot is a Grid item placed
     by an inline grid-column/grid-row set in JS, so a multi-hour slot just spans rows - no
     absolute-position math needed the way the Focus-screen timeline strip requires. */
  /* no max-height/overflow here on purpose - #calendar (.view) is already its own full-page
     scroll container, so this grid just flows as normal page content within it instead of
     becoming a second, nested scrollbar. Day/hour headers are NOT position:sticky - nested inside
     a padded panel that's itself inside the real scroll container, sticky would detach them from
     their own panel bounds while scrolling and could visually collide with content above/below. */
  #calgrid { display: grid; grid-template-columns: 3.5rem repeat(7, minmax(0, 1fr));
             border: 1px solid var(--hair); border-radius: 12px; }
  .caldayhead { grid-row: 1; text-align: center;
                font-size: .78rem; font-weight: 600; color: var(--ink-2); padding: .5rem 0;
                background: rgba(20,25,38,.94);
                border-bottom: 1px solid var(--hair-2); }
  .calhourhead { grid-row: 1; grid-column: 1;
                 background: rgba(20,25,38,.94);
                 border-bottom: 1px solid var(--hair-2); }
  .calhour { grid-column: 1; font-size: .68rem; color: var(--ink-3); text-align: right;
             padding: .2rem .5rem 0 0; border-top: 1px solid var(--hair); }
  .calcell { border-top: 1px solid var(--hair); border-left: 1px solid var(--hair); cursor: pointer;
             transition: background 0.15s; min-height: 2.75rem; }
  .calcell:hover { background: rgba(255,255,255,.045); }
  .calblock { margin: 1px 2px; border-radius: 7px; padding: .3rem .5rem; font-size: .74rem;
              font-weight: 600; color: #fff; cursor: pointer; overflow: hidden; line-height: 1.3;
              display: flex; align-items: flex-start; transition: filter 0.15s; }
  .calblock:hover { filter: brightness(1.12); }
  /* dark glass popup for adding/editing a routine slot - same visual language as .ctxmenu/.datepop */
  .calpop { position: fixed; z-index: 10; width: 15.5rem; padding: .9rem; border-radius: 14px;
            background: rgba(20,25,38,.94); border: 1px solid var(--hair-2); backdrop-filter: blur(18px);
            box-shadow: 0 14px 40px rgba(0,0,0,.5); animation: ctxIn 0.15s var(--ease); }
  .calpop h4 { font-size: .82rem; font-weight: 600; }
  .calpop label { display: block; font-size: .68rem; color: var(--ink-3); text-transform: uppercase;
                  letter-spacing: .04em; margin: .6rem 0 .3rem; }
  .calpop input { width: 100%; font: inherit; font-size: .86rem; color: var(--ink);
                  background: rgba(255,255,255,.05); border: 1px solid var(--hair);
                  border-radius: 8px; padding: .4rem .6rem; box-sizing: border-box; }
  .calpop input[type=number] { width: 4.5rem; }
  .calswatches { display: flex; gap: .4rem; flex-wrap: wrap; }
  .calswatch { all: unset; width: 1.4rem; height: 1.4rem; border-radius: 50%; cursor: pointer;
               box-shadow: 0 0 0 2px transparent; transition: box-shadow 0.15s; }
  .calswatch.sel { box-shadow: 0 0 0 2px var(--ink); }
  .calpop-actions { display: flex; gap: .5rem; margin-top: 1rem; }
  .calpop-actions button { all: unset; box-sizing: border-box; flex: 1; text-align: center; cursor: pointer;
                           font-size: .82rem; padding: .45rem 0; border-radius: 8px;
                           background: var(--accent); color: #fff; transition: background 0.15s; }
  .calpop-actions button:hover { background: #0a7cf0; }
  .calpop-actions button.ghost2 { background: rgba(255,255,255,.08); color: var(--ink-2); }
  .calpop-actions button.ghost2:hover { background: rgba(255,255,255,.14); }
  .calpop-actions button.danger2 { background: rgba(255,69,58,.18); color: var(--red); }
  .calpop-actions button.danger2:hover { background: rgba(255,69,58,.28); }
  /* willpower commitments - a user-fed "I will not..." pledge, checked in once a day, disconnected
     from the routine calendar above (separate concept: resisting something vs. planning a routine) */
  .wprow { display: flex; flex-direction: column; gap: .4rem; padding: .6rem .2rem;
           border-bottom: 1px solid var(--hair); }
  .wprow:last-of-type { border-bottom: none; }
  .wprow-top { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .8rem; }
  /* flex items default to min-width:auto, which floors their shrink at the content's own width -
     for a long, unbroken commitment text that forced the whole row wider than the panel instead
     of wrapping, which is what actually caused the horizontal-scroll regression this fixes. A
     fixed min-width (instead of 0) keeps the text column from squeezing to nothing before the
     streak/%/buttons get a chance to wrap to their own line via flex-wrap above. */
  .wptext { flex: 1 1 auto; min-width: 8rem; font-size: .92rem; overflow-wrap: break-word; }
  .wpstreak { font-size: .72rem; color: var(--ink-3); white-space: nowrap; }
  .wpadherence { font-size: .72rem; color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums;
                 min-width: 2.4rem; text-align: right; }
  .wppbar { margin: 0; background: rgba(255,255,255,.07); }
  .wppbar i { background: var(--accent); }
  .wpbtns { display: flex; gap: .4rem; }
  .wpbtn { all: unset; box-sizing: border-box; cursor: pointer; font-size: .74rem; padding: .3rem .65rem;
           border-radius: 999px; border: 1px solid var(--hair-2); color: var(--ink-2); white-space: nowrap;
           transition: background 0.15s, color 0.15s, border-color 0.15s; }
  .wpbtn:hover { background: rgba(255,255,255,.08); }
  .wpbtn.good.on { background: rgba(48,209,88,.18); color: var(--green); border-color: rgba(48,209,88,.4); }
  .wpbtn.bad.on { background: rgba(255,69,58,.18); color: var(--red); border-color: rgba(255,69,58,.4); }
  .addtask { width: 100%; margin-top: .5rem; padding: .5rem .55rem; font: inherit; font-size: .88rem;
             color: var(--ink); background: none; border: 1px dashed transparent; border-radius: 9px;
             transition: border-color 0.35s, background 0.35s; }
  .addtask:hover, .addtask:focus { border-color: var(--hair); background: rgba(255,255,255,.03); }
  .subinput { font: inherit; font-size: .86rem; color: var(--ink); background: rgba(255,255,255,.05);
              border: 1px solid var(--hair); border-radius: 8px; padding: .28rem .6rem; flex: 1; }

  /* ============ stats: 3 vertical sections - Tasks / Pomodoro / Signal-Noise ============ */
  #stats .wrap { display: block; }
  #achpanel { margin-bottom: 1.1rem; }
  .stats-cols { display: flex; align-items: flex-start; }
  .stats-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 1.1rem;
               padding: 0 1.4rem; }
  .stats-col:first-child { padding-left: 0; }
  .stats-col:last-child { padding-right: 0; }
  .stats-col:not(:last-child) { border-right: 1px solid var(--hair); }
  @media (max-width: 1000px) {
    .stats-cols { flex-direction: column; }
    .stats-col { border-right: none; padding: 0; }
  }
  /* each column gets a colored marker so the 3 sections read as equal-weight peers regardless of height */
  .stats-zone { font-size: .82rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
                color: var(--ink-2); display: flex; align-items: center; gap: .55rem; }
  .stats-zone::before { content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none; }
  #statscol-tasks .stats-zone::before { background: var(--c4); }
  #statscol-pomo .stats-zone::before { background: var(--accent); }
  #statscol-sn .stats-zone::before { background: var(--green); }
  #sttiles { display: flex; gap: 1.7rem; margin-top: .2rem; }
  #tiles, #tttiles, #sntiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 1.1rem; }
  .tile .lab { font-size: .72rem; font-weight: 500; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-2); }
  .tile .val { font-size: 2.5rem; font-weight: 600; letter-spacing: -.02em; margin-top: .35rem; line-height: 1.1; }
  .tile .delta { margin-top: .3rem; font-size: .76rem; font-weight: 500; color: var(--ink-3); }
  .tile .delta.up { color: var(--green); }
  .tile .delta.down { color: var(--red); }
  #chart { display: flex; justify-content: space-between; align-items: flex-end; height: 13.75rem;
           padding: 0 .4rem; border-bottom: 1px solid var(--hair-2); margin-top: 1.1rem; }
  .colwrap { display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
             height: 100%; width: 3.375rem; cursor: default; border-radius: 8px 8px 0 0; transition: background 0.35s; }
  .colwrap:hover { background: rgba(255,255,255,.04); }
  .seg { width: 1.5rem; transition: height 0.91s var(--ease), filter 0.28s; }
  .colwrap:hover .seg { filter: brightness(1.25); }
  .seg.c { background: var(--accent); }
  .seg.a { background: var(--red); margin-bottom: .125rem; }
  .seg.top { border-radius: 4px 4px 0 0; }
  .colwrap .cap { font-size: .74rem; color: var(--ink-2); margin-bottom: .3rem; height: 1em; font-variant-numeric: tabular-nums; }
  #days { display: flex; justify-content: space-between; padding: .5rem .4rem 0; }
  #days span { width: 3.375rem; text-align: center; font-size: .72rem; color: var(--ink-3); }
  #days span.today { color: var(--ink); font-weight: 600; }
  .legend { display: flex; gap: 1.3rem; margin-top: 1rem; font-size: .78rem; color: var(--ink-2); }
  .legend i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: .45rem; }
  /* session log */
  #slog { max-height: 23.75rem; overflow-y: auto; }
  .srow { display: grid; grid-template-columns: minmax(0,10rem) 1fr auto auto 1.1rem; gap: 1rem; align-items: center;
          padding: .5rem 0; border-bottom: 1px solid rgba(255,255,255,.05); font-size: .88rem; }
  .srow:hover .ghost { opacity: 1; }
  .srow:last-child { border-bottom: none; }
  .srow .sname { color: var(--ink); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .srow .when { color: var(--ink-2); }
  .srow .dur { color: var(--ink-2); font-variant-numeric: tabular-nums; }
  .chip { font-size: .72rem; font-weight: 600; padding: .18rem .6rem; border-radius: 999px; }
  .chip.ok  { color: var(--green); background: rgba(48,209,88,.12); }
  .chip.ab  { color: var(--red); background: rgba(255,69,58,.12); }
  .chip.issue { color: var(--c3); background: rgba(255,159,10,.12); }
  .chip.fb { color: var(--accent); background: rgba(10,132,255,.12); }
  #sform { display: flex; gap: .6rem; align-items: center; margin: .3rem 0 .8rem; }
  #sform .subinput { flex: none; width: 5.625rem; }
  #sform #sname { width: 10rem; }
  #sform select { font: inherit; font-size: .86rem; color: var(--ink); background: rgba(255,255,255,.05);
                  border: 1px solid var(--hair); border-radius: 8px; padding: .28rem .6rem; }
  #sform select option { background: #12141c; }
  /* deliberately not .due-pill - that component's overdue/today coloring means "you're late",
     which is backwards here: a past date is the normal, expected case for a backfilled session */
  #sform #sdatepill { all: unset; font: inherit; font-size: .86rem; color: var(--ink); cursor: pointer;
                       background: rgba(255,255,255,.05); border: 1px solid var(--hair); border-radius: 8px;
                       padding: .28rem .6rem; }
  #sform #sdatepill:hover { border-color: var(--hair-2); }
  /* app usage */
  .approw { display: grid; grid-template-columns: 1.875rem 6.875rem 1fr 4rem; gap: .8rem; align-items: center; padding: .42rem 0; }
  .mono { width: 1.75rem; height: 1.75rem; border-radius: 50%; background: var(--glass-2); border: 1px solid var(--hair);
          display: grid; place-items: center; font-size: .78rem; font-weight: 600; color: var(--ink-2); }
  .approw .name { font-size: .88rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .approw .bar { height: .5rem; border-radius: 0 4px 4px 0;
                 background: linear-gradient(90deg, #0060df, #2b95ff); transition: width 0.91s var(--ease); }
  .approw .min { color: var(--ink-2); font-size: .84rem; text-align: right; font-variant-numeric: tabular-nums; }

  /* signal / noise */
  #snbar, #fsnbar { display: flex; height: .75rem; border-radius: 6px; overflow: hidden;
           background: rgba(255,255,255,.07); margin-top: 1rem; }
  #snbar i, #fsnbar i { transition: width 0.91s var(--ease); }
  #snsig, #fsnsig { background: var(--accent); }
  #snnoise, #fsnnoise { background: var(--red); }
  #fsn { position: absolute; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
         width: min(26.25rem, 40vw); }
  #fsnbar, #ftcbar { height: .5rem; border-radius: 4px; margin-top: 0; }
  #ftcbar { display: flex; overflow: hidden; background: rgba(255,255,255,.07); margin-top: .6rem; }
  #ftcbar i { transition: width 0.91s var(--ease); }
  #ftcopen { background: var(--c4); }
  #ftcdone { background: var(--accent); }
  #fsnmeta, #ftcmeta { margin-top: .45rem; text-align: center; font-size: .72rem;
             letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); }
  #snadd { width: 100%; margin-top: .6rem; }
  #sntoday .frow:hover .ghost { opacity: 1; }
  #snbar.pulse, #fsnbar.pulse { animation: pulse 1.26s var(--ease); }
  #sncarry { display: flex; flex-wrap: wrap; gap: .5rem; }
  #sncarry:not(:empty) { margin-top: .6rem; }
  #sncarry button { font: inherit; font-size: .8rem; color: var(--ink-2); background: var(--glass);
                    border: 1px dashed var(--hair-2); border-radius: 999px; padding: .3rem .8rem;
                    cursor: pointer; transition: color 0.28s, border-color 0.28s; }
  #sncarry button:hover { color: var(--ink); border-color: rgba(10,132,255,.6); }
  #sntoday .ghost.recon { opacity: .9; color: var(--accent); }
  #recap { position: fixed; inset: 0; z-index: 7; display: grid; place-items: center;
           background: rgba(0,0,0,.55); backdrop-filter: blur(8px); }
  #recap[hidden] { display: none; }
  #recap .panel { width: min(28.75rem, 90vw); animation: rise 0.7s var(--ease); }
  /* same centered-scrim treatment as #recap - a real gate should feel deliberate, not a
     dismissible corner suggestion like #installPrompt */
  #authGate { position: fixed; inset: 0; z-index: 8; display: grid; place-items: center;
              background: rgba(0,0,0,.55); backdrop-filter: blur(8px); }
  #authGate[hidden] { display: none; }
  #authGate .panel { width: min(23.75rem, 90vw); text-align: center; animation: rise 0.7s var(--ease); }
  #authGate h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: .6rem; }
  #authGate p { font-size: .86rem; color: var(--ink-2); line-height: 1.55; margin-bottom: 1.3rem; }
  #authGateGo, #authPickEmail { width: 100%; justify-content: center; margin-bottom: .8rem; }
  #authGateSkip { all: unset; cursor: pointer; color: var(--ink-3); font-size: .8rem; transition: color 0.2s; }
  #authGateSkip:hover { color: var(--ink-2); }
  #authGate p.authGateLegal { font-size: .72rem; color: var(--ink-3); margin: 1rem 0 0; }
  .authGateLegal a { color: var(--ink-2); }
  /* step 1: log in vs sign up - two equal-weight choices, neither styled as more "primary" */
  .authModeBtn { display: block; width: 100%; height: 2.4rem; border-radius: 999px;
    border: 1px solid var(--hair-2); background: var(--glass); color: var(--ink);
    font: inherit; font-size: .92rem; font-weight: 600; cursor: pointer; margin-bottom: .8rem;
    transition: background 0.25s, border-color 0.25s; }
  .authModeBtn:hover { background: var(--glass-2); border-color: rgba(10,132,255,.5); }
  #authStepProvider h3, #authStepEmail h3 { text-align: center; }
  .authBack { all: unset; cursor: pointer; color: var(--ink-3); font-size: .78rem; display: block;
    margin-bottom: .9rem; transition: color 0.2s; }
  .authBack:hover { color: var(--ink-2); }
  #authGate input { display: block; width: 100%; font: inherit; font-size: .86rem; color: var(--ink);
    background: rgba(255,255,255,.05); border: 1px solid var(--hair); border-radius: 8px;
    padding: .5rem .65rem; box-sizing: border-box; margin-bottom: .6rem; }
  #authGate input:focus { outline: none; border-color: rgba(10,132,255,.5); }
  #authGateSubmit { width: 100%; height: 2.1rem; border-radius: 999px; border: none; background: var(--accent);
    color: #fff; font: inherit; font-size: .84rem; font-weight: 600; cursor: pointer; margin-bottom: .8rem;
    transition: opacity 0.2s; }
  #authGateSubmit:disabled { opacity: .55; cursor: default; }
  #authGateSubmit:hover:not(:disabled) { opacity: .88; }
  #authGateForgot { display: block; text-align: center; color: var(--ink-3); font-size: .74rem; margin-bottom: .9rem;
    text-decoration: none; transition: color 0.2s; }
  #authGateForgot:hover { color: var(--ink-2); }
  /* same centered-scrim treatment as #recap/#authGate - a deliberate action, not a dismissible
     corner suggestion like #installPrompt/#sessionNamePrompt */
  #feedbackModal { position: fixed; inset: 0; z-index: 9; display: grid; place-items: center;
                   background: rgba(0,0,0,.55); backdrop-filter: blur(8px); }
  #feedbackModal[hidden] { display: none; }
  #feedbackModal .panel { width: min(26.25rem, 90vw); text-align: left; animation: rise 0.7s var(--ease); }
  #feedbackModal h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: .6rem; }
  #feedbackModal p { font-size: .86rem; color: var(--ink-2); line-height: 1.55; margin-bottom: 1rem; }
  #feedbackModal textarea { display: block; width: 100%; font: inherit; font-size: .86rem; color: var(--ink);
    background: rgba(255,255,255,.05); border: 1px solid var(--hair); border-radius: 8px;
    padding: .5rem .65rem; box-sizing: border-box; margin-bottom: .7rem; resize: vertical; }
  #feedbackModal textarea:focus { outline: none; border-color: rgba(10,132,255,.5); }
  #feedbackModal textarea[hidden] { display: none; }   /* own display:block above outranks the plain UA [hidden] rule otherwise */
  #fbError { color: var(--red); font-size: .78rem; line-height: 1.4; margin: -.15rem 0 .7rem; }
  #feedbackModal .instrow { display: flex; align-items: center; justify-content: flex-end; gap: .8rem; }
  #fbCancel { all: unset; cursor: pointer; color: var(--ink-3); font-size: .8rem; transition: color 0.2s; }
  #fbCancel:hover { color: var(--ink-2); }
  #fbSubmit { border: none; font: inherit; font-weight: 500; font-size: .82rem; padding: .48rem 1.05rem;
    border-radius: 999px; cursor: pointer; background: var(--accent); color: #fff; transition: opacity 0.2s; }
  #fbSubmit:disabled { opacity: .55; cursor: default; }
  #fbSubmit:hover:not(:disabled) { opacity: .88; }
  #authGateError { color: var(--red); font-size: .78rem; line-height: 1.4; margin: -.15rem 0 .7rem; text-align: left; }
  /* gate-level error (every Google sign-in path reports here - see app.js showGateTopError).
     Sits outside the three steps, so it needs its own boxed treatment rather than inheriting
     the inline-in-a-form look #authGateError has. */
  #authGateTopError { color: var(--red); font-size: .78rem; line-height: 1.4; margin: .6rem 0 0;
    text-align: left; padding: .5rem .65rem; border-radius: .5rem;
    background: color-mix(in srgb, var(--red) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--red) 35%, transparent); }
  .rrow { display: flex; justify-content: space-between; gap: 1rem; padding: .5rem 0;
          border-bottom: 1px solid rgba(255,255,255,.05); font-size: .95rem; }
  .rrow:last-child { border-bottom: none; }
  .rrow b { font-weight: 600; }
  /* 84-day consistency heatmap: cell intensity = share of that day's main tasks done */
  /* fixed 3-column grid (not flex) so a 2-item row's columns still line up under a 3-item row above it */
  .constats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.7rem; margin: .2rem 0 1.2rem; }
  .constat { display: flex; flex-direction: column; min-width: 0; }
  .constat b { font-size: 1.5rem; font-weight: 700; line-height: 1.1; letter-spacing: -.02em; }
  .constat span { font-size: .68rem; color: var(--ink-3); text-transform: uppercase; letter-spacing: .1em; margin-top: .15rem; }
  .conheat-months { display: flex; margin-bottom: .35rem; }
  .conheat-spacer { width: 1.75rem; flex: none; }
  #conmonths { display: grid; grid-auto-flow: column; grid-auto-columns: .6875rem; gap: .1875rem;
               font-size: .62rem; color: var(--ink-3); }
  .conheat-body { display: flex; gap: .25rem; }
  #condows { width: 1.5rem; flex: none; display: grid; grid-template-rows: repeat(7, .6875rem); gap: .1875rem; }
  #condows span { font-size: .6rem; color: var(--ink-3); line-height: .6875rem; }
  #snheat { display: grid; grid-template-rows: repeat(7, .6875rem); grid-auto-flow: column;
            grid-auto-columns: .6875rem; gap: .1875rem; }
  #snheat i { background: rgba(255,255,255,.045); border-radius: 2.5px; cursor: pointer;
              outline: 1px solid transparent; outline-offset: 1px; transition: outline-color 0.21s; }
  #snheat i:hover { outline-color: rgba(255,255,255,.35); }
  #snheat i.selected { outline-color: var(--accent); outline-width: 2px; }
  #conlegend { display: flex; align-items: center; gap: .3rem; margin-top: .9rem;
               font-size: .72rem; color: var(--ink-3); }
  #conlegend i { width: .6875rem; height: .6875rem; border-radius: 2.5px; display: inline-block; }
  #conlegend .cl0 { background: rgba(255,255,255,.045); }
  #conlegend .cl1 { background: rgba(10,132,255,.30); }
  #conlegend .cl2 { background: rgba(10,132,255,.50); }
  #conlegend .cl3 { background: rgba(10,132,255,.70); }
  #conlegend .cl4 { background: rgba(10,132,255,.90); }
  #condetail { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid rgba(255,255,255,.06); }
  #condetail .drow { padding: .22rem 0; font-size: .86rem; }
  #condetail .drow.done { color: var(--ink); }
  #condetail .drow.miss { color: var(--ink-3); }

  /* top-right icon cluster: #acct's width varies (wide "Sign in with Google" pill when signed
     out, compact circle when signed in), so its siblings live in a flex row rather than each
     having their own independent `right` offset - which would break the moment #acct resized. */
  #topRightBar { position: fixed; top: 1.3rem; right: 1.5rem; z-index: 5;
                 display: flex; align-items: center; gap: .8rem; transition: opacity 1.12s var(--ease); }
  body.focusing #topRightBar { opacity: .15; }
  body.focusing #topRightBar:hover { opacity: 1; }
  /* signed out: a proper labeled "Sign in with Google" pill, the recognizable G logo doing the
     branding work - signed in: collapses back to the original compact avatar circle */
  #acct, #authGateGo, #authPickEmail { height: 1.875rem; border-radius: 999px; border: 1px solid var(--hair-2); background: var(--glass);
          color: var(--ink-2); font-size: .78rem; font-weight: 600; cursor: pointer;
          display: flex; align-items: center; gap: .5rem; padding: 0 .9rem;
          transition: background 0.35s, color 0.35s, border-color 0.35s; }
  #acct:hover, #authGateGo:hover, #authPickEmail:hover { background: var(--glass-2); color: var(--ink); }
  #acct.on { width: 1.875rem; padding: 0; justify-content: center; color: var(--green); border-color: rgba(48,209,88,.4); }
  #acct svg { flex: none; }
  #acct.on svg { display: none; }
  #tuthelp, #adminbtn, #installbtn, #feedbackbtn, #feedbackMailFallback, #themetoggle { width: 1.875rem; height: 1.875rem; border-radius: 50%; border: 1px solid var(--hair-2);
             background: var(--glass); color: var(--ink-2); font-size: .82rem; font-weight: 600;
             cursor: pointer; display: grid; place-items: center; transition: background 0.35s, color 0.35s; }
  #tuthelp:hover, #adminbtn:hover, #installbtn:hover, #feedbackbtn:hover, #feedbackMailFallback:hover, #themetoggle:hover { background: var(--glass-2); color: var(--ink); }
  #adminbtn[hidden], #installbtn[hidden], #feedbackbtn[hidden], #feedbackMailFallback[hidden] { display: none; }   /* own display:grid above outranks the UA [hidden] rule otherwise */
  /* an actual icon, not the 👥 emoji it replaced - emoji glyphs render through the OS's own emoji
     font (not this app's font stack), so their exact position within their own em-box isn't
     something a CSS nudge can reliably target across different machines. An SVG (same approach
     already used for the Google "G" logo) centers identically everywhere, no font involved. */
  #adminbtn svg, #themetoggle svg { width: 1rem; height: 1rem; }
  #snbanner { margin-top: 1.4rem; color: #FF9F0A; border-color: rgba(255,159,10,.45); }

  #tip { position: fixed; z-index: 6; pointer-events: none; background: rgba(20,25,38,.85);
         border: 1px solid var(--hair-2); border-radius: 10px; padding: .5rem .8rem;
         font-size: .8rem; backdrop-filter: blur(14px); box-shadow: 0 10px 30px rgba(0,0,0,.45);
         opacity: 0; transition: opacity 0.21s; white-space: nowrap; }
  #hint { position: fixed; right: 1.6rem; bottom: 1.3rem; z-index: 3; font-size: .7rem;
          letter-spacing: .16em; text-transform: uppercase; color: var(--ink-3); transition: opacity 0.7s; }

  /* recent-activity log: "wait, what did I just click?" - a scrollable feed like a game chat
     log (oldest on top, newest at the bottom, auto-scrolled down) of your last 100 actions,
     so an accidental checkbox click is easy to spot and undo */
  #actlog { position: fixed; left: 1.6rem; bottom: 1.3rem; z-index: 3; width: 18.75rem;
            transition: opacity 0.7s; pointer-events: none; }
  #actlog .albl { font-size: .65rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
                  color: var(--ink-3); margin-bottom: .3rem; display: flex; align-items: center; }
  /* a state toggle, not a rare action - stays visible at all times rather than hover-revealed,
     same reasoning as the section header's hide-completed eye icon elsewhere in the app */
  #actlog .collapse { all: unset; pointer-events: auto; cursor: pointer; margin-left: auto;
                       color: var(--ink-2); font-size: 1.05rem; line-height: 1;
                       transition: transform 0.28s var(--ease), color 0.2s; }
  #actlog .collapse:hover { color: var(--ink); }
  #actlog .collapse.collapsed { transform: rotate(-90deg); }
  #actlog .feed { max-height: 15rem; overflow-y: auto; pointer-events: auto;
                  display: flex; flex-direction: column; }
  #actlog .row { display: flex; align-items: center; gap: .4rem; font-size: .76rem; color: var(--ink-2);
                 padding: .1rem 0; flex: none; }
  #actlog .row .txt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* newest entry is the last row in the feed - chat convention, oldest at top */
  #actlog .feed .row:last-child .txt { color: var(--ink); }
  #actlog .undo { pointer-events: auto; flex: none; background: rgba(255,255,255,.1); border: none;
                  color: var(--ink); font-size: .66rem; padding: .12rem .45rem; border-radius: 5px; cursor: pointer; }
  #actlog .undo:hover { background: rgba(255,255,255,.18); }

  /* achievement unlock toast */
  #toast { position: fixed; bottom: 2rem; left: 50%; z-index: 9; pointer-events: none;
           display: flex; align-items: center; gap: .8rem; padding: .7rem 1.2rem;
           background: rgba(20,25,38,.9); border: 1px solid var(--hair-2); border-radius: 14px;
           backdrop-filter: blur(18px); box-shadow: 0 14px 40px rgba(0,0,0,.5);
           opacity: 0; transform: translate(-50%, 140%); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
  #toast.show { opacity: 1; transform: translate(-50%, 0); }
  #toast .glyph { font-size: 1.4rem; color: var(--accent); }
  #toast .tx b { display: block; font-size: .88rem; }
  #toast .tx span { font-size: .72rem; color: var(--ink-2); }

  /* achievements grid */
  #achgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(3.875rem, 1fr)); gap: .5rem; margin-top: .2rem; }
  .ach { display: flex; flex-direction: column; align-items: center; gap: .3rem; padding: .55rem .2rem;
         text-align: center; cursor: pointer; border-radius: 9px; transition: background 0.28s; }
  .ach:hover { background: rgba(255,255,255,.04); }
  /* same selected-tint convention as .admrow.sel - a solid background tint reads more clearly on a
     tile this size than the outline #snheat's tiny heatmap cells use */
  .ach.selected { background: rgba(10,132,255,.14); }
  .ach .glyph { font-size: 1.25rem; color: var(--ink-3); opacity: .5; }
  .ach.on .glyph { color: var(--accent); opacity: 1; }
  .ach .nm { font-size: .6rem; color: var(--ink-3); line-height: 1.2; }
  .ach.on .nm { color: var(--ink-2); }
  /* same layout convention as #condetail below the consistency heatmap - a plain-language
     expansion of whichever tile/cell was last clicked, not a second competing panel */
  #achdetail { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid rgba(255,255,255,.06); }
  #achdetail .drow { padding: .22rem 0; font-size: .86rem; }
  #achdetail .drow.done { color: var(--ink); }
  #achdetail .drow.miss { color: var(--ink-3); }

  /* first-run guided tour: a spotlight cutout (box-shadow trick - one element darkens the whole
     screen except a hole around the current target) plus a glass card, same visual language as
     the rest of the app. #tutCatch blocks interaction with the app underneath while the tour is
     up, since the spotlight itself has no hit-testing of its own (pointer-events:none). */
  #tutCatch { position: fixed; inset: 0; z-index: 29; }
  #tutSpot { position: fixed; z-index: 30; border-radius: 16px; pointer-events: none;
             box-shadow: 0 0 0 9999px rgba(4,6,14,.82); border: 1.5px solid rgba(255,255,255,.32);
             opacity: 1; transition: left 0.5s var(--ease), top 0.5s var(--ease), width 0.5s var(--ease),
             height 0.5s var(--ease), opacity 0.3s var(--ease), border-radius 0.5s var(--ease); }
  #tutCard { position: fixed; z-index: 31; width: 20rem; padding: 1.3rem 1.4rem 1.2rem;
             background: rgba(20,25,38,.96); border: 1px solid var(--hair-2); border-radius: 18px;
             backdrop-filter: blur(22px); box-shadow: 0 20px 60px rgba(0,0,0,.55); opacity: 0;
             transition: left 0.5s var(--ease), top 0.5s var(--ease), opacity 0.35s var(--ease); }
  #tutCard.show { opacity: 1; }
  #tutCard h3 { font-size: 1.05rem; font-weight: 600; margin-bottom: .5rem; }
  #tutCard p { font-size: .86rem; color: var(--ink-2); line-height: 1.55; margin-bottom: 1.2rem; }
  #tutDots { display: flex; gap: .35rem; margin-bottom: 1rem; }
  #tutDots i { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-3);
               transition: background 0.3s, transform 0.3s; }
  #tutDots i.on { background: var(--accent); transform: scale(1.6); }
  #tutCard .tutrow { display: flex; align-items: center; justify-content: space-between; gap: .8rem; }
  #tutskip { all: unset; cursor: pointer; color: var(--ink-3); font-size: .78rem; transition: color 0.2s; }
  #tutskip:hover { color: var(--ink-2); }
  .tutnav { display: flex; gap: .5rem; }
  #tutback, #tutnext { border: none; font: inherit; font-weight: 500; font-size: .82rem; padding: .48rem 1.05rem;
                        border-radius: 999px; cursor: pointer; transition: background 0.2s, opacity 0.2s, transform 0.2s; }
  #tutback { background: var(--glass-2); color: var(--ink-2); }
  #tutback:hover { background: rgba(255,255,255,.12); }
  #tutback:disabled { visibility: hidden; }
  #tutnext { background: var(--accent); color: #fff; }
  #tutnext:hover { transform: translateY(-1px); }
  #tut.closing #tutSpot, #tut.closing #tutCatch { opacity: 0; }
  /* admin dashboard - every signed-up user, and a per-user usage drill-down reusing the same
     stat functions the Stats page itself uses, just pointed at someone else's db blob */
  #adminPanel { position: fixed; inset: 0; z-index: 20; background: #07090f; overflow-y: auto;
                padding: 5.6rem 1.5rem 2.5rem; }
  .admHead { max-width: 68.75rem; margin: 0 auto 1.5rem; display: flex; align-items: center; gap: .8rem; }
  .admHead h2 { font-size: 1.3rem; font-weight: 600; }
  .admHead .meta { color: var(--ink-3); font-size: .84rem; margin-left: auto; }
  .admTabs { display: flex; gap: .2rem; background: rgba(255,255,255,.05); border: 1px solid var(--hair);
             border-radius: 999px; padding: .1875rem; }
  .admTab { all: unset; box-sizing: border-box; cursor: pointer; font: inherit; font-size: .82rem;
            font-weight: 500; color: var(--ink-2); padding: .35rem 1rem; border-radius: 999px;
            transition: background 0.2s, color 0.2s; }
  .admTab:hover { color: var(--ink); }
  .admTab.on { background: rgba(255,255,255,.12); color: var(--ink); }
  /* fixed to the viewport, not the (centered, max-width-capped) header row, so it's always in the
     same reachable corner regardless of scroll position or how wide the admin panel itself is -
     previously this reused .ghost, whose opacity:0 default is only revealed by hover selectors
     scoped to .proj/.section-head/.phead - none of which match .admHead, so it was invisible. */
  #admClose { position: fixed; z-index: 21; top: 1.3rem; left: 1.5rem; }
  #admBody { max-width: 68.75rem; margin: 0 auto; display: grid; grid-template-columns: 20rem 1fr; gap: 1.4rem; align-items: start; }
  @media (max-width: 860px) { #admBody { grid-template-columns: 1fr; } }
  #admList { background: var(--glass); border: 1px solid var(--hair-2); border-radius: 14px;
             padding: .5rem; max-height: 75vh; overflow-y: auto; }
  #admList .pill { display: block; width: 100%; margin-top: .4rem; text-align: center; font-size: .82rem; padding: .5rem; }
  .admrow { display: flex; align-items: center; gap: .7rem; padding: .55rem .6rem; border-radius: 9px;
            cursor: pointer; transition: background 0.2s; }
  .admrow:hover { background: rgba(255,255,255,.04); }
  .admrow.sel { background: rgba(10,132,255,.14); }
  .admrow .mono { flex: none; }
  .admwho { min-width: 0; flex: 1; display: flex; flex-direction: column; }
  .admwho b { font-size: .86rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .admwho span { font-size: .74rem; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .admwhen { flex: none; font-size: .7rem; color: var(--ink-3); }
  #admDetail { background: var(--glass); border: 1px solid var(--hair-2); border-radius: 14px; padding: 1.3rem; min-height: 12.5rem; }
  .admDetailHead { display: flex; align-items: center; gap: .8rem; margin-bottom: 1.3rem; }
  .admDetailHead b { font-size: 1.1rem; font-weight: 600; }
  .admDetailHead span { color: var(--ink-3); font-size: .82rem; }
  .admProjects { margin-top: 1.4rem; display: flex; flex-direction: column; gap: .2rem; }
  .admTasks { display: flex; flex-direction: column; margin: 0 0 .9rem 2.7rem; }
  .admTaskRow { display: flex; align-items: center; gap: .5rem; font-size: .82rem; color: var(--ink-2); padding: .12rem 0; }
  .admTaskRow.sub { margin-left: 1.2rem; opacity: .85; }
  .admTaskRow.done { color: var(--ink-3); text-decoration: line-through; }
  .admTaskRow.empty { color: var(--ink-3); font-style: italic; }
  .admTaskMark { width: .875rem; flex: none; text-align: center; opacity: .6; }
  .admTaskText { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .admTaskDue { flex: none; font-size: .74rem; color: var(--ink-3); }
  .admFeedback { margin-top: 1.6rem; padding-top: 1.2rem; border-top: 1px solid var(--hair); }
  .admFeedback h4 { font-size: .74rem; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
    color: var(--ink-2); margin-bottom: .7rem; }
  .admFbRow { padding: .6rem 0; border-bottom: 1px solid rgba(255,255,255,.05); }
  .admFbRow:last-child { border-bottom: none; }
  .admFbHead { display: flex; align-items: center; gap: .6rem; margin-bottom: .3rem; }
  .admFbHead .chip { font-size: .68rem; padding: .12rem .5rem; }
  .admFbHead .admwhen { flex: 1; text-align: right; }
  .admFbMsg { font-size: .84rem; color: var(--ink); line-height: 1.5; white-space: pre-wrap; }
  .admFbSteps { margin-top: .35rem; font-size: .78rem; color: var(--ink-2); line-height: 1.5; white-space: pre-wrap; }
  .admFbSteps b { color: var(--ink-3); font-weight: 600; }
  /* combined cross-user feedback feed - see admin.js */
  #admFeedbackBody { max-width: 68.75rem; margin: 0 auto; }
  #admFeedbackRefresh { margin-bottom: 1rem; font-size: .82rem; padding: .45rem 1rem; }
  #admFeedbackList { background: var(--glass); border: 1px solid var(--hair-2); border-radius: 14px;
                      padding: .3rem 1.3rem; }
  .admFbRow-global { cursor: pointer; padding: .8rem 0; transition: background 0.2s; margin: 0 -1.3rem; padding-left: 1.3rem; padding-right: 1.3rem; }
  .admFbRow-global:hover { background: rgba(255,255,255,.03); }
  .admFbWho { font-size: .82rem; font-weight: 600; color: var(--ink); }

  /* install-as-app prompt, shown once after a genuinely first-ever sign-in */
  #installPrompt { position: fixed; z-index: 25; bottom: 1.6rem; right: 1.6rem; width: 18.75rem;
                    padding: 1.1rem 1.2rem; background: rgba(20,25,38,.96); border: 1px solid var(--hair-2);
                    border-radius: 16px; backdrop-filter: blur(20px); box-shadow: 0 16px 44px rgba(0,0,0,.5);
                    animation: rise 0.6s var(--ease) both; }
  #installPrompt h3 { font-size: .95rem; font-weight: 600; margin-bottom: .4rem; }
  #installPrompt p { font-size: .8rem; color: var(--ink-2); line-height: 1.5; margin-bottom: 1rem; }
  #installPrompt .instrow { display: flex; align-items: center; justify-content: space-between; gap: .8rem; }
  #installSkip { all: unset; cursor: pointer; color: var(--ink-3); font-size: .78rem; transition: color 0.2s; }
  #installSkip:hover { color: var(--ink-2); }
  #installGo { border: none; font: inherit; font-weight: 500; font-size: .82rem; padding: .48rem 1.05rem;
               border-radius: 999px; cursor: pointer; background: var(--accent); color: #fff; transition: transform 0.2s; }
  #installGo:hover { transform: translateY(-1px); }

  /* name-this-session prompt, shown right after a completed timer run logs itself. Same corner-card
     footprint as #installPrompt but bottom-LEFT so the two can never visually collide, however
     unlikely (installPrompt fires once ever, on first sign-in; this can fire any time a session
     finishes) - simpler than adding stacking/queueing logic for an overlap that may never happen. */
  #sessionNamePrompt { position: fixed; z-index: 25; bottom: 1.6rem; left: 1.6rem; width: 18.75rem;
                    padding: 1.1rem 1.2rem; background: rgba(20,25,38,.96); border: 1px solid var(--hair-2);
                    border-radius: 16px; backdrop-filter: blur(20px); box-shadow: 0 16px 44px rgba(0,0,0,.5);
                    animation: rise 0.6s var(--ease) both; }
  #sessionNamePrompt[hidden] { display: none; }
  #sessionNamePrompt h3 { font-size: .95rem; font-weight: 600; margin-bottom: .4rem; }
  #sessionNamePrompt p { font-size: .8rem; color: var(--ink-2); line-height: 1.5; margin-bottom: .8rem; }
  #sessionNamePrompt input { width: 100%; font: inherit; font-size: .86rem; color: var(--ink);
    background: rgba(255,255,255,.05); border: 1px solid var(--hair); border-radius: 8px;
    padding: .5rem .65rem; box-sizing: border-box; margin-bottom: .9rem; }
  #sessionNamePrompt input:focus { outline: none; border-color: rgba(10,132,255,.5); }
  #sessionNamePrompt .instrow { display: flex; align-items: center; justify-content: space-between; gap: .8rem; }
  #snpSkip { all: unset; cursor: pointer; color: var(--ink-3); font-size: .78rem; transition: color 0.2s; }
  #snpSkip:hover { color: var(--ink-2); }
  #snpSave { border: none; font: inherit; font-weight: 500; font-size: .82rem; padding: .48rem 1.05rem;
             border-radius: 999px; cursor: pointer; background: var(--accent); color: #fff; transition: transform 0.2s; }
  #snpSave:hover { transform: translateY(-1px); }

  /* Stats page only, 50% slower than their shared base rule - duration-only overrides so the
     property list/easing still comes from the base declaration, just stretched. #stats-scoped
     (higher specificity than the plain class selectors below) or a later cascade position wins
     out for the couple of ids (#stbar/#snbar) that share a rule with a non-Stats twin
     (#tcbar on Tasks, #fsnbar/#ftcbar on Focus) - those twins are deliberately left untouched. */
  #stats.view.on .anim { animation-duration: 1.155s; }
  #stats.view.on .anim:nth-child(2) { animation-delay: .105s; }
  #stats.view.on .anim:nth-child(3) { animation-delay: .21s; }
  #stats.view.on .anim:nth-child(4) { animation-delay: .315s; }
  #stats.view.on .anim:nth-child(5) { animation-delay: .42s; }
  #stbar i { transition-duration: 1.365s; }
  #snbar i { transition-duration: 1.365s; }
  #snbar.pulse { animation-duration: 1.89s; }
  .colwrap { transition-duration: .525s; }
  .seg { transition-duration: 1.365s, .42s; }
  .approw .bar { transition-duration: 1.365s; }
  #snheat i { transition-duration: .315s; }
