/* Self-hosted DM Mono (300/400/500), so it's on our CDN + preloadable — no Google
   round-trip, no FOUT. latin + latin-ext cover accented city names. */
@font-face {
  font-family: "DM Mono"; font-style: normal; font-weight: 300; font-display: swap;
  src: url(assets/fonts/dmmono-300-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "DM Mono"; font-style: normal; font-weight: 300; font-display: swap;
  src: url(assets/fonts/dmmono-300-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "DM Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url(assets/fonts/dmmono-400-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "DM Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url(assets/fonts/dmmono-400-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "DM Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url(assets/fonts/dmmono-500-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "DM Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url(assets/fonts/dmmono-500-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg: #f7f7ee;
  --ink: #111111;
  --muted: #9a9a94;
  --yellow: #e9ff32;
  --line: #ffffff;
  --empty: #fffaf2;
  --field-border: #d8d8d4;
  --hover: #e4ddd5;
  --track: #eeede8;
  --surface: #ffffff;
 /*--page: linear-gradient(150deg, #f7f7ee 0%, #fbf5ed 50%, #fbf2e8 100%); /* soft off-white canvas — darker so white chips read, faint warm drift */
  --page: linear-gradient(125deg, #eeede8 0%, #f1ede4 50%, #fce8cd 100%); /* soft off-white canvas — darker so white chips read, faint warm drift */
  --page-solid: rgb(238, 237, 232); /* solid behind the fixed gradient so overscroll matches, not white */
  --chip: #ffffff; /* flat control/brand chips that pop off the off-white canvas */
  --edge: 28px;
  --bar-h: 54px; /* shared height for the day-nav / search / times pill */
  --tl-day: rgba(255,255,255, 0.4); /* timeline day-hours band */
  --tl-night: rgba(0, 0, 0, 0.05); /* timeline night-hours band */
}
:root[data-theme="dark"] {
  --bg: #151514;
  --ink: #f1f0ea;
  --muted: #85847d;
  --line: #151514;
  --empty: #30302d;
  --field-border: #3a3a34;
  --hover: #3d3b35;
  --track: #2d2c27;
  --surface: #1d1d1b;
  --page: linear-gradient(150deg, #2d2c27 0%, #272622 50%, #26221e 100%); /* soft off-black canvas — lighter so dark chips read, faint warm drift */
  --page-solid: #2d2c27; /* solid behind the fixed gradient so overscroll matches */
  --chip: #0a0a09; /* near-black chips on the off-black canvas */
  --tl-day: #201f1d;
  --tl-night: #2c2c29;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* Text selection stays visible on any card color (default blue-ish blends with LA) */
::selection { background: var(--hover); color: var(--ink); }
::-moz-selection { background: var(--hover); color: var(--ink); }

/* File-referenced icons: the SVG shape is a mask so currentColor tints it */
.icon {
  display: inline-block;
  flex: none;
  background-color: currentColor;
  -webkit-mask: var(--icon) no-repeat center / contain;
  mask: var(--icon) no-repeat center / contain;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--page);
  background-attachment: fixed; /* gradient stays put on scroll */
  background-color: var(--page-solid); /* fills the overscroll/rubber-band area instead of white */
  color: var(--ink);
  font-family: "DM Mono", ui-monospace, monospace;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.25s ease, color 0.25s ease;
}

/* Pull-to-refresh indicator (standalone web app): a chip + spinner ring that follows
   the pull down from the top and rests while the page reloads. Positioned/animated by
   js/pull-refresh.js. Starts tucked just above the top edge. */
.ptr {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) - 42px);
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  z-index: 300;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--chip);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity; /* keep it on its own GPU layer so the pull never repaints */
}
.ptr-snap { transition: transform 0.26s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.26s ease; }
.ptr-ring {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-top-color: var(--ink);
  will-change: transform;
}
.ptr-loading .ptr-ring { animation: ptr-spin 0.7s linear infinite; }
@keyframes ptr-spin { to { transform: rotate(360deg); } }

/* Light/dark switch: JS adds .theme-anim to <html> for the duration of the switch
   so every color (cards, borders, tracks, text, icons) crossfades together instead
   of some snapping. Scoped to the switch so it never interferes with other motion. */
.theme-anim, .theme-anim *, .theme-anim *::before, .theme-anim *::after {
  transition: background-color 0.34s ease, color 0.34s ease, border-color 0.34s ease,
    fill 0.34s ease, box-shadow 0.34s ease, outline-color 0.34s ease !important;
}
/* ...but the sun<->moon icon morphs DURING the theme switch, so exempt it: keep its
   geometry/transform transitions (the broad rule above would otherwise freeze them). */
.theme-anim .ic-theme, .theme-anim .ic-theme * {
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
    cx 0.45s cubic-bezier(0.4, 0, 0.2, 1), cy 0.45s cubic-bezier(0.4, 0, 0.2, 1),
    r 0.45s cubic-bezier(0.4, 0, 0.2, 1), fill 0.34s ease !important;
}
@media (prefers-reduced-motion: reduce) {
  .theme-anim, .theme-anim *, .theme-anim *::before, .theme-anim *::after { transition: none !important; }
}

/* ---------- Top bar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 12px;
  /* Pad the top by the status-bar safe area so (a) the pill clears the time/battery and
     (b) the ::before blur box grows up under the status bar and blurs it too. 0 in a
     browser tab / on desktop, so no change there. */
  padding: calc(env(safe-area-inset-top, 0px) + 20px) var(--edge) 20px;
  background: transparent; /* blur only, no white tint */
}
/* ---- Frosted top (see the .top-tint + .top-blur divs in index.html) ----
   Both layers are fixed and 100vh: iOS 26 won't composite a SHORT fixed element under the
   Dynamic Island, but a full-viewport-tall one renders across the whole screen. Each is
   cropped back to the top strip with absolute lengths so it doesn't cover the rest of the
   page — the tint via an alpha gradient, the blur via a mask.

   .top-tint is the ONLY layer that shows under the Dynamic Island: iOS refuses to render
   backdrop-filter there, so the real blur can't reach up. It fills that strip with the
   PAGE's own background colour so the island area reads as the canvas continuing upward
   (hiding the content that bleeds under it), then fades out into the real .top-blur on the
   bar just below. The rgb() values are --page-solid (light / dark). */
.top-tint {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  z-index: 49; /* above .top-blur, below the topbar pill/controls (z:50) */
  pointer-events: none;
  /* Background is painted ONLY on iOS/iPadOS (gate below). It's purely a Dynamic-Island
     workaround; on desktop and Android the .top-blur alone is enough, so the tint stays
     invisible there and adds no off-white veil. */
}
/* Scope the tint to iOS/iPadOS Safari-engine:
   - (pointer: coarse) → a touch device (phone/tablet); excludes desktop, incl. macOS
     Safari, which uses a mouse (fine pointer).
   - @supports (-webkit-touch-callout: none) → the WebKit/iOS engine specifically; Chrome
     and Firefox don't support this property, so Android and desktop Chrome fall out.
   There's no CSS media query for "is Safari"; this pair is the standard way to isolate
   iOS/iPadOS — which is exactly where the island bug lives (and where all browsers are
   Safari's engine anyway). */
@media (pointer: coarse) {
  @supports (-webkit-touch-callout: none) {
    .top-tint {
      background: linear-gradient(
        to bottom,
        rgb(238, 237, 232) 0,
        rgb(238, 237, 232) calc(env(safe-area-inset-top, 0px)),
        rgba(238, 237, 232, 0) calc(env(safe-area-inset-top, 0px) + 100px)
      );
    }
    :root[data-theme="dark"] .top-tint {
      background: linear-gradient(
        to bottom,
        rgb(45, 44, 39) 0,
        rgb(45, 44, 39) calc(env(safe-area-inset-top, 0px)),
        rgba(45, 44, 39, 0) calc(env(safe-area-inset-top, 0px) + 100px)
      );
    }
  }
}
.top-blur {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  z-index: 48; /* below the tint veil and the pill, above the scrolling content */
  pointer-events: none;
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  -webkit-mask-image: linear-gradient(
    to bottom,
    #000 0,
    #000 calc(env(safe-area-inset-top, 0px) + 60px),
    transparent calc(env(safe-area-inset-top, 0px) + 94px)
  );
  mask-image: linear-gradient(
    to bottom,
    #000 0,
    #000 calc(env(safe-area-inset-top, 0px) + 60px),
    transparent calc(env(safe-area-inset-top, 0px) + 94px)
  );
}

/* Each control = optional label + segmented pill. Label shows only in the drawer. */
.ctl { display: inline-flex; align-items: center; }
.ctl-label { display: none; }

.settings-btn {
  display: none; /* shown on mobile only */
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  border: 0;
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
}
.settings-btn:hover { background: var(--hover); }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  background: var(--chip); /* flat white (light) / near-black (dark) — no shadow */
  color: var(--ink);
  text-decoration: none;
  font-weight: 400;
  font-size: 15px;
  padding: 9px 20px 9px 11px;
  border-radius: 999px;
  white-space: nowrap;
  margin-right: auto; /* pushes controls + share to the right */
}
/* Little gradient "sun" — a warm rust→gold sphere that very slowly drifts. */
.brand-dot {
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: 50%;
  background: linear-gradient(135deg, #cf4c1a 0%, #e5781d 45%, #f2b82e 100%);
  background-size: 200% 200%;
  animation: brand-dot 13s ease-in-out infinite;
}
@keyframes brand-dot {
  0%, 100% { background-position: 0% 0%; }
  50% { background-position: 100% 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .brand-dot { animation: none; }
}

.controls { display: flex; align-items: center; gap: 12px; }

/* Desktop uses circular one-click toggles; the segmented pills show only in the
   mobile drawer now. Circles are sized to the Share button so they read as a set. */
.topbar > .controls { display: none; }
.desk-toggles { display: inline-flex; align-items: center; gap: 10px; }
.circle-toggle {
  width: 46px;
  height: 46px;
  flex: none;
  border: 0;
  border-radius: 50%;
  background: var(--chip);
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s ease;
}
.circle-toggle:hover { background: var(--hover); }

/* ---------- Morphing desktop toggles ---------- */
/* Each circle holds both states; toggling a data-attribute morphs between them.
   Transitions are suppressed on first paint via .booting (set by JS). */
.desk-toggles.booting * { transition: none !important; }
.circle-toggle .ic-morph { display: block; overflow: visible; }

/* Clock: 12 <-> 24 vertical roll with a static "h". A window clips the stacked
   number strip; toggling data-fmt slides it one notch (odometer-style). The "h"
   lives outside the window so it stays put while the number rolls. */
.clk-win { display: inline-block; height: 1.35em; overflow: hidden; vertical-align: middle; }
.clk-strip { display: block; transition: transform 0.34s cubic-bezier(0.4, 0, 0.2, 1); }
.clk-strip > span { display: block; height: 1.35em; line-height: 1.35em; text-align: center; }
.circle-toggle[data-fmt="24"] .clk-strip { transform: translateY(-1.35em); }
.clk-h { display: inline-block; line-height: 1.35em; vertical-align: middle; }

/* View: 2x2 grid -> 3 list lines. Both pairs merge + shorten and the middle line
   fades in, all at once. */
.ic-view rect {
  transition: x 0.3s cubic-bezier(0.4, 0, 0.2, 1), y 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    width 0.3s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.3s ease;
}
.ic-view .v5 { opacity: 0; } /* middle line: hidden in grid, appears for list */
.circle-toggle[data-view="list"] .v1,
.circle-toggle[data-view="list"] .v3 { x: 6px; width: 10px; y: 2.6px; height: 1.6px; }
.circle-toggle[data-view="list"] .v2,
.circle-toggle[data-view="list"] .v4 { x: 6px; width: 10px; y: 10.8px; height: 1.6px; }
.circle-toggle[data-view="list"] .v5 { opacity: 1; }

/* Theme: sun <-> moon, a geometry morph (same idea as grid/list). The bite is a
   circle whose cx/cy/r animate — it grows from a zero-radius point off the disc's
   upper-right and sweeps in to eat the crescent — while the rays spin inward and
   retract to nothing. No opacity, so nothing crossfades. */
.ic-theme .ic-core {
  fill: currentColor;
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.88); /* SUN center-disc size (rays are sized separately below) */
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
/* MOON SIZE — how big the moon disc is (1 = same as the sun's disc). */
.circle-toggle[data-mode="dark"] .ic-theme .ic-core { transform: scale(1.45); }
.ic-theme .ic-cutout {
  fill: var(--track);
  cx: 21px; cy: 3px; r: 0px; /* sun: a zero-radius point off the disc's upper-right */
  /* Rotate the bite around the disc centre so the crescent turns a little as it forms
     (settling at rotate(0) = the normal moon). Invisible in the sun state (r:0). */
  transform-box: view-box;
  transform-origin: 12px 12px;
  transform: rotate(55deg); /* MOON SPIN — how far the crescent turns as it forms (sign = direction) */
  transition: cx 0.45s cubic-bezier(0.4, 0, 0.2, 1), cy 0.45s cubic-bezier(0.4, 0, 0.2, 1),
    r 0.45s cubic-bezier(0.4, 0, 0.2, 1), transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.circle-toggle:hover .ic-theme .ic-cutout { fill: color-mix(in srgb, var(--ink) 10%, var(--track)); }
.circle-toggle[data-mode="dark"] .ic-theme .ic-cutout { cx: 16px; cy: 8px; r: 7.2px; transform: rotate(0deg); }
.ic-theme .ic-rays {
  transform-box: fill-box;
  transform-origin: center;
  /* SUN rays size (matches the disc scale so the gap stays). The rotate(0deg) keeps
     the same transform *structure* as the dark state below, so the spin actually
     interpolates instead of falling back to a matrix (which drops the rotation). */
  transform: rotate(0deg) scale(1.02);
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
/* spin + shrink (a rotation that actually animates, unlike a full 360) */
.circle-toggle[data-mode="dark"] .ic-theme .ic-rays { transform: rotate(-135deg) scale(0); }

@media (prefers-reduced-motion: reduce) {
  .clk-strip, .ic-view rect,
  .ic-theme .ic-core, .ic-theme .ic-cutout, .ic-theme .ic-rays { transition: none !important; }
}

.segmented {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
  background: var(--track);
}

/* Sliding indicator behind the active segment (positioned by JS) */
.seg-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  width: 0;
  border-radius: 999px;
  background: var(--ink);
}
.segmented.ready .seg-thumb {
  transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}

.seg {
  position: relative;
  z-index: 1;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.02em;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  transition: color 0.2s ease;
}
.seg.active { color: var(--bg); }

.share-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 0;
  background: var(--ink);
  color: var(--bg);
  font-family: inherit;
  font-weight: 500;
  font-size: 14px;
  padding: 12px 20px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease;
}
/* Minimal hover: lift the ink fill slightly toward the background */
.share-btn:hover { background: color-mix(in srgb, var(--ink) 84%, var(--bg)); }
.share-btn svg { display: block; }

/* ---------- Settings drawer (mobile) ---------- */
.settings-backdrop {
  position: fixed;
  inset: 0;
  z-index: 199;
  background: rgba(0, 0, 0, 0.4);
  opacity: 0;
  transition: opacity 0.24s ease;
}
.settings-backdrop.open { opacity: 1; }
.settings-backdrop[hidden] { display: none; }

.settings-drawer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  background: var(--surface);
  border-radius: 18px 18px 0 0;
  padding: 10px 20px calc(24px + env(safe-area-inset-bottom));
  box-shadow: 0 -10px 34px rgba(0, 0, 0, 0.22);
  transform: translateY(100%);
  transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}
.settings-drawer.open { transform: translateY(0); }
.settings-drawer[hidden] { display: none; }
.settings-drawer::before {
  content: "";
  display: block;
  width: 40px;
  height: 4px;
  margin: 0 auto 16px;
  border-radius: 999px;
  background: var(--field-border);
}
.drawer-title {
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  margin-bottom: 6px;
}
/* In the drawer, controls stack as labeled rows */
.settings-body .controls { display: flex; flex-direction: column; gap: 0; width: 100%; }
.settings-body .ctl {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 16px 0;
  border-top: 1px solid var(--field-border);
}
.settings-body .ctl:first-child { border-top: 0; }
.settings-body .ctl-label { display: block; font-size: 15px; color: var(--ink); }
/* All toggles share one width and split evenly, so they line up on the right */
.settings-body .segmented { width: 164px; }
.settings-body .seg { flex: 1; }
/* Selected pill flips per theme: white pill + black icon in light, black pill +
   light icon in dark (uses the page color --bg, so it inverts automatically). */
.settings-body .seg-thumb {
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14);
}
.settings-body .seg.active { color: var(--ink); }

/* ---------- Sections ---------- */
main { padding: 20px 0 64px; } /* mobile adds bottom clearance for its floating pill */

.pinned-section { margin-bottom: 64px; }

.section-head {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-bottom: 18px;
  padding: 0 var(--edge);
}

.section-label {
  margin: 0;
  font-size: 20px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}

/* Stack the label over a full-width search field. */
.all-head {
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  margin-bottom: 18px;
}
.all-head .search-wrap { flex: none; }

.search-wrap { position: relative; flex: 1; }
/* Underline instead of a pill: a single hairline under the input, text aligned
   to the section edge (matching the label). */
.search {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--field-border);
  border-radius: 0;
  padding: 15px 46px 15px 0;
  font-family: inherit;
  font-size: 14px;
  color: var(--ink);
  background: transparent;
  outline: none;
}
.search::placeholder { color: var(--muted); }
.search:focus { border-bottom-color: var(--ink); }
.search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.search-clear:hover { color: var(--ink); }
.search-clear[hidden] { display: none; }

