/* Night Shift Calculator — base styles (theme tokens, typography, reset).
   Component-level CSS is colocated in the React files. */

:root,
[data-theme="dark"] {
  color-scheme: dark;
  /* iOS system colours: grouped backgrounds (black page, raised cards),
     label greys and the system tints, so the app sits naturally next to
     Apple's own apps. */
  --bg: #000000;
  --bg-1: #1c1c1e;
  --bg-2: #2c2c2e;
  --bg-3: #3a3a3c;
  --line: #38383a;
  --line-soft: #2c2c2e;
  --ink: #f5f5f7;
  --ink-dim: #aeaeb2;
  --ink-faint: #98989d;
  --accent: #ff9f0a;
  --accent-ink: #000000;
  --sp1: #0a84ff;
  --sp2: #bf5af2;
  --am: #30d158;
  /* Straight Day: iOS teal (dark chip text on it reads 9:1). */
  --sd: #40c8e0;
  --holiday: #ff453a;
  --warn: #ffd60a;
  --ok: #30d158;
  --extra: #ff375f;
  --chip-ink: #111216;
  /* Gray fills (segmented controls, plain buttons) and the switch. */
  --fill: rgba(118, 118, 128, 0.24);
  --seg-pill: #636366;
  --switch-on: #30d158;
  --card-edge: rgba(255, 255, 255, 0.05);
  --glass-edge: rgba(255, 255, 255, 0.1);
  --shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.7);
  --surface-translucent: rgba(30, 30, 32, 0.8);
  --r: 16px;
  --r-sm: 12px;
  --r-xs: 6px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* Motion: damped springs. Each curve is a mass on a spring released from
     rest, sampled from the physics (x'' = -w²(x - 1) - 2ζw·x') into a CSS
     linear() easing, so things overshoot, swing back and settle the way real
     objects do. The duration only sets how stiff the spring is; the shape
     (how far it overshoots, how many swings) comes from the damping ratio ζ.
     Browsers without linear() get a single-overshoot cubic-bezier instead.
     - bouncy (ζ 0.42, ~23% overshoot): pops, ticks, chevrons, press release
     - snappy (ζ 0.62, ~8%): sliding pills, cards rising in, dialogs
     - soft (ζ 0.78, ~2%): big moves (a sheet sliding up the whole screen)
     - wobbly (ζ 0.3, ~37%): one-off flourishes (the logo landing) */
  --ease-bouncy: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-snappy: cubic-bezier(0.32, 1.25, 0.5, 1);
  --ease-soft: cubic-bezier(0.22, 1.08, 0.36, 1);
  --ease-wobbly: cubic-bezier(0.3, 1.9, 0.45, 0.85);
  /* Press feedback: buttons give under the finger and spring back. Inline
     transitions list var(--press) so they keep it. */
  --press: scale 0.34s var(--ease-bouncy);
}
/* Also on [data-theme]: the app sets the theme on <body> too, and the dark
   theme's block above re-declares the fallbacks there, which would hide
   these springs from everything on the page in dark mode. */
@supports (transition-timing-function: linear(0, 1)) {
  :root,
  [data-theme] {
    --ease-bouncy: linear(
      0,
      0.055,
      0.194,
      0.383,
      0.588,
      0.783,
      0.952,
      1.083,
      1.172,
      1.22,
      1.233,
      1.219,
      1.185,
      1.141,
      1.093,
      1.047,
      1.009,
      0.979,
      0.959,
      0.948,
      0.946,
      0.949,
      0.957,
      0.968,
      0.979,
      0.99,
      0.999,
      1.005,
      1.01,
      1.012,
      1.013,
      1.012,
      1.01,
      1.007,
      1.005,
      1.002,
      1
    );
    --ease-snappy: linear(
      0,
      0.044,
      0.151,
      0.292,
      0.445,
      0.595,
      0.728,
      0.841,
      0.93,
      0.996,
      1.041,
      1.068,
      1.081,
      1.083,
      1.078,
      1.068,
      1.056,
      1.043,
      1.031,
      1.02,
      1.011,
      1.004,
      0.999,
      0.996,
      0.994,
      0.993,
      0.993,
      0.994,
      1
    );
    --ease-soft: linear(
      0,
      0.046,
      0.156,
      0.293,
      0.435,
      0.568,
      0.684,
      0.78,
      0.856,
      0.913,
      0.955,
      0.984,
      1.002,
      1.013,
      1.018,
      1.02,
      1.019,
      1.017,
      1.014,
      1.012,
      1.009,
      1.007,
      1
    );
    --ease-wobbly: linear(
      0,
      0.071,
      0.252,
      0.498,
      0.759,
      0.999,
      1.188,
      1.312,
      1.368,
      1.362,
      1.308,
      1.224,
      1.127,
      1.033,
      0.955,
      0.899,
      0.868,
      0.862,
      0.876,
      0.904,
      0.939,
      0.975,
      1.006,
      1.031,
      1.046,
      1.051,
      1.049,
      1.04,
      1.028,
      1.015,
      1.002,
      0.992,
      0.985,
      0.981,
      0.981,
      0.984,
      0.988,
      0.993,
      0.998,
      1.002,
      1.005,
      1.007,
      1.007,
      1.007,
      1
    );
  }
}

[data-theme="light"] {
  color-scheme: light;
  --bg: #f2f2f7;
  --bg-1: #ffffff;
  --bg-2: #f2f2f7;
  --bg-3: #e5e5ea;
  --line: #d1d1d6;
  --line-soft: #e5e5ea;
  --ink: #1d1d1f;
  --ink-dim: #6e6e73;
  --ink-faint: #76767b;
  /* The system tints, darkened where needed so text in them stays
     readable on white (4.5:1). */
  --accent: #b84f00;
  --accent-ink: #ffffff;
  --sp1: #0071e3;
  --sp2: #8944ab;
  --am: #248a3d;
  /* Darkened so white chip text on it reads 4.5:1. */
  --sd: #00809a;
  --holiday: #d70015;
  --warn: #a35f00;
  --ok: #248a3d;
  --extra: #d30f45;
  --chip-ink: #ffffff;
  --fill: rgba(118, 118, 128, 0.12);
  --seg-pill: #ffffff;
  --switch-on: #34c759;
  --card-edge: transparent;
  --glass-edge: rgba(0, 0, 0, 0.06);
  --shadow: 0 16px 40px -20px rgba(0, 0, 0, 0.18);
  --surface-translucent: rgba(255, 255, 255, 0.8);
}