/* Pinned header holds the label, day switcher, and copyable times in one grid so
   the pieces can rearrange per breakpoint (desktop: label on top, switcher left of
   the times; mobile: switcher on the label row, times full-width below). The
   second row reserves the chip's height so it doesn't shift when the last pin
   is added/removed (the chip is display:none when empty). */
/* Desktop: date selector + copyable times side by side (mobile floats the times
   at the bottom instead — see the media query). */
.pinned-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 var(--edge);
  margin-bottom: 22px;
}

/* Copyable times float as a pill at the bottom-center (desktop); the mobile block
   floats it full-width. Removed from the header grid so it doesn't sit inline. */
/* Desktop: in-flow, gray like the date selector, sitting to its right. */
.pinned-text {
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
  height: 46px; /* match the date selector */
  min-width: 0; /* allow the text to ellipsis inside the flex row */
  max-width: 100%; /* use the full remaining width before truncating */
  padding: 0 8px 0 18px; /* right = copy-circle inset ((46 − 36)/2 ≈ 5) + a touch */
  border: 0; /* reset the native <button> border */
  border-radius: 999px;
  margin: 0;
  cursor: pointer;
  text-align: left;
  line-height: 1.4;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--chip);
  transition: background 0.18s ease;
}
/* Dark mode keeps the rim subtle (the bright rim is light-mode only) + a deeper shadow. */
.pinned-text .pt-lines { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pinned-text:empty { display: none; }
/* The link icon sits in a round hit-target that fills on hover (accent = interactive). */
.pt-copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  transition: background 0.15s ease;
}
.pinned-text:hover .pt-copy { background: var(--hover); }
/* .pinned-text:hover .link-ico { opacity: 1; color: var(--copy-ink, #111111); } */

/* ---------- Grids (edge to edge) ---------- */
.grid {
  display: grid;
  gap: 2px;
  /* background: var(--line); */
}
.pinned-grid,
.all-grid { grid-template-columns: repeat(3, 1fr); }
/* Anchor for the leaving-card clone overlay during the remove animation. */
.pinned-grid { position: relative; }

/* ---------- Pin add / remove motion (grid view) ---------- */
/* Quiet fade-in; the visible motion is the neighbours gliding to make room. */
@keyframes cardEnter {
  from { opacity: 0; transform: scale(0.985); }
  to   { opacity: 1; transform: scale(1); }
}
.card-enter { opacity: 0; }
.card-enter-active { animation: cardEnter 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; }
/* Leaving card (a clone floated over its old spot) fades out with a faint settle. */
.card-leaving {
  z-index: 3;
  pointer-events: none;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.28s ease;
}
.card-leaving-active { transform: scale(0.985); opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .card-enter-active { animation: none; }
  .card-leaving { transition: none; }
}

/* ---------- Cards ---------- */
/* The tile gradient's two stops are registered as <color> so a CSS transition can
   interpolate them — that's what lets the tile FADE to the new sky color when the
   scrubbed time changes (cardStyle sets --grad-a/--grad-b per city per local hour). */