* {
  box-sizing: border-box;
}
html,
body {
  margin: 0;
  padding: 0;
}
/* No rubber-band at the ends of the page. It has to be set on the root:
   on body it doesn't reach the page's own scrolling, so the whole app
   (tab bar included) could still be dragged past its ends and spring back. */
html {
  overscroll-behavior: none;
}
/* San Francisco on Apple devices; Geist (loaded in index.html) elsewhere. */
body {
  font-family:
    -apple-system, BlinkMacSystemFont, "SF Pro Text", "Geist", system-ui,
    sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}

button {
  font-family: inherit;
}
input,
select,
button {
  font-family: inherit;
  color: inherit;
}

/* Figures: the body font with tabular digits (as Apple's apps set money
   and times), so amounts line up and don't jitter while they count. Real
   monospace is kept for the worked-out maths. */
.mono {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
pre.mono {
  font-family: ui-monospace, "SF Mono", "Geist Mono", monospace;
  letter-spacing: 0;
}

#root {
  min-height: 100vh;
}

/* Loading screen in index.html: an overlay above the app. A fire truck drives
   a night road while the scripts load: it rides on springs, hits a bump and
   bobs over it, headlight on and lights flashing. Flooring it off the road
   (the nose lifts as the weight shifts back) is driven from index.html,
   dashOff, as is the flight into the header; .is-dashing only marks that
   phase. .is-ready drops the
   app logo in under gravity, bouncing to rest, its ring draws and its hands
   wind round; .is-flying clears the backdrop while the logo and title fly
   into the header (.is-leaving is the plain-fade fallback). See
   nscSplashDone. */
.nsc-splash {
  position: fixed;
  inset: 0;
  z-index: 200;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  transition:
    opacity 0.45s ease,
    visibility 0s linear 0.45s;
}
/* The backdrop is its own layer, faded by opacity, so clearing it while the
   logo flies costs the GPU a blend instead of repainting the whole screen
   on every frame. */
.nsc-splash::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--bg);
  transition: opacity 0.5s ease;
}
.nsc-splash.is-flying {
  pointer-events: none;
}
.nsc-splash.is-flying::before {
  opacity: 0;
}
.nsc-splash.is-leaving {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
/* Night sky: a soft accent glow on the horizon, a moon and twinkling stars.
   Light theme keeps only the glow. */
.nsc-sky {
  position: absolute;
  inset: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;
  background: radial-gradient(
    ellipse 70% 40% at 50% 58%,
    color-mix(in oklab, var(--accent) 10%, transparent),
    transparent 70%
  );
}
.nsc-splash.is-flying .nsc-sky {
  opacity: 0;
}
.nsc-star {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: #f5f3ee;
  opacity: 0;
  animation: nscTwinkle 2.6s ease-in-out infinite;
}
.nsc-star.is-big {
  width: 3px;
  height: 3px;
  box-shadow: 0 0 6px 1px rgba(245, 243, 238, 0.5);
}
.nsc-moon {
  position: absolute;
  top: 19%;
  right: 18%;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  box-shadow:
    inset -7px 3px 0 0 #f1e6c8,
    0 0 24px 2px rgba(241, 230, 200, 0.12);
  transform: rotate(-20deg);
  animation: nscFadeIn 1.2s ease-out 0.2s backwards;
}
[data-theme="light"] .nsc-star,
[data-theme="light"] .nsc-moon {
  display: none;
}
@keyframes nscTwinkle {
  0%,
  100% {
    opacity: 0.15;
    transform: scale(0.7);
  }
  50% {
    opacity: 0.85;
    transform: scale(1);
  }
}
/* Truck and logo share one spot, so the logo lands where the truck was. */
.nsc-stage {
  position: relative;
  display: grid;
  width: min(100%, 520px);
  margin-bottom: 8px;
}
.nsc-stage > * {
  grid-area: 1 / 1;
}
.nsc-splash.is-ready .nsc-road {
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity 0.3s ease,
    transform 0.3s ease;
}
/* The logo waits above the stage, then falls onto it: gravity on the way
   down (ease-in), squash on impact, and each bounce lower and quicker than
   the last (a restitution of about 0.4), as a dropped ball comes to rest. */
.nsc-brand {
  position: relative;
  justify-self: center;
  align-self: center;
  width: 96px;
  height: 96px;
  opacity: 0;
  transform-origin: 50% 100%;
}
.nsc-brand svg {
  position: relative;
  display: block;
  border-radius: 21px;
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.65);
}
.nsc-brand-glow {
  position: absolute;
  inset: -28px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in oklab, var(--accent) 38%, transparent),
    transparent 68%
  );
  opacity: 0;
}
/* Ripple and dust kicked up where the logo hits. */
.nsc-brand-wave {
  position: absolute;
  inset: -6px;
  border-radius: 28px;
  border: 2px solid color-mix(in oklab, var(--accent) 70%, transparent);
  opacity: 0;
}
.nsc-dust {
  position: absolute;
  bottom: -2px;
  left: 50%;
  width: 10px;
  height: 10px;
  margin-left: -5px;
  border-radius: 50%;
  background: var(--ink-faint);
  opacity: 0;
}
.nsc-brand-ring {
  stroke-dasharray: 377;
  stroke-dashoffset: 377;
}
.nsc-brand-hands {
  stroke-dasharray: 80;
  stroke-dashoffset: 80;
  transform-box: view-box;
  transform-origin: 96px 96px;
}
.nsc-brand-dot {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0);
}
.nsc-splash.is-ready .nsc-brand {
  animation: nscLogoDrop 0.9s 0.05s both;
}
.nsc-splash.is-ready .nsc-brand-wave {
  animation: nscWave 0.7s ease-out 0.42s forwards;
}
.nsc-splash.is-ready .nsc-dust-l {
  animation: nscDustL 0.6s ease-out 0.42s forwards;
}
.nsc-splash.is-ready .nsc-dust-r {
  animation: nscDustR 0.6s ease-out 0.42s forwards;
}
.nsc-splash.is-ready .nsc-brand-glow {
  animation: nscGlow 1.1s ease-out 0.42s forwards;
}
.nsc-splash.is-ready .nsc-brand-ring {
  animation: nscDraw 0.6s cubic-bezier(0.65, 0, 0.35, 1) 0.4s forwards;
}
/* The hands draw in as they wind round, then swing back and settle on
   the hour like a clock spring let go. */