@property --grad-a { syntax: "<color>"; inherits: false; initial-value: transparent; }
@property --grad-b { syntax: "<color>"; inherits: false; initial-value: transparent; }
.card {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* the tile is the "sky" at the city's local hour, same in light/dark (color tracks
     time, not theme). --ink flips black/off-white so text stays legible all day. */
  background: linear-gradient(160deg, var(--grad-a, var(--track)), var(--grad-b, var(--track)));
  transition: --grad-a 1.8s ease, --grad-b 1.8s ease; /* subtle fade on time change */
  color: var(--ink, #111111);
  /* draggable cards default to user-select:none; re-enable so the highlight shows */
  -webkit-user-select: text;
  user-select: text;
  --muted: color-mix(in srgb, var(--ink, #111111) 55%, transparent);
}
/* Dark mode: lay a slight dark veil over the gradient so tiles sit a touch calmer on
   black (text is a child, so it's unaffected). */
:root[data-theme="dark"] .card,
:root[data-theme="dark"] .tl-label {
  background: linear-gradient(rgba(12, 12, 11, 0.16), rgba(12, 12, 11, 0.16)),
    linear-gradient(160deg, var(--grad-a, var(--track)), var(--grad-b, var(--track)));
}

/* Floating round "+" button, inset from the corner (matches the reference's
   circular controls). Hidden at rest; revealed on hover to declutter the grid. */
.pin-btn {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Plus pins: white in light mode, black in dark mode (theme-based, not per-tile). */
  background: #ffffff;
  color: #111111;
  opacity: 0;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
:root[data-theme="dark"] .pin-btn { background: #111111; color: #f6f4ee; }
.card:hover .pin-btn,
.card:focus-within .pin-btn { opacity: 1; }
.pin-btn:hover { transform: scale(1.07); }
/* Pinned tiles keep the "+" visible; hovering animates it into an "✕" to signal "remove". */
.card.is-pinned .pin-btn { opacity: 1; }
.pin-btn .icon { transition: transform 0.16s ease; }
.card.is-pinned .pin-btn:hover .icon { transform: rotate(45deg); }
/* Touch devices have no hover, so keep the pin tappable there. */
@media (hover: none) {
  .pin-btn { opacity: 1; }
}
/* All-cities grid: the hover-revealed "+" is a frosted disc that follows the COLOR MODE
   (not the per-tile text ink, which flipped it dark/light per tile). Light mode = frosted
   white disc + white "+"; dark mode = frosted dark disc + black "+". Translucent so the
   tile colour still shows through. */
.all-grid .pin-btn { background: rgba(255, 255, 255, 0.15); color: #ffffff; }
.all-grid .pin-btn:hover { background: rgba(255, 255, 255, 0.68); }
:root[data-theme="dark"] .all-grid .pin-btn { background: rgba(12, 12, 11, 0.15); color: #111111; }
:root[data-theme="dark"] .all-grid .pin-btn:hover { background: rgba(12, 12, 11, 0.62); }
/* ...but once a tile IS pinned, its "+" goes solid (black in light, white in dark)
   so pinned reads as a committed state, not a faint hover hint. */
.all-grid .card.is-pinned .pin-btn { background: #ffffff; color: #111111; }
:root[data-theme="dark"] .all-grid .card.is-pinned .pin-btn { background: #111111; color: #f6f4ee; }

/* "reset to now" — clears an edited time back to the live clock */
.now-group { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
/* Reset + UTC offset cluster on the footer's right edge, so the reset pill is anchored
   there instead of floating mid-card at wide widths. */
.foot-end { display: inline-flex; align-items: center; gap: 12px; }
.reset-now {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: 0;
  background: color-mix(in srgb, var(--ink) 13%, transparent);
  color: var(--ink);
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  padding: 4px 9px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.reset-now:hover { background: color-mix(in srgb, var(--ink) 22%, transparent); }
.reset-now[hidden] { display: none; }

.city-name { font-weight: 400; font-size: 15px; letter-spacing: 0.02em; color: var(--ink); }

.time {
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* AM/PM sits inline at the same size as the digits ("3:32 AM"), just lighter. */
.time .ampm {
  font-size: 1em;
  font-weight: 300;
  margin-left: 0.28em;
  color: var(--ink);
}

.utc { font-size: 13px; color: var(--muted); letter-spacing: 0.02em; }

/* Editable first-card time (input left, pencil right, shared underline) */
.time-edit-wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding-bottom: 4px;
  border-bottom: 2px solid var(--ink);
  border-bottom: 2px solid color-mix(in srgb, var(--ink) 40%, transparent);
  cursor: text;
}
.time-edit-wrap:focus-within { border-bottom-color: var(--ink); }
/* Custom hour:minute editor — each segment is focusable and highlights on focus */
.time-edit {
  flex: none;
  display: inline-flex;
  align-items: baseline;
  font-family: inherit;
}
.tseg {
  cursor: text;
  outline: none;
  border-radius: 5px;
  padding: 0 2px;
  font-variant-numeric: tabular-nums;
}
/* Highlight the active segment with a translucent-white wash and keep the digits
   their original colour (white or black per tile) — no colour flip. */
.tseg:focus,
.tseg.is-editing { background: rgba(255, 255, 255, 0.22); color: var(--ink); }
/* The segment's own wash is the highlight; keep the native text-selection invisible
   (transparent) and leave the digits their normal colour rather than flipping them. */
.tseg::selection { background: transparent; color: var(--ink); }
.tseg::-moz-selection { background: transparent; color: var(--ink); }
.tcolon { padding: 0 1px; }
/* AM/PM toggle in the editable card (12-hour mode) — a small tappable pill that
   sits on the digits' baseline, matching the read-only cards' am/pm */
.tampm {
  align-self: baseline;
  margin-left: 0.28em;
  padding: 0 2px;
  font-size: 1em;
  font-weight: 300;
  cursor: pointer;
  user-select: none;
}

/* Editable first-card date (text left, calendar right) */
.date-wrap { position: relative; }
.date-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  font-family: inherit;
  font-size: 15px;
  font-weight: 400;
  color: var(--ink);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.date-edit {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Edit-affordance icons (pen + calendar), right-aligned and uniform */
.edit-ico { color: var(--ink); color: color-mix(in srgb, var(--ink) 72%, transparent); }
.time-edit-wrap:hover .edit-ico,
.date-btn:hover .edit-ico { color: var(--hover); }
/* No color/opacity transition: the copy circle's gradient background can't be
   animated (gradients aren't interpolable), so it snaps in — the icon snaps with it
   in sync rather than fading a beat later. */
.link-ico { color: var(--ink); opacity: 0.55; vertical-align: -2px; }

/* Pinned (large) cards */
.pinned-grid .card {
  padding: 46px 22px 28px;
  min-height: 340px;
  cursor: grab;
  container-type: inline-size;
}
/* City / time / date sit as one vertically-centered cluster; now + UTC stay pinned
   to the bottom via .card-foot's margin-top:auto. */
.pinned-grid .card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.pinned-grid .card:active { cursor: grabbing; }
.pinned-grid .card.dragging { opacity: 0.4; }
/* Drop indicator (grid view): a vertical ink line at the drop boundary. Drawn as a
   box-shadow (NOT ::after) so the card's overflow:hidden can't clip it; z-index
   lifts it over the neighbour.
   - Interior boundary: an OUTSET shadow to the right of the preceding card, landing
     exactly in the 2px gutter between the two cards.
   - Grid edges (very front = first card's left; end of a row = last card's right):
     no gutter exists there, so the line is INSET onto that edge instead. */
.pinned-grid .card.drop-line-before,
.pinned-grid .card.drop-line-after { z-index: 6; }
.pinned-grid .card.drop-line-after { box-shadow: 2px 0 0 0 #111111; }
.pinned-grid .card.drop-line-before { box-shadow: inset 2px 0 0 0 #111111; }
.pinned-grid .card.drop-line-after.drop-line-edge { box-shadow: inset -2px 0 0 0 #111111; }
:root[data-theme="dark"] .pinned-grid .card.drop-line-after { box-shadow: 2px 0 0 0 #f1f0ea; }
:root[data-theme="dark"] .pinned-grid .card.drop-line-before { box-shadow: inset 2px 0 0 0 #f1f0ea; }
:root[data-theme="dark"] .pinned-grid .card.drop-line-after.drop-line-edge { box-shadow: inset -2px 0 0 0 #f1f0ea; }
.pinned-grid .city-name { margin-bottom: 12px; }
/* Card-relative like the all-cities time; a touch smaller to leave room for the
   pencil / calendar affordances in the editable card. */
.pinned-grid .time { font-size: clamp(34px, 16cqw, 72px); }
/* Read-only pinned cards lack the editable card's underline; reserve the same
   space (4px padding + 2px border) so every card's date lines up. */
.pinned-grid .time:not(.time-edit) { padding-bottom: 4px; border-bottom: 2px solid transparent; }
.pinned-grid .card-date { font-size: 15px; font-weight: 400; margin-top: 14px; }
.pinned-grid .card-foot {
  margin-top: auto;
  padding-top: 24px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}
.pinned-grid .now { font-size: 13px; color: var(--muted); }

/* All-cities cards: city + time vertically centered; a footer (now / date + UTC)
   reveals on hover. Two-size scale — big time + one small size for the rest. */
/* The whole tile toggles the pin, so it reads as one big button. */
.all-grid .card {
  padding: 44px 22px 20px;
  min-height: 280px;
  container-type: inline-size;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}
.all-grid .card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center; /* vertically center the city + time cluster */
}
.all-grid .city-name { margin-bottom: 6px; } /* city + time sit close together */
/* Search-context: a country/region search appends a country shorthand to the city
   name (same ink as the name); a UTC search reveals the hover foot (now + UTC). */
.all-grid .card.show-meta .card-foot { opacity: 1; }
/* Sized to the card (not the viewport) so the time is as big as fits its own
   tile at any column count, and the full-size AM/PM never overflows. */
.all-grid .time { font-size: clamp(34px, 18cqw, 72px); }
/* Footer: now on the left, date + UTC on the right; revealed on hover. Space is
   always reserved (opacity, not display) so nothing shifts. */
.all-grid .card-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.all-grid .card:hover .card-foot,
.all-grid .card:focus-within .card-foot { opacity: 1; }
.all-grid .foot-right { display: inline-flex; align-items: baseline; gap: 14px; }
.all-grid .now,
.all-grid .card-date-sm,
.all-grid .utc { font-size: 13px; color: var(--muted); }
/* Mobile only: shave the footer meta to 11px so now/date/UTC fit the narrower cards
   (desktop stays 13px). Placed after the base rules so it wins at equal specificity. */
@media (max-width: 720px) {
  .utc { font-size: 11px; }
  .pinned-grid .now { font-size: 11px; }
  .all-grid .now,
  .all-grid .card-date-sm,
  .all-grid .utc { font-size: 11px; }
}
@media (hover: none) {
  .all-grid .card-foot { opacity: 1; } /* touch: no hover, so keep meta visible */
}

/* Footer values never break mid-token (no "UTC-\n5" or "10:39\nPM"). */
.card-foot .foot-val,
.card-foot .utc { white-space: nowrap; }
/* On a narrow card (any column count — driven by the card's own width, not the
   viewport) stack each label over its value: "now" / time and weekday / date,
   dropping the date comma. This keeps everything on tidy two-line columns and
   leaves UTC room to stay on one line. */
@container (max-width: 250px) {
  .card-foot .foot-lbl { display: block; }
  .card-foot .foot-com { display: none; }
  .card-foot .foot-val { display: block; }
  /* Three columns — now · date · UTC — spread with equal gaps. display:contents
     dissolves the date+UTC wrapper so all three space-between evenly across the row. */
  .all-grid .card-foot {
    display: flex;
    justify-content: space-between;
    align-items: start;
    gap: 8px;
  }
  .all-grid .foot-right { display: contents; }
}

/* Empty placeholder slots — cascade in (staggered fade) when the grid widens to
   multiple columns; on the way down to mobile 1-up they're removed instantly. */
.slot-empty { background: var(--empty); min-height: 340px; transition: opacity 0.28s ease; }
.slot-empty.entering { opacity: 0; }

/* ---------- Custom calendar popover ---------- */
.cal-pop {
  position: fixed;
  z-index: 200;
  width: 300px;
  background: var(--surface);
  border: 0;
  border-radius: 24px;
  padding: 16px 16px 12px;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.16);
  font-family: inherit;
  color: var(--ink);
}
.cal-pop[hidden] { display: none; }
/* Desktop popover: fade + slight rise on open/close (the drawer has its own slide).
   JS toggles .open a frame after it's shown, and defers hiding until this finishes. */
.cal-pop:not(.drawer) {
  opacity: 0;
  transform: translateY(-6px) scale(0.985);
  transform-origin: top center;
  transition: opacity 0.15s ease, transform 0.16s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.cal-pop:not(.drawer).open { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .cal-pop:not(.drawer) { transition: none; }
}
.cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}
.cal-title { flex: 1; text-align: center; font-weight: 400; font-size: 15px; }
.cal-nav {
  width: 30px;
  height: 30px;
  flex: none;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 18px;
  line-height: 1;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cal-nav:hover { background: var(--track); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-dows { margin-bottom: 2px; }
.cal-dow { text-align: center; font-size: 12px; color: var(--muted); padding: 4px 0; }
.cal-day {
  border: 0;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}
.cal-day:hover { background: var(--track); }
.cal-day.other { color: var(--muted); }
.cal-day.past { color: var(--muted); } /* dates before today, de-emphasized */
/* Today gets a gray fill so it's clearly marked; the ink selection overrides it
   when today is the selected day. */
.cal-day.today { background: var(--track); }
.cal-day.selected { background: var(--ink); color: var(--bg); font-weight: 500; }
.cal-foot { display: flex; justify-content: flex-end; margin-top: 8px; }
.cal-today-btn {
  border: 0;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
  font-weight: 400;
  cursor: pointer;
  padding: 6px 14px;
  border-radius: 999px;
}
.cal-today-btn:hover { background: var(--track); }

/* Calendar as a bottom drawer on mobile */
.cal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 199;
  background: rgba(0, 0, 0, 0.4);
  opacity: 0;
  transition: opacity 0.24s ease;
}
.cal-backdrop.open { opacity: 1; }
.cal-backdrop[hidden] { display: none; }

@media (max-width: 640px) {
  .cal-pop.drawer {
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: 100%;
    border-radius: 18px 18px 0 0;
    padding: 10px 18px calc(20px + env(safe-area-inset-bottom));
    box-shadow: 0 -10px 34px rgba(0, 0, 0, 0.22);
    transform: translateY(100%);
    transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .cal-pop.drawer.open { transform: translateY(0); }
  .cal-pop.drawer::before {
    content: "";
    display: block;
    width: 40px;
    height: 4px;
    margin: 0 auto 14px;
    border-radius: 999px;
    background: var(--field-border);
  }
  .cal-pop.drawer .cal-day { font-size: 15px; }
  .cal-pop.drawer .cal-title { font-size: 16px; }
}

/* Search: no matches / result note */
.no-results,
.results-note {
  grid-column: 1 / -1;
  padding: 28px var(--edge);
  color: var(--muted);
  font-size: 15px;
}

/* Epoch (Unix time) tool — the strip under the search: convert an entered timestamp, or
   copy the current moment's epoch. Hidden until relevant (see js/app.js renderEpochBar). */
.epoch-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin: 0 var(--edge) 20px; /* aligned to the search — no banner; this is a secondary tool */
  font-size: 14px; /* same as .search */
  color: var(--muted);
}
.epoch-bar[hidden] { display: none; }
.epoch-lead b { color: var(--ink); font-weight: 500; }
.epoch-when { color: var(--ink); }
.epoch-hint { color: var(--muted); }
/* Epoch chip (the timestamp itself) sits right after the time; click to copy. */
.epoch-val {
  font: inherit;
  border: 0;
  color: var(--ink);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  border-radius: 8px;
  padding: 4px 10px;
  cursor: pointer;
  transition: background 0.12s;
}
.epoch-val:hover { background: color-mix(in srgb, var(--ink) 15%, transparent); }
/* Action ("Copy epoch" / "Set as current time") — a subtle text link at the right edge. */
.epoch-btn {
  font: inherit;
  margin-left: auto;
  color: var(--ink);
  background: transparent;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--ink) 30%, transparent);
  transition: text-decoration-color 0.12s;
}
.epoch-btn:hover { text-decoration-color: var(--ink); }
@media (max-width: 720px) {
  /* The epoch chip is itself tappable to copy, so drop the redundant "Copy epoch" link. */
  .epoch-btn[data-epoch-copy] { display: none; }
  /* Left-align "Set as current time" instead of pushing it to the right edge. */
  .epoch-btn[data-epoch-set] { margin-left: 0; }
}

/* ---------- Pinned timeline (list view) ---------- */
/* List view changes ONLY the pinned section: each city becomes a row of 24 hour
   cells on one shared absolute-time axis, so a vertical column is a single moment
   and you can read across cities. All-cities stays the card grid untouched. */
.pinned-grid.timeline {
  grid-template-columns: minmax(128px, 200px) repeat(24, minmax(30px, 1fr));
  gap: 1px;                        /* hairline between cells = the page background */
  overflow-x: auto;                /* the strip scrolls on narrow screens */
  scrollbar-width: none;           /* hide the scrollbar (Firefox), keep scrolling */
  -ms-overflow-style: none;        /* legacy Edge/IE */
}
.pinned-grid.timeline::-webkit-scrollbar { display: none; } /* Chrome/Safari/iOS */

/* Empty list: one blank row matching the timeline columns (label + 24 cells).
   Inherits the column template from .timeline; the label cell stays sticky. */
.pinned-grid.timeline-empty .slot-empty { min-height: 64px; }
.pinned-grid.timeline-empty .slot-empty:first-child { position: sticky; left: 0; z-index: 2; }

/* Sticky city label carries the city color, name, and its exact time/day/offset. */
.tl-label {
  position: sticky;
  left: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 12px 14px;
  background: linear-gradient(160deg, var(--grad-a, var(--track)), var(--grad-b, var(--track))); /* sky gradient for this city's local hour */
  color: var(--ink, #111111);
  --muted: color-mix(in srgb, var(--ink, #111111) 55%, transparent);
  cursor: grab;
}
.tl-label:active { cursor: grabbing; }
.tl-label.dragging { opacity: 0.4; }
/* Drop indicator (list view): a full-width horizontal ink line spanning the whole
   grid row at the drop boundary. The timeline is a flat grid with no per-row
   wrapper, so JS positions this one element (absolute, ignored by grid layout)
   across the full scroll width at the row boundary. */
.pinned-grid .drop-line-el {
  position: absolute;
  left: 0;
  height: 3px;
  border-radius: 3px;
  background: #111111;
  z-index: 5;
  pointer-events: none;
}
:root[data-theme="dark"] .pinned-grid .drop-line-el { background: #f1f0ea; }
.tl-city { font-weight: 400; font-size: 15px; letter-spacing: 0.02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 22px; }
.tl-meta { font-size: 13px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-meta .tl-time { color: var(--ink); font-weight: 400; }
/* Offset from the top (anchor) row, e.g. +9h — the "how far ahead/behind" at a glance. */
.tl-off { font-variant-numeric: tabular-nums; color: color-mix(in srgb, var(--ink) 72%, transparent); }
/* Every timeline row is pinned, so its pin button reads as a compact unpin control. */
.tl-label .pin-btn { top: 8px; right: 8px; width: 22px; height: 22px; border-radius: 50%; opacity: 1; }
.tl-label .pin-btn:hover .icon { transform: rotate(45deg); } /* "+" -> "✕" to signal remove */

.tl-cell {
  border: 0;
  margin: 0;
  font-family: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--tl-day);
  min-height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.1s ease;
}
.tl-cell.is-night { background: var(--tl-night); color: var(--muted); }   /* local night hours */
/* Column guide: hovering any cell lights its whole column (same shared moment) in
   the yellow hover color so you can read one absolute time straight down every row.
   Uniform across day/night cells; the "now" column (is-active) still wins below. */
.tl-cell.col-hover { background: color-mix(in srgb, var(--ink) 16%, var(--tl-night)); color: var(--ink); }
/* Local midnight (day change): a full ink line sitting IN the 1px gap between the
   prior (11p) cell and this midnight cell — i.e. replacing that hairline, not inset
   inside the box. Negative offset places it in the gap; later DOM paint keeps it on top. */
.tl-cell.is-daybreak { box-shadow: -1px 0 0 var(--ink); } /* day boundary line */
/* ...unless it lands on the first cell (row's left edge), where it'd hug the label. */
.tl-label + .tl-cell.is-daybreak { box-shadow: none; }
.tl-cell:hover { background: color-mix(in srgb, var(--ink) 16%, var(--tl-night)); color: var(--ink); }
/* the selected moment — pure white (light) / near-black (dark), the same chip treatment
   as the logo, popping against the off-white / off-black cells without a harsh fill */
.tl-cell.is-active { background: var(--chip); color: var(--ink); font-weight: 500; }

/* Day navigator (‹ date ›) sits next to the "Pinned" label in both views. */
.tl-daynav { position: relative; display: flex; align-items: center; gap: 8px; }
.tl-daynav[hidden] { display: none; }
.tl-nav {
  width: 46px; /* match the mode-toggle circles: filled gray, no border */
  height: 46px;
  border: 0;
  border-radius: 999px;
  background: var(--chip);
  color: var(--ink);
  font-family: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease;
}
.tl-nav:hover { background: var(--hover); } /* subtle fill, matches the rest of the site */
/* Desktop: arrows hidden for a cleaner top (the date pill opens the calendar, which
   reaches any date). Mobile keeps the chevrons — restored in the media query below. */
.tl-nav { display: none; }
.tl-date {
  height: 46px; /* match the mode-toggle circles */
  display: inline-flex;
  align-items: center;
  gap: 8px; /* space between the date text and the calendar icon */
  border: 0;
  background: var(--chip);
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400; /* match the copyable times text */
  padding: 0 18px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease;
}
.tl-date:hover { background: var(--hover); }
.tl-date-edit { position: absolute; right: 34px; bottom: 0; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

@media (max-width: 720px) {
  .pinned-grid.timeline { grid-template-columns: 116px repeat(24, 44px); }
  .section-head { flex-wrap: wrap; }
  /* "All cities" label on its own row; search wraps below it, full width. */
  .all-head { gap: 14px 24px; }
  .all-head .search-wrap { flex-basis: 100%; }
  /* iOS Safari zooms the page when a tapped input's font-size is < 16px. Keep the real
     font-size at 16px (so it doesn't zoom) but render it at 14px via a transform, to match
     the epoch text below. The input is widened to 100/87.5% and the wrap clips the pre-scale
     overflow so the underline still spans full width. */
  .search-wrap { overflow: hidden; }
  .search {
    font-size: 16px;
    width: 114.2857%;
    transform: scale(0.875);
    transform-origin: left center;
  }
  /* Header is just the label + day switcher; copyable times float at the bottom. */
  .tl-daynav { width: 100%; }
  .tl-nav { display: flex; } /* chevrons return on mobile */
  /* Chevrons + date pill inherit --bar-h; the date pill fills between them. */
  .tl-date {
    flex: 1;
    justify-content: center;
  }
  .tl-date .icon { display: none; } /* no calendar icon on mobile (chevrons signal it) */
  /* Copyable times: a compact one-line bar floating at the bottom, always
     reachable while scrolling. Tap still copies the full text. */
  .pinned-text {
    position: fixed;
    left: calc(var(--edge) + 60px); /* room for the settings gear (48px + 12px gap) */
    right: var(--edge);
    transform: none; /* reset the desktop bottom-center centering */
    max-width: none;
    bottom: calc(14px + env(safe-area-inset-bottom));
    z-index: 60;
    /* Explicit width: the flex row's nowrap content would otherwise stretch it
       to full width (clamped by max-width:100%) and cancel the side gaps. */
    width: calc(100% - var(--edge) * 2 - 60px);
    height: 48px;
    margin: 0;
    padding: 0 6px 0 18px; /* right = circle's top/bottom inset ((48 − 36)/2) */
    display: flex;
    align-items: center;
    gap: 12px;
    border-radius: 999px;
    background: var(--chip); /* mobile floats over cards: chip pill + shadow */
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
    transition: width 0.34s cubic-bezier(0.22, 1, 0.36, 1), background 0.18s ease;
  }
  .pinned-text .pt-lines {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;         /* one line; link icon stays at the right */
  }
  /* Reserve space so the floating bar never covers the last cards */
  main { padding-bottom: calc(90px + env(safe-area-inset-bottom)); }
}

/* ---------- Toast ---------- */
.toast {
  position: fixed;
  top: max(20px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--bg);
  font-family: inherit;
  font-size: 13px;
  padding: 10px 18px;
  border-radius: 999px;
  z-index: 70;
}
.toast[hidden] { display: none; }
/* On mobile the top bar is crowded (brand + share), so drop the toast to the bottom,
   above the floating times bar; desktop keeps it at the top. */
@media (max-width: 720px) {
  .toast { top: auto; bottom: calc(74px + env(safe-area-inset-bottom)); }
}

/* ---------- Scroll-to-top ---------- */
/* Appears (via .show, toggled by JS) once you've scrolled the browse grid down. */
.scroll-top {
  position: fixed;
  right: var(--edge);
  bottom: 28px;
  z-index: 65;
  width: 46px;
  height: 46px;
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  color: var(--bg);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.18);
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none; /* not interactive until shown */
  transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s ease;
}
.scroll-top.show { opacity: 1; transform: none; pointer-events: auto; }
.scroll-top:hover { background: color-mix(in srgb, var(--ink) 84%, var(--bg)); }
@media (max-width: 720px) {
  /* Sit on the SAME row as the copyable times bar and slide in from the right edge
     (instead of floating awkwardly above it). The bar shortens to make room. */
  .scroll-top {
    bottom: calc(14px + env(safe-area-inset-bottom));
    width: 48px;
    height: 48px; /* match the times bar */
    transform: translateX(64px); /* parked just off the right edge, hidden */
    transition: opacity 0.25s ease,
      transform 0.34s cubic-bezier(0.22, 1, 0.36, 1), background 0.15s ease;
  }
  .scroll-top.show { transform: translateX(0); } /* slide in from the side */
  /* Shorten the copyable times bar while the arrow is in, so they sit side by side
     (48px button + 12px gap = 60px). */
  body.scrolltop-shown .pinned-text { width: calc(100% - var(--edge) * 2 - 120px); }
}

/* ---------- Responsive ---------- */
@media (max-width: 720px) {
  :root { --edge: 18px; --bar-h: 48px; }
  /* Settings floats at the bottom-left, level with the copyable times pill (a
     matching white round button), instead of sitting up in the crowded top bar. */
  .settings-btn {
    display: inline-flex;
    position: fixed;
    left: var(--edge);
    bottom: calc(14px + env(safe-area-inset-bottom));
    width: 48px;
    height: 48px;
    z-index: 60;
    background: var(--chip);
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
  }
  .settings-btn:hover { background: var(--chip); }
  .desk-toggles { display: none; }            /* mobile uses the drawer, not circles */
  .topbar > .controls { display: none; }      /* JS moves them into the drawer */
  .pinned-grid { grid-template-columns: 1fr; }          /* pinned = full-width hero cards, one per row */
  .all-grid { grid-template-columns: repeat(2, 1fr); }  /* browse stays 2-up for density */
  /* Shorter full-width pinned cards so 3-5 fit above the fold; time scales down to match.
     Side padding stays 22px to match the rest of the site's margins. */
  .pinned-grid .card { min-height: 168px; padding: 26px 22px 16px; }
  .pinned-grid .time { font-size: clamp(30px, 12cqw, 44px); }
  /* Smaller "+" on mobile so it clears the pencil edit icon on the narrow pinned card. */
  .pin-btn { width: 38px; height: 38px; }
}
@media (max-width: 480px) {
  .brand { padding: 10px 14px; font-size: 14px; }
  /* Share collapses to a round icon button next to settings */
  .share-btn { width: 44px; height: 44px; padding: 0; gap: 0; justify-content: center; }
  .share-label { display: none; }
}