.nsc-splash.is-ready .nsc-brand-hands {
  animation:
    nscDraw 0.3s ease-out 0.5s forwards,
    nscWind 0.95s var(--ease-snappy) 0.5s backwards;
}
.nsc-splash.is-ready .nsc-brand-dot {
  animation: nscDotIn 0.7s var(--ease-wobbly) 0.85s forwards;
}
.nsc-splash-sub {
  transition: opacity 0.3s ease;
}
.nsc-splash.is-ready .nsc-splash-sub {
  opacity: 0;
}
/* The glow and logo shadow belong to the splash, not the header. */
.nsc-splash.is-flying .nsc-brand-glow {
  animation: nscGlowOut 0.3s ease-out forwards;
}
/* The header's own logo and title stay hidden until the splash copies
   land on them, so the swap is invisible. */
.nsc-booting [data-splash-target],
.nsc-handoff [data-splash-target] {
  visibility: hidden;
}
/* The header logo takes the landing: a squash, then it springs back. */
.nsc-landed {
  animation: nscBoing 0.75s;
}
@keyframes nscLogoDrop {
  0% {
    opacity: 0;
    transform: translateY(-170px) scale(0.92, 1.1);
    animation-timing-function: cubic-bezier(0.5, 0, 1, 0.6);
  }
  12% {
    opacity: 1;
  }
  41% {
    transform: translateY(0) scale(1.2, 0.78);
    animation-timing-function: cubic-bezier(0.1, 0.6, 0.4, 1);
  }
  58% {
    opacity: 1;
    transform: translateY(-30px) scale(0.96, 1.05);
    animation-timing-function: cubic-bezier(0.6, 0, 0.9, 0.4);
  }
  74% {
    transform: translateY(0) scale(1.08, 0.92);
    animation-timing-function: cubic-bezier(0.1, 0.6, 0.4, 1);
  }
  84% {
    transform: translateY(-7px) scale(0.99, 1.01);
    animation-timing-function: cubic-bezier(0.6, 0, 0.9, 0.4);
  }
  93% {
    transform: translateY(0) scale(1.02, 0.98);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}
@keyframes nscWave {
  from {
    opacity: 0.6;
    transform: scale(0.9, 0.7);
  }
  to {
    opacity: 0;
    transform: scale(1.9, 1.5);
  }
}
@keyframes nscDustL {
  from {
    opacity: 0.45;
    transform: translate(-20px, 0) scale(0.5);
  }
  to {
    opacity: 0;
    transform: translate(-72px, -12px) scale(1.8);
  }
}
@keyframes nscDustR {
  from {
    opacity: 0.45;
    transform: translate(20px, 0) scale(0.5);
  }
  to {
    opacity: 0;
    transform: translate(72px, -12px) scale(1.8);
  }
}
@keyframes nscWind {
  from {
    transform: rotate(-300deg);
  }
  to {
    transform: rotate(0deg);
  }
}
@keyframes nscGlowOut {
  from {
    opacity: 0.55;
  }
  to {
    opacity: 0;
    transform: scale(0.6);
  }
}
@keyframes nscGlow {
  0% {
    opacity: 0;
    transform: scale(0.7);
  }
  45% {
    opacity: 1;
  }
  100% {
    opacity: 0.55;
    transform: scale(1);
  }
}
@keyframes nscDotIn {
  to {
    transform: scale(1);
  }
}
/* While the splash is up, screen entrances wait, so the cards come in as it
   lifts rather than hidden underneath it. On that first entrance (.nsc-intro)
   they fall into place from above, a little slower and more staggered, as
   if dropping out of the header the logo just flew into. */
.nsc-booting #root * {
  animation-play-state: paused !important;
}
.nsc-intro {
  --rise-from: -30px;
  --rise-dur: 0.85s;
  --rise-step: 0.07s;
}
/* Fire truck that drives across the road on a loop, kicking up tyre smoke.
   The lane is as wide as the road, so translating it by 100% plus the
   truck's width carries the truck from just off the left edge to just off
   the right one at any screen size. */
.nsc-road {
  position: relative;
  width: 100%;
  height: 110px;
  align-self: end;
  overflow: hidden;
  border-bottom: 2px dashed var(--line);
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent,
    #000 12%,
    #000 88%,
    transparent
  );
  mask-image: linear-gradient(
    90deg,
    transparent,
    #000 12%,
    #000 88%,
    transparent
  );
}
/* A small hump mid-road: the truck's wheels go over it (see nscHopF/R). */
.nsc-bump {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 22px;
  height: 5px;
  margin-left: -11px;
  border-radius: 11px 11px 0 0;
  background: var(--line);
}
.nsc-lane {
  position: absolute;
  inset: 0;
  animation: nscDrive 2.2s linear infinite;
}
.nsc-truck {
  position: absolute;
  right: 100%;
  bottom: 0;
  width: 150px;
  height: 62px;
}
/* The truck's parts are stacked layers of one 150 × 62 box (the art's
   160 × 66 units at × 0.9375), so positions below are art units × 0.9375. */
/* Positioned, so the art paints in source order with the layers around it
   (the light bar's glow under the cab, the lights on top). */
.nsc-truck svg {
  position: relative;
  display: block;
  overflow: visible;
}
.nsc-susp,
.nsc-body,
.nsc-hop {
  position: absolute;
  inset: 0;
}
/* Suspension. The body sits on springs: a constant idle jiggle (.nsc-body),
   plus the bump (.nsc-susp) — the front wheel hops first and pitches the
   nose up, then the rear, and the body rocks back and forth, smaller each
   time, until the springs settle. Timed to the drive loop, so it happens as
   each wheel reaches the hump. Flooring it (index.html, dashOff) takes the
   suspension over from wherever it is and squats it on the rear wheel. */
.nsc-susp {
  transform-origin: 75px 50.6px;
  animation: nscPitch 2.2s linear infinite;
}
.nsc-body {
  animation: nscJiggle 0.24s ease-in-out infinite alternate;
}
.nsc-hop-f {
  animation: nscHopF 2.2s linear infinite;
}
.nsc-hop-r {
  animation: nscHopR 2.2s linear infinite;
}
.nsc-wheel {
  position: absolute;
  top: 40.3px;
  width: 20.625px;
  height: 20.625px;
  animation: nscSpin 0.35s linear infinite;
}
.nsc-wheel svg {
  width: 100%;
  height: 100%;
}
.nsc-hop-r .nsc-wheel {
  left: 19.7px;
}
.nsc-hop-f .nsc-wheel {
  left: 107.8px;
}
/* The light bar (art: 7 × 4.5 at x 110 / 117, y 12.5) and its glow (r 9
   at x 113 / 121, y 14), flashing red and blue in turn. */
.nsc-light,
.nsc-glow {
  position: absolute;
}
.nsc-light {
  top: 11.7px;
  width: 6.6px;
  height: 4.2px;
  border-radius: 1.4px;
}
.nsc-glow {
  top: 4.7px;
  width: 16.9px;
  height: 16.9px;
  border-radius: 50%;
  opacity: 0.6;
  filter: blur(3.75px);
}
.nsc-light-r {
  left: 103.1px;
}
.nsc-light-b {
  left: 109.7px;
}
.nsc-glow-r {
  left: 97.5px;
}
.nsc-glow-b {
  left: 105px;
}
.nsc-light-r,
.nsc-glow-r {
  background: #ff3b3b;
  animation: nscBlink 0.5s steps(1) infinite;
}
.nsc-light-b,
.nsc-glow-b {
  background: #3b82ff;
  animation: nscBlink 0.5s steps(1) infinite reverse;
}
/* Shadow on the road (art: rx 72, ry 2.2 at 80, 65): stays put while the
   body rides. */
.nsc-shadow {
  position: absolute;
  left: 7.5px;
  top: 58.9px;
  width: 135px;
  height: 4.1px;
  border-radius: 50%;
  background: #000;
  opacity: 0.35;
}
[data-theme="light"] .nsc-shadow {
  opacity: 0.12;
}
[data-theme="light"] .nsc-beam {
  opacity: 0.55;
}
.nsc-puff {
  position: absolute;
  left: 18px;
  bottom: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--ink-faint);
  opacity: 0;
  animation: nscPuff 0.6s ease-out infinite;
}
.nsc-puff-2 {
  animation-delay: 0.15s;
}
.nsc-puff-3 {
  animation-delay: 0.3s;
}
.nsc-puff-4 {
  animation-delay: 0.45s;
}
/* Front wheel puffs: smaller and fainter, mostly hidden behind the body. */
.nsc-puff-f1,
.nsc-puff-f2 {
  left: 108px;
  width: 10px;
  height: 10px;
}
.nsc-puff-f2 {
  animation-delay: 0.3s;
}
.nsc-speed {
  position: absolute;
  right: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--ink-faint);
  transform-origin: right center;
  animation: nscStreak 0.4s ease-out infinite;
}
.nsc-speed-1 {
  top: 22px;
  width: 34px;
}
.nsc-speed-2 {
  top: 34px;
  width: 52px;
  animation-delay: 0.13s;
}
.nsc-speed-3 {
  top: 46px;
  width: 26px;
  animation-delay: 0.26s;
}
@keyframes nscDrive {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(calc(100% + 150px));
  }
}
@keyframes nscJiggle {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-0.8px);
  }
}
/* Front wheel reaches the hump at ~42% of the loop, the rear at ~58%. */
@keyframes nscHopF {
  0%,
  40%,
  47%,
  100% {
    transform: translateY(0);
  }
  43.5% {
    transform: translateY(-4px);
  }
}
@keyframes nscHopR {
  0%,
  56%,
  63%,
  100% {
    transform: translateY(0);
  }
  59.5% {
    transform: translateY(-4px);
  }
}
@keyframes nscPitch {
  0%,
  41%,
  100% {
    transform: none;
  }
  45% {
    transform: translateY(-2.5px) rotate(-2.2deg);
  }
  50% {
    transform: translateY(0.5px) rotate(0.9deg);
  }
  54% {
    transform: translateY(-0.5px) rotate(-0.4deg);
  }
  57% {
    transform: none;
  }
  61% {
    transform: translateY(-2.5px) rotate(2deg);
  }
  66% {
    transform: translateY(0.8px) rotate(-0.8deg);
  }
  71% {
    transform: translateY(-0.4px) rotate(0.35deg);
  }
  76% {
    transform: translateY(0.1px) rotate(-0.1deg);
  }
  80% {
    transform: none;
  }
}
@keyframes nscSpin {
  to {
    transform: rotate(360deg);
  }
}
@keyframes nscBlink {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0.2;
  }
}
@keyframes nscPuff {
  0% {
    opacity: 0.55;
    transform: translate(0, 0) scale(0.4);
  }
  100% {
    opacity: 0;
    transform: translate(-46px, -14px) scale(1.8);
  }
}
@keyframes nscStreak {
  0% {
    opacity: 0;
    transform: translateX(0) scaleX(0.3);
  }
  30% {
    opacity: 0.6;
  }
  100% {
    opacity: 0;
    transform: translateX(-24px) scaleX(1);
  }
}
/* Reduced motion: park the truck in the middle of the road, no smoke, no
   stars twinkling; the logo simply appears. */
@media (prefers-reduced-motion: reduce) {
  .nsc-lane,
  .nsc-body,
  .nsc-susp,
  .nsc-hop-f,
  .nsc-hop-r,
  .nsc-wheel,
  .nsc-light-r,
  .nsc-light-b,
  .nsc-glow-r,
  .nsc-glow-b,
  .nsc-letters > span,
  .nsc-dots i {
    animation: none;
  }
  .nsc-star {
    animation: none;
    opacity: 0.5;
  }
  .nsc-lane {
    transform: translateX(calc(50% + 75px));
  }
  .nsc-puff,
  .nsc-speed,
  .nsc-brand-wave,
  .nsc-dust {
    display: none;
  }
  .nsc-brand-ring,
  .nsc-brand-hands {
    stroke-dashoffset: 0;
  }
  .nsc-brand-dot {
    transform: none;
  }
  .nsc-splash.is-ready .nsc-brand,
  .nsc-splash.is-ready .nsc-brand-hands {
    animation: none;
    opacity: 1;
    transform: none;
  }
}
/* Matches the header title, which it turns into. Its letters spring up one
   after another as the screen opens. */
.nsc-splash-title {
  position: relative;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transform-origin: left center;
}
.nsc-letters > span {
  display: inline-block;
  white-space: pre;
  animation: nscLetterIn 0.8s var(--ease-bouncy) backwards;
  animation-delay: calc(var(--d) * 26ms + 120ms);
}
@keyframes nscLetterIn {
  from {
    opacity: 0;
    transform: translateY(16px) rotate(10deg) scale(0.6);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.nsc-splash-sub {
  font-size: 13px;
  color: var(--ink-faint);
}
/* "Loading" with three dots hopping in turn, each under gravity. */
.nsc-dots {
  display: inline-flex;
  gap: 3px;
  margin-left: 4px;
}
.nsc-dots i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  animation: nscHopDot 1s infinite;
}
.nsc-dots i:nth-child(2) {
  animation-delay: 0.14s;
}
.nsc-dots i:nth-child(3) {
  animation-delay: 0.28s;
}
@keyframes nscHopDot {
  0%,
  60%,
  100% {
    transform: none;
    animation-timing-function: cubic-bezier(0.2, 0.6, 0.4, 1);
  }
  30% {
    transform: translateY(-5px);
    animation-timing-function: cubic-bezier(0.6, 0, 0.8, 0.4);
  }
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Small section label: sentence case, sans, muted. Replaces the old
   uppercase monospace eyebrows so text is calmer to scan. */
.label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-dim);
}

input:focus-visible,
textarea:focus-visible,
select:focus-visible,
button:focus-visible {
  outline: 2px solid color-mix(in oklab, var(--accent) 70%, transparent);
  outline-offset: 2px;
}

/* ----- shared motion: modal enter/exit, backdrop fade, toast ----- */
@keyframes nscFadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes nscFadeOut {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
/* A sheet slides up from below the screen edge and settles with a small
   bounce; its opacity has its own quick fade (the spring would overshoot it). */
@keyframes nscSheetIn {
  from {
    transform: translateY(calc(100% + 120px));
  }
  to {
    transform: translateY(0);
  }
}
@keyframes nscSheetFade {
  from {
    opacity: 0.4;
  }
  to {
    opacity: 1;
  }
}
@keyframes nscSheetOut {
  from {
    transform: translateY(0);
    opacity: 1;
  }
  to {
    transform: translateY(24px);
    opacity: 0;
  }
}
@keyframes nscCenterIn {
  from {
    transform: translateY(14px) scale(0.88);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}
@keyframes nscCenterOut {
  from {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(0.96);
    opacity: 0;
  }
}
@keyframes nscToastIn {
  from {
    transform: translateY(26px) scale(0.86);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

/* Modals: a dimmed backdrop (no blur: re-blurring the whole screen on
   every frame of the fade made sheets stutter on mid-range phones) and one
   card style. Centred dialogs scroll inside when tall. */
.nsc-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  /* The backdrop runs under the status bar and home indicator
     (viewport-fit=cover), so keep the dialog clear of both. */
  padding: calc(16px + var(--safe-top)) 16px calc(16px + var(--safe-bottom));
  animation: nscFadeIn 0.18s ease-out;
}
.nsc-backdrop.is-bottom {
  align-items: flex-end;
  padding: 0 12px 12px;
}
/* Dialogs whose height changes as you use them (Settings tabs, Auto-fill
   patterns) hang from a fixed top instead of re-centring, so their tabs
   don't jump out from under the finger. */
.nsc-backdrop.is-top {
  align-items: flex-start;
  padding-top: calc(var(--safe-top) + max(16px, 5vh));
}
.nsc-modal {
  width: 100%;
  background: var(--bg-1);
  border: 1px solid var(--card-edge);
  border-radius: 22px;
  padding: 18px;
  outline: none;
  box-shadow: var(--shadow);
}
.nsc-center {
  max-height: calc(100vh - 32px - var(--safe-top) - var(--safe-bottom));
  max-height: calc(100dvh - 32px - var(--safe-top) - var(--safe-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
}
.nsc-backdrop.is-top .nsc-center {
  max-height: calc(
    100vh - max(16px, 5vh) - 16px - var(--safe-top) - var(--safe-bottom)
  );
  max-height: calc(
    100dvh - max(16px, 5vh) - 16px - var(--safe-top) - var(--safe-bottom)
  );
  transform-origin: 50% 0;
}
.nsc-backdrop.is-closing {
  animation: nscFadeOut 0.18s ease-in forwards;
}
.nsc-modal.nsc-sheet {
  animation:
    nscSheetIn 0.62s var(--ease-soft),
    nscSheetFade 0.2s ease-out;
}
.nsc-modal.nsc-sheet.is-closing {
  animation: nscSheetOut 0.18s ease-in forwards;
}
/* Pull a sheet down by its top to close it (useSheetDrag). Once flung, it
   leaves on its own flight and the backdrop fades from where the drag left
   it, so the usual exit keyframes stand aside. */
.nsc-grabber {
  display: block;
  width: 36px;
  height: 5px;
  border-radius: 3px;
  margin: -8px auto 10px;
  background: var(--ink-faint);
  opacity: 0.45;
}
.nsc-modal.is-flung.is-closing,
.nsc-backdrop.is-flung.is-closing {
  animation: none;
}
/* Bottom sheets sit above the taskbar (margin-bottom 96px), so cap their
   height to what's left of the screen and scroll inside, or the top of a
   tall sheet (all three shifts, per-shift taxi) goes off-screen on phones. */
/* Rounder than a dialog, like an iOS sheet. */
.nsc-sheet {
  border-radius: 28px;
  margin-bottom: calc(96px + var(--safe-bottom));
  max-height: calc(100vh - 120px - var(--safe-top) - var(--safe-bottom));
  max-height: calc(100dvh - 120px - var(--safe-top) - var(--safe-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
}
.nsc-modal.nsc-center {
  animation: nscCenterIn 0.5s var(--ease-bouncy);
}
.nsc-modal.nsc-center.is-closing {
  animation: nscCenterOut 0.16s ease-in forwards;
}
.nsc-toast {
  animation: nscToastIn 0.6s var(--ease-bouncy);
}
.nsc-toast.is-leaving {
  animation: nscToastOut 0.2s ease-in forwards;
}
@keyframes nscToastOut {
  to {
    transform: translateY(8px) scale(0.96);
    opacity: 0;
  }
}
/* The floating total eases in and out rather than popping. */
.total-chip-slot {
  transition:
    opacity 0.24s ease,
    transform 0.6s var(--ease-bouncy);
}
.total-chip-slot.is-hidden {
  opacity: 0;
  transform: translateY(12px) scale(0.96);
  visibility: hidden;
  transition:
    opacity 0.18s ease,
    transform 0.2s ease-in,
    visibility 0s linear 0.2s;
}
/* A dialog covers the page, so the floating total steps aside rather than
   peeking out between a bottom sheet and the tab bar. */
:root:has(.nsc-backdrop) .total-chip-slot {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.18s ease,
    visibility 0s linear 0.18s;
}
/* Toasts sit above the tab bar, and above the floating total when it's up. */
.nsc-toast-wrap {
  bottom: calc(120px + var(--safe-bottom));
  transition: bottom 0.55s var(--ease-snappy);
}
:root:has(.total-chip-slot:not(.is-hidden)):not(:has(.nsc-backdrop))
  .nsc-toast-wrap {
  bottom: calc(168px + var(--safe-bottom));
}

/* ----- premium motion -----
   Entrances use fill-mode "backwards" (never "both"): a transform left on a
   screen after it lands would turn it into the containing block for the
   fixed-position sheets opened inside it. */
@keyframes nscRise {
  from {
    transform: translateY(var(--rise-from, 22px));
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}
@keyframes nscPopIn {
  0% {
    transform: scale(0.4);
    opacity: 0;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}
/* Pressed in, then released: the spring easing carries it past full size
   and back. */
@keyframes nscCheckPop {
  from {
    transform: scale(0.55);
  }
  to {
    transform: scale(1);
  }
}
/* Squash and stretch: a quick squash (ease-out), then the spring lets go. */
@keyframes nscBoing {
  0% {
    transform: scale(1);
    animation-timing-function: ease-out;
  }
  22% {
    transform: scale(1.2, 0.8);
    animation-timing-function: var(--ease-wobbly);
  }
  100% {
    transform: scale(1);
  }
}
@keyframes nscDraw {
  to {
    stroke-dashoffset: 0;
  }
}
@keyframes nscSheen {
  from {
    transform: translateX(-120%) skewX(-18deg);
  }
  to {
    transform: translateX(320%) skewX(-18deg);
  }
}

/* Screens: each card rises in on a spring, a beat after the one above it,
   overshooting its place a touch before it settles. */
.nsc-view > :not(.nsc-backdrop) {
  animation: nscRise var(--rise-dur, 0.7s) var(--ease-snappy) backwards;
}
.nsc-view > :nth-child(2) {
  animation-delay: var(--rise-step, 0.05s);
}
.nsc-view > :nth-child(3) {
  animation-delay: calc(var(--rise-step, 0.05s) * 2);
}
.nsc-view > :nth-child(n + 4) {
  animation-delay: calc(var(--rise-step, 0.05s) * 3);
}
/* Two-column screens: the cards (or columns) rise, not the wrapper around
   them. */
.nsc-view > .nsc-split {
  animation: none;
}
.nsc-split > * {
  animation: nscRise var(--rise-dur, 0.7s) var(--ease-snappy) backwards;
}
.nsc-split > :nth-child(2) {
  animation-delay: var(--rise-step, 0.05s);
}
.nsc-split > :nth-child(n + 3) {
  animation-delay: calc(var(--rise-step, 0.05s) * 2);
}

/* App chrome. The header only fades (no movement), so the splash can
   measure where its logo will land while the header is still hidden. The
   tab bar slides up into place; the footer settles in after the cards.
   The header is solid, not frosted glass: it runs up under the status bar
   and Dynamic Island, and a backdrop blur there smeared whatever scrolled
   beneath it. Its hairline only shows once the page has scrolled. */
.nsc-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--bg);
  border-bottom: 1px solid transparent;
  transition:
    background-color 0.18s ease,
    border-color 0.2s ease,
    box-shadow 0.2s ease;
  animation: nscFadeIn 0.45s ease-out backwards;
}
.nsc-header.is-scrolled {
  border-bottom-color: var(--line-soft);
  box-shadow: 0 10px 24px -22px rgba(0, 0, 0, 0.9);
}
/* The tab bar is fixed to the bottom on its own compositing layer, so iOS
   doesn't repaint it a frame behind the page (it judders or drifts during
   momentum scrolling, worst on an iPad's large screen). */
.nsc-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  padding-bottom: var(--safe-bottom);
  pointer-events: none;
  transform: translateZ(0);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  transition:
    transform 0.3s var(--ease-snappy),
    opacity 0.2s ease,
    visibility 0s;
}
.nsc-tabbar.is-away {
  transform: translateY(110%);
  opacity: 0;
  visibility: hidden;
  transition:
    transform 0.2s ease-in,
    opacity 0.2s ease,
    visibility 0s linear 0.2s;
}
.nsc-dock {
  animation: nscDockIn 0.8s var(--ease-bouncy) 0.18s backwards;
}
.nsc-footer {
  animation: nscFadeIn 0.5s ease-out 0.3s backwards;
}
@keyframes nscDockIn {
  from {
    transform: translateY(70px);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

/* Wizard steps slide in from the right. */
.nsc-step {
  animation: nscStepIn 0.6s var(--ease-snappy) backwards;
}
@keyframes nscStepIn {
  from {
    transform: translateX(40px);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

/* Shift chips pop onto the calendar when they're added. */
.nsc-chip-in {
  animation: nscPopIn 0.55s var(--ease-bouncy) backwards;
}
.nsc-pop {
  display: inline-block;
  animation: nscPopIn 0.5s var(--ease-bouncy) backwards;
}
.nsc-float-in {
  animation: nscToastIn 0.6s var(--ease-bouncy) backwards;
}
/* The tab you just picked: its icon squashes and springs back. */
.nsc-tab-pop {
  display: inline-flex;
  animation: nscBoing 0.7s backwards;
}

/* Day editor tick boxes: the box springs, then the tick draws itself. */
.nsc-check {
  transition:
    background 0.18s ease,
    border-color 0.18s ease;
}
.nsc-check.is-on {
  animation: nscCheckPop 0.55s var(--ease-bouncy);
}
.nsc-tick {
  stroke-dasharray: 12;
  stroke-dashoffset: 12;
  animation: nscDraw 0.3s 0.1s ease-out forwards;
}

/* Sliding selection: segmented controls and the tab bar. The pill swings
   past the option it's heading for and settles back onto it. */
.nsc-seg-pill {
  transition:
    transform 0.55s var(--ease-snappy),
    width 0.55s var(--ease-snappy),
    height 0.55s var(--ease-snappy);
}
.nsc-tab-pill {
  transition: transform 0.65s var(--ease-snappy);
}
/* Fold-out arrows flip over on a spring. */
.nsc-chev {
  display: inline-block;
  transition: transform 0.55s var(--ease-bouncy);
}
.nsc-chev.is-open {
  transform: rotate(180deg);
}

/* Collapsing content fades as it folds, and slides down a touch as it
   opens, so it reads as coming out from under the row above. The slide is
   only there while closed: an open fold carries no transform, which would
   otherwise trap fixed-position dialogs opened inside it. */
.nsc-collapse {
  transition: grid-template-rows 0.42s var(--ease-snappy);
}
.nsc-collapse > div {
  transition:
    opacity 0.22s ease,
    transform 0.42s var(--ease-snappy);
}
.nsc-collapse:not(.is-open) > div {
  opacity: 0;
  transform: translateY(-6px);
}

/* Hero totals: a soft light sweeps across when the number changes. */
.nsc-hero {
  position: relative;
  overflow: hidden;
  padding: 18px 16px;
  border-radius: 14px;
  background: linear-gradient(
    180deg,
    color-mix(in oklab, var(--accent) 16%, transparent),
    color-mix(in oklab, var(--accent) 6%, transparent)
  );
  border: 1px solid color-mix(in oklab, var(--accent) 30%, transparent);
}
.nsc-sheen {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in oklab, var(--accent) 22%, transparent),
    transparent
  );
  transform: translateX(-120%) skewX(-18deg);
  animation: nscSheen 1.1s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
}

/* Press feedback: everything tappable gives a little under the finger and
   springs back on release. Uses the scale property (not transform), so it
   never fights an entrance animation or an inline transform. */
button {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: var(--press);
}
button:not(:disabled):active {
  scale: 0.96;
  transition-duration: 0.12s;
}
button[role="tab"]:active,
.nsc-day:active,
.nsc-pick:active {
  scale: 0.9;
}
/* A row as wide as its card (a fold-out row, the calendar's header) would
   lurch at the usual press, so it gives only slightly. */
.nsc-fold-btn:not(:disabled):active {
  scale: 0.985;
}
@media (hover: hover) {
  .nsc-day:hover,
  .nsc-pick:hover {
    filter: brightness(1.08);
  }
}

/* Page widths. A screen is a phone-width column until there's room for two
   (744px: an iPad mini held upright), then the split screens below widen. */
.nsc-page {
  max-width: 720px;
  margin: 0 auto;
}
.nsc-page.is-narrow {
  max-width: 560px;
}

/* Two columns from an iPad upright, side by side: Home (result and pay
   settings | calendar), Watch (calendar | people and totals), a watch
   member (calendar | details and pay) and History (year to date | saved
   periods). Each column is as wide as a phone, so nothing inside changes.
   A column can be pinned while the other scrolls, but only while all of it
   fits between the header and the tab bar (App sets .is-fit); otherwise its
   last rows would be stuck under the tab bar. Header (12 + 44 + 12 + 1px
   border) plus the 16px gap. */
@media (min-width: 744px) {
  .nsc-page.is-split {
    max-width: 1180px;
  }
  .nsc-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0 16px;
    align-items: start;
  }
  /* A watch member: the calendar on the left, beside both cards. */
  .nsc-split > .is-left {
    grid-column: 1;
    grid-row: 1 / span 2;
  }
  .nsc-split > .is-right {
    grid-column: 2;
  }
  /* Home: on a phone the calendar sits between the result and the pay
     settings; here it moves to the right, beside both. The first row is
     the result's height, so the settings card follows straight under it. */
  .nsc-split.has-side {
    grid-template-rows: auto 1fr;
  }
  .nsc-split.has-side > * {
    grid-column: 1;
  }
  .nsc-split.has-side > .is-side {
    grid-column: 2;
    grid-row: 1 / span 2;
  }
}
@media (min-width: 744px) {
  .nsc-split > .is-pinned.is-fit {
    position: sticky;
    top: calc(var(--safe-top) + 85px);
  }
}

/* The floating total pulses when the period is saved. */
.total-chip.flash {
  animation: chipFlash 0.8s;
}
@keyframes chipFlash {
  0% {
    transform: scale(1);
    animation-timing-function: ease-out;
  }
  20% {
    transform: scale(1.12, 0.9);
    box-shadow:
      0 0 0 8px color-mix(in oklab, var(--accent) 25%, transparent),
      0 12px 32px -16px rgba(0, 0, 0, 0.7);
    animation-timing-function: var(--ease-wobbly);
  }
  100% {
    transform: scale(1);
  }
}

/* Calendar days flagged by the roster cross-check. */
.pulse-day {
  animation: pulseDay 1.4s ease-out 2;
}
@keyframes pulseDay {
  0%,
  100% {
    box-shadow: 0 0 0 0 color-mix(in oklab, var(--warn) 0%, transparent);
  }
  50% {
    box-shadow: 0 0 0 6px color-mix(in oklab, var(--warn) 30%, transparent);
  }
}

/* History charts. Lines draw left to right (a clip reveal, so the
   stretched sparkline needs no path measuring), columns and bars grow from
   their baseline one after another, and saved rows rise in, then fold
   away when deleted. */
@keyframes nscRevealX {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}
@keyframes nscGrowY {
  from {
    transform: scaleY(0);
  }
  to {
    transform: none;
  }
}
@keyframes nscGrowX {
  from {
    transform: scaleX(0);
  }
  to {
    transform: none;
  }
}
.nsc-reveal-x {
  animation: nscRevealX 0.9s cubic-bezier(0.65, 0, 0.35, 1) 0.15s backwards;
}
/* Columns shoot up past their height and bounce back down onto it. */
.nsc-grow-y {
  transform-origin: bottom;
  animation: nscGrowY 0.8s var(--ease-bouncy) backwards;
}
.nsc-grow-x {
  transform-origin: left;
  animation: nscGrowX 0.8s var(--ease-snappy) backwards;
}
.nsc-spark-dot {
  animation: nscPopIn 0.55s var(--ease-bouncy) backwards;
}
.nsc-tip {
  animation: nscFadeIn 0.12s ease-out;
}
.nsc-row {
  display: grid;
  grid-template-rows: 1fr;
  transition:
    grid-template-rows 0.22s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.18s ease-in;
}
.nsc-row.is-leaving {
  grid-template-rows: 0fr;
  opacity: 0;
}
.nsc-row > div {
  min-height: 0;
  /* Let a row narrower than its unwrapped text, so ellipses can do their job. */
  min-width: 0;
}
/* Clip only while folding, so focus rings aren't cut off the rest of the time. */
.nsc-row.is-leaving > div {
  overflow: hidden;
}
.nsc-row-in {
  animation: nscRise 0.65s var(--ease-snappy) backwards;
}
/* Calendars: the days settle in left to right, row by row, when a period
   opens. Cells set --i to their place in the grid. */
.nsc-cascade > * {
  animation: nscCellIn 0.6s var(--ease-bouncy) backwards;
  animation-delay: calc(var(--i, 0) * 12ms + 60ms);
}
@keyframes nscCellIn {
  from {
    transform: translateY(12px) scale(0.82);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}
.nsc-col:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Respect the OS "reduce motion" setting across the whole app. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .nsc-sheen {
    display: none;
  }
  button:not(:disabled):active {
    scale: none;
  }
}

/* iOS switch for on/off settings (a styled checkbox, so it stays a real,
   labelled form control). The knob slides on a spring. */
input.nsc-switch {
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  flex-shrink: 0;
  width: 51px;
  height: 31px;
  margin: 0;
  border-radius: 999px;
  background: var(--fill);
  cursor: pointer;
  transition: background-color 0.25s ease;
}
[data-theme="dark"] input.nsc-switch {
  background: rgba(120, 120, 128, 0.32);
}
input.nsc-switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow:
    0 3px 8px rgba(0, 0, 0, 0.15),
    0 3px 1px rgba(0, 0, 0, 0.06);
  transition: transform 0.45s var(--ease-snappy);
}
input.nsc-switch:checked {
  background: var(--switch-on);
}
input.nsc-switch:checked::before {
  transform: translateX(20px);
}

/* Reconcile rows: label · amount paid · variance. On a narrow phone there
   isn't room for three columns, so the variance drops under the amount. */
.recon-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 110px 110px;
}
@media (max-width: 420px) {
  .recon-row {
    grid-template-columns: minmax(0, 1fr) 118px;
  }
  .recon-var {
    grid-column: 2;
  }
}
