/* ==========================================================================
   Skyclock — styles.css
   Minimalist full-screen world clock. Offline-only: no imports, no remote fonts.
   ========================================================================== */

/* ---------- Reset & base ---------- */

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

html,
body {
  height: 100%;
  overflow: hidden;
  background: #0a0e23;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  color: #e8eaf6;
  -webkit-font-smoothing: antialiased;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

/* ---------- Surface tokens ----------
   Panels and popovers are frosted LIGHT glass rather than dark slabs, so they
   belong to the same world as the gradients behind them. The saturate() in the
   blur is what does the work: it pulls the colour of whichever columns sit
   underneath up through the glass, so the panel is peach at sunrise and violet
   at dusk instead of a constant grey box.

   The veil has to be opaque enough to survive the WORST backdrop. A panel is
   translucent, so its real background is (veil over whatever column sits
   behind it) — at 66% white over a midnight column that composites to about
   rgb(172,173,181), where secondary ink falls to 3.2:1 and looks washed out.
   At 80% it composites to ~rgb(206,207,212) in the same spot, which holds
   9.8:1 for --ink and 4.6:1 for --ink-soft over EVERY gradient, pale or dark.
   saturate(200%) then buys back the colour that the extra opacity costs, so
   the glass still blushes with the time of day. */

:root {
  --surface: rgba(255, 255, 255, 0.8);
  --surface-strong: rgba(255, 255, 255, 0.9); /* popovers over busy gradients */
  --surface-blur: blur(30px) saturate(200%);
  --surface-edge: rgba(255, 255, 255, 0.6);
  --surface-shadow: -20px 0 60px rgba(15, 20, 40, 0.16);
  --surface-shadow-pop: 0 14px 34px rgba(15, 20, 40, 0.18);

  --ink: #17203a;
  --ink-soft: rgba(23, 32, 58, 0.7); /* body + secondary: >=4.6:1 anywhere */
  --ink-faint: rgba(23, 32, 58, 0.58); /* glyphs + counts only, ~3.5:1 */
  --ink-wash: rgba(23, 32, 58, 0.07); /* row hover */
  --ink-line: rgba(23, 32, 58, 0.13); /* hairlines */
  --ink-track: rgba(23, 32, 58, 0.18);

  --accent: #5b6ee0;
  --accent-ring: rgba(91, 110, 224, 0.4);
  --accent-wash: rgba(91, 110, 224, 0.14);
  /* deep enough to stay legible as a glyph colour ON the light panel: the old
     #d94f68 dropped the hovered × to 2.04:1, i.e. hovering hid the control */
  --danger: #a82a42;
}

/* ---------- Utility: .hidden ----------
   Default pattern: hard display none.
   #panel-overlay and #settings-panel override this below so they can
   transition in/out (visibility + opacity + transform instead). */

.hidden {
  display: none;
}

/* ---------- Utility: .visually-hidden ----------
   For a label that must exist for a screen reader but never draw: the quick-add
   dialog's <h2>, which is there only to satisfy aria-labelledby. NOT .hidden —
   display:none would drop it out of the accessibility tree, which is the one
   thing it is for. Same recipe the .switch inputs carry inline. */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ---------- Board & clock columns ---------- */

#board {
  display: flex;
  flex-direction: row;
  width: 100vw;
  height: 100vh; /* fallback */
  height: 100dvh;
}

.clock-column {
  flex: 1;
  min-width: 0;
  /* Makes the column itself the sizing reference, so 1cqw = 1% of THIS column.
     Text then shrinks as places are added instead of overflowing — sizing off
     the viewport (vw) ignores how many columns are dividing it up. */
  container-type: inline-size;
  /* container-type already applies layout containment, which by itself makes the
     column the containing block for absolutely positioned children (.col-actions,
     the picker dropdown). Stated explicitly so that relationship is not an
     accident of the container query. No offsets -> no layout change. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* hints that the column is interactive (click to edit, drag to scrub) */
  cursor: pointer;
  /* inline style supplies background (gradient) + color (text) */
  transition: background 0.4s ease, color 0.4s ease;
}

.clock-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.35rem;
  padding: 1rem;
  max-width: 100%;
  color: currentColor;
}

.clock-city {
  /* also column-relative, so long names like ADDIS ABABA stay on one line */
  font-size: clamp(0.7rem, 8cqw, 1.15rem);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: currentColor;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.clock-country {
  font-size: clamp(0.6rem, 5.5cqw, 0.75rem);
  letter-spacing: 0.05em;
  opacity: 0.7;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.clock-time {
  display: flex;
  align-items: baseline;
  justify-content: center;
  margin-block: 0.4rem;
}

.time-main {
  /* 26cqw keeps the widest case ("12:02 PM") inside the column at every place
     count, with ~10% breathing room. Measured: the string runs ~3.44em wide. */
  font-size: clamp(1.4rem, 26cqw, 6.5rem);
  font-weight: 200;
  line-height: 1;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.time-meridiem {
  /* 0.35 of .time-main's size, tracking it through the same container unit */
  font-size: clamp(0.49rem, 9.1cqw, 2.275rem);
  font-weight: 400;
  letter-spacing: 0.14em;
  margin-left: 0.5em;
  transform: translateY(-0.35em);
  opacity: 0.85;
}

/* ---------- Inline time editor (time travel) ----------
   JS swaps .time-main for this input inside .clock-time. It must occupy the
   same visual box as .time-main so nothing jumps when editing begins. */

.time-edit {
  font: inherit;
  font-size: clamp(1.4rem, 26cqw, 6.5rem); /* identical to .time-main */
  font-weight: 200;
  line-height: 1;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  width: 4.5ch;
  min-width: 0;
  padding: 0;
  text-align: center;
  color: currentColor;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent; /* reserved so focus adds no height */
  border-radius: 3px 3px 0 0;
  outline: none;
  -webkit-appearance: none;
  -moz-appearance: textfield;
  appearance: none;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

/* Soft focus affordance, tinted from the column's own text colour so it
   reads on both midday (light) and midnight (dark) gradients. */
.time-edit:focus,
.time-edit:focus-visible {
  outline: none;
  border-bottom-color: currentColor;
  border-bottom-color: color-mix(in srgb, currentColor 35%, transparent);
  background: color-mix(in srgb, currentColor 8%, transparent);
}

.time-edit::selection {
  color: inherit;
  background: color-mix(in srgb, currentColor 26%, transparent);
}

.time-edit::placeholder {
  color: currentColor;
  opacity: 0.35;
}

/* Kill number-input spinners in case type="number" is used */
.time-edit::-webkit-outer-spin-button,
.time-edit::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}

.clock-diff,
.clock-day {
  font-size: clamp(0.55rem, 5cqw, 0.7rem);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  opacity: 0.75;
}

.clock-day {
  font-style: italic;
  font-weight: 300;
}

/* Collapse when the JS leaves them empty */
.clock-diff:empty,
.clock-day:empty,
.clock-country:empty {
  display: none;
}

/* Entrance animation for newly added columns */
.col-enter {
  animation: col-enter 0.4s ease-out both;
}

@keyframes col-enter {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* ---------- Per-column hover controls ----------
   Two icon buttons parked in the column's top-right. Invisible until the column
   is hovered (or something inside it is focused), so the board stays clean.

   Legibility over BOTH pale midday and dark midnight gradients is handled the
   same way twice over: the icon is currentColor (the gradient system already
   guarantees that colour contrasts with its own column), and the chip carries a
   white veil PLUS a currentColor-derived inset hairline. The white veil is what
   reads on dark columns; the currentColor hairline is what reads on pale ones,
   where a white veil/border washes out entirely. */

.col-actions {
  /* In the flow at the end of .clock-inner, so the column's own centring puts
     them under the clock. Being in the flow also means they hold their space
     while hidden — revealing them never nudges the time. */
  display: flex;
  flex-direction: row;
  gap: 10px;
  margin-top: 0.9rem;
  opacity: 0;
  /* not just invisible — unclickable, so an unhovered column cannot be hit */
  pointer-events: none;
  transition: opacity 0.18s ease;
}

/* :focus-within as well as :hover — the controls have to be reachable by
   keyboard, and they must stay put while the inline picker inside has focus */
.clock-column:hover .col-actions,
.clock-column:focus-within .col-actions {
  opacity: 1;
  pointer-events: auto;
}

.col-action {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: currentColor; /* icon inherits the column's light/dark text colour */
  cursor: pointer;
  /* soft lift, same trick as #now-btn, so the chip has an edge on pale gradients */
  box-shadow: 0 2px 10px rgba(5, 8, 20, 0.14);
  box-shadow: 0 2px 10px rgba(5, 8, 20, 0.14),
    inset 0 0 0 1px color-mix(in srgb, currentColor 16%, transparent);
  transition: background-color 0.18s ease, transform 0.18s ease,
    color 0.18s ease;
}

/* fixed 14px: the icon must not be squeezed by the 26px flex/grid box */
.col-action svg {
  display: block;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.col-action:hover {
  background: rgba(255, 255, 255, 0.26);
  transform: scale(1.06);
}

/* The panel's flat --danger cannot be reused here: this button sits directly on
   a gradient, where a dark red disappears at midnight and a light pink washes
   out at midday. Mixing red INTO currentColor keeps it in the column's own
   luminance family, so it reads as red on both. */
.col-remove:hover,
.col-remove:focus-visible {
  color: #ff8a9a;
  color: color-mix(in srgb, #ff4d6a 60%, currentColor);
  background: rgba(255, 138, 154, 0.22);
}

.col-action:focus-visible {
  outline: 2px solid rgba(111, 141, 255, 0.9);
  outline-offset: 2px;
}

/* Touch: no hover state exists, so keep them on permanently but quiet */
@media (hover: none) {
  .col-actions {
    opacity: 0.5;
    pointer-events: auto;
  }
}

/* ---------- Inline city picker ----------
   JS swaps .clock-city for .city-edit-wrap, so the search field lands exactly
   where the place name was and the dropdown hangs beneath it. */

.city-edit-wrap {
  position: relative; /* anchor for .city-edit-results */
  /* bounded, so the dropdown's width:100% cannot stretch the column */
  width: min(92%, 260px);
  margin-inline: auto;
}

/* Legible on pale and dark alike: text is currentColor (contrast with the
   gradient is already guaranteed for it), the plate is a white veil that reads
   on dark columns, and the inset currentColor hairline keeps the field's edge
   visible on pale ones where the white border disappears. */
.city-edit {
  width: 100%;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-align: center;
  color: currentColor;
  background: rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 8px;
  padding: 7px 10px;
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 14%, transparent);
  transition: border-color 0.2s ease, box-shadow 0.2s ease,
    background-color 0.2s ease;
}

.city-edit::placeholder {
  color: currentColor;
  /* 0.45 measured only 2.3:1 on the pale midday plate, under the 3:1 floor —
     and it is the field's only label, since the picker opens empty. */
  opacity: 0.62;
}

.city-edit:focus,
.city-edit:focus-visible {
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(111, 141, 255, 0.75);
  box-shadow: 0 0 0 3px rgba(111, 141, 255, 0.2);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 14%, transparent),
    0 0 0 3px rgba(111, 141, 255, 0.2);
}

.city-edit::selection {
  color: inherit;
  background: color-mix(in srgb, currentColor 26%, transparent);
}

.city-edit::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* A popover, not column furniture: it wears the panel's frosted-light surface so
   it reads identically whichever gradient it opens over, and stays part of the
   same family as the settings panel. */
.city-edit-results {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  margin-top: 6px;
  max-height: 212px; /* fits 5 stacked rows without a scrollbar */
  overflow-y: auto;
  list-style: none;
  /* 4px inset rather than a bare 0 so a hovered row never touches the radius */
  padding: 4px;
  background: var(--surface-strong);
  -webkit-backdrop-filter: var(--surface-blur);
  backdrop-filter: var(--surface-blur);
  border: 1px solid var(--surface-edge);
  border-radius: 10px;
  box-shadow: var(--surface-shadow-pop);
  color: var(--ink); /* NOT currentColor — see above */
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  z-index: 25; /* clears sibling columns, stays under the pill and the panel */
  scrollbar-width: thin;
  scrollbar-color: var(--ink-track) transparent;
}

/* No matches -> no empty glass box */
.city-edit-results:empty {
  display: none;
}

.city-edit-results::-webkit-scrollbar {
  width: 6px;
}

.city-edit-results::-webkit-scrollbar-track {
  background: transparent;
}

.city-edit-results::-webkit-scrollbar-thumb {
  background: var(--ink-track);
  border-radius: 3px;
}

.city-edit-results::-webkit-scrollbar-thumb:hover {
  background: rgba(23, 32, 58, 0.32);
}

/* Stacked, not side-by-side: the popover is only ~157px wide inside a 7-place
   column, where a single row truncated BOTH the name and the subtitle into
   uselessness ("San Fra…" / "Californ…"). Giving the subtitle its own line
   hands each the full width, so names never truncate at all. The panel's
   .search-item keeps the side-by-side layout — it has 370px to play with. */
.city-edit-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  padding: 6px 9px;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.18s ease;
}

.city-edit-item:hover:not(:disabled),
.city-edit-item:focus-visible {
  background: var(--ink-wash);
}

.city-edit-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.city-edit-item:disabled {
  opacity: 0.45;
  cursor: default;
}

/* restated (not inherited from the panel's copies) so the popover stands alone.
   The popover lives inside a column, which can be only ~200px wide at 7 places,
   so the truncation floors matter far more here than in the 370px panel. */
.city-edit-item .loc-name {
  flex: 0 0 auto;
  min-width: 0;
  max-width: 100%;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.city-edit-item .loc-country {
  font-size: 0.7rem;
  color: var(--ink-soft);
  /* own line: reset the panel's push-to-the-right treatment */
  margin-left: 0;
  padding-left: 0;
  flex: 0 0 auto;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The column clips its content (overflow: hidden) to guard the time text, which
   would also clip this absolutely positioned dropdown. Unclip ONLY the column
   that currently holds a picker — the wrap exists in the DOM only while the
   picker is open, so this needs no JS class — and lift it over its siblings.
   Safe for the time text: .time-main/.time-edit are sized in cqw, which keeps
   the widest string inside the column at every supported width, and .clock-city
   / .clock-country carry their own ellipsis clipping. Every other column keeps
   overflow: hidden. */
.clock-column:has(.city-edit-wrap) {
  overflow: visible;
  z-index: 20;
}

/* ---------- Scrubbing (live drag) ----------
   While dragging, the gradient must track the pointer 1:1, so the 0.4s
   easing on .clock-column is switched off for the duration of the drag. */

body.scrubbing {
  cursor: ew-resize;
  -webkit-user-select: none;
  user-select: none;
}

body.scrubbing .clock-column {
  cursor: ew-resize;
  transition: none;
}

/* cursor is inherited, but these carry their own — restate for the drag */
body.scrubbing .clock-inner,
body.scrubbing .clock-time,
body.scrubbing .time-edit {
  cursor: ew-resize;
}

/* ...the inverse for the new controls: the resize cursor must NOT bleed onto
   them, since they stay clickable/typeable mid-drag. An explicit cursor on the
   element itself also beats the ns-resize variant set in the row-layout query. */
body.scrubbing .col-action {
  cursor: pointer;
}

body.scrubbing .city-edit {
  cursor: text;
}

/* user-select is inherited, but form controls (the .time-edit input) do not
   reliably inherit it — restate it across the board so a drag never selects */
body.scrubbing #board,
body.scrubbing #board * {
  -webkit-user-select: none;
  user-select: none;
}

/* ---------- Empty state ---------- */

#empty-state {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.9rem;
  letter-spacing: 0.08em;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
}

#empty-state.hidden {
  display: none;
}

/* ---------- Board controls (the Now pill + quick-add) ----------
   The wrapper owns the fixed placement that #now-btn used to carry itself, so
   the two controls travel as one group.

   Dead centre horizontally, high above the clocks: being off the present is a
   state you must notice, so it sits on the app's centre line rather than hiding
   in a corner. The old collisions are gone — .col-actions moved into the flow
   beneath each clock, and the row layout makes room below.

   Anchored to the CENTRE, not the top edge. The clocks are centred, so a
   top-anchored group drifts away from them as the window grows — at 540px tall
   it sat 3x the .col-actions gap above the block and much further on a tall
   screen. Hanging it off the same centre line keeps the gap roughly constant
   (~30px, about 1.5x the gap the action buttons keep below the block) at every
   viewport height. The -143px is tuned and must not drift: because the wrapper
   centres its children, the pill's own centre still lands on
   (viewport centre - 143px) whatever height the wrapper takes, so re-homing the
   offset here moves the pill by exactly nothing. Row layout re-pins it to the
   top; see the media query, where the rows are not centred.

   gap is 0 ON PURPOSE. The spacing between the pill and the + is a margin on
   the pill instead, because a flex gap still applies either side of a
   zero-width item: with the pill collapsed, a 10px gap would shove the + 5px
   off the centre line — and the pill is collapsed most of the time. */

#board-controls {
  position: fixed;
  top: 50%;
  left: 50%;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0;
  transform: translateX(-50%) translateY(calc(-50% - 143px));
  z-index: 35; /* above the columns, below #panel-overlay / #settings-panel */
}

/* ---------- Now button (return from time travel) ----------
   Toggled by .hidden, so it uses the same transitioned-hidden pattern as
   #settings-panel / #panel-overlay (visibility + opacity) rather than
   display:none, otherwise the entrance transition would never run.

   It also COLLAPSES when hidden — max-width/padding/margin/border all to zero —
   rather than merely fading. A faded-but-present pill would hold its box and
   leave the + parked ~70px right of centre whenever you are in the present,
   which is nearly always. Collapsing lets the + slide to dead centre as the
   pill fades out, and slide back as it fades in. overflow:hidden + nowrap keep
   the label from reflowing to two lines on the way through; min-width:0 stops
   the flex automatic minimum size from re-inflating the box. */

#now-btn {
  flex: 0 0 auto;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  min-width: 0;
  /* Only a ceiling, never a width: the pill still sizes to its own text. Wide
     enough that even a long offset ("+13d 23h 45m") can never be clipped by it.
     The cost of the headroom is that the last stretch of the max-width
     transition is invisible (the pill has already stopped growing), which is
     why the collapse runs a touch longer than the fade. */
  max-width: 300px;
  margin-right: 10px;
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #ffffff;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
  /* keeps the white text legible over pale midday gradients too */
  text-shadow: 0 1px 10px rgba(5, 8, 20, 0.45);
  box-shadow: 0 2px 16px rgba(5, 8, 20, 0.16);
  /* Relative now, not absolute: the wrapper holds the centring, so the pill's
     transform is free to carry only its own 1px hover lift and exit slide. */
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),
    transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
    max-width 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    padding 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    margin-right 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    border-width 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    background-color 0.25s ease, visibility 0s linear 0s;
}

#travel-offset {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  opacity: 0.75;
}

.now-label {
  font-weight: 600;
  padding-left: 10px;
  border-left: 1px solid rgba(255, 255, 255, 0.22);
}

/* No offset text yet -> drop the divider so the pill reads as one word */
#travel-offset:empty {
  display: none;
}

#travel-offset:empty + .now-label {
  padding-left: 0;
  border-left: none;
}

#now-btn:hover,
#now-btn:focus-visible {
  background: rgba(255, 255, 255, 0.2);
  transform: translateY(-1px); /* 1px lift */
}

#now-btn:focus-visible {
  /* not clipped by the pill's own overflow:hidden — an element's outline is
     painted outside its border box and only descendants are clipped */
  outline: 2px solid rgba(111, 141, 255, 0.9);
  outline-offset: 2px;
}

/* Must follow :hover — equal specificity, later rule wins */
#now-btn.hidden {
  display: flex; /* keep rendered so the fade + collapse can transition */
  opacity: 0;
  transform: translateY(-8px); /* slides up out */
  /* every box contribution to zero, so the + lands on the exact centre line.
     border-width included: box-sizing can shrink the padding box but never the
     border, so a 1px border either side would leave the + 1px off centre — and
     it is transitioned above so the collapse does not start with a 2px jump. */
  max-width: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
  border-width: 0;
  visibility: hidden; /* also takes it out of the tab order */
  pointer-events: none;
  transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),
    transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
    max-width 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    padding 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    margin-right 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    border-width 0.28s cubic-bezier(0.4, 0, 0.2, 1),
    visibility 0s linear 0.28s;
}

/* ---------- Quick-add button ----------
   Always visible, unlike the pill — it is the shortcut that keeps adding a
   place out of the settings panel. Same frosted family as the pill so the pair
   reads as one control group: white veil, blur, hairline, white glyph. The
   pill's text-shadow becomes a drop-shadow filter on the icon (text-shadow does
   not reach SVG strokes), which is what keeps a white plus legible where a
   midday gradient goes near-white behind it. */

#quick-add-btn {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.13);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #ffffff;
  cursor: pointer;
  box-shadow: 0 2px 16px rgba(5, 8, 20, 0.16);
  transition: background-color 0.2s ease, transform 0.2s ease;
}

#quick-add-btn svg {
  display: block;
  flex-shrink: 0;
  filter: drop-shadow(0 1px 8px rgba(5, 8, 20, 0.45));
}

#quick-add-btn:hover,
#quick-add-btn:focus-visible {
  background: rgba(255, 255, 255, 0.22);
  transform: translateY(-1px);
}

/* the palette is open — hold the button lit so it reads as the active source */
#quick-add-btn[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.24);
}

#quick-add-btn:focus-visible {
  outline: 2px solid rgba(111, 141, 255, 0.9);
  outline-offset: 2px;
}

/* ---------- Quick-add palette ----------
   A command palette, not a modal: it sits in the upper third so the eye lands
   on the field immediately and the results grow downward into empty space,
   rather than a box pinned to the dead centre that pushes its own list off both
   edges of the viewport as it fills.

   .hidden gives it a hard display:none (the base utility) — nothing here needs
   to transition out, and display:none is also what guarantees the entrance
   animation cannot run, or replay on a background re-layout, while closed. */

#quick-add {
  position: fixed;
  inset: 0;
  /* over the board AND over #settings-panel (50), so it is never opened behind
     something. Its own children stack inside this context. */
  z-index: 60;
}

#quick-add-scrim {
  position: fixed;
  inset: 0;
  /* deeper than #panel-overlay's veil (0.16) because the palette is a light
     surface floating free rather than a panel anchored to an edge — it needs
     the board pushed back far enough to read as behind it */
  background: rgba(12, 18, 40, 0.3);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  z-index: 1;
}

#quick-add-panel {
  position: fixed;
  top: 14vh;
  left: 50%;
  transform: translateX(-50%);
  width: min(560px, 92vw);
  max-height: 66vh;
  display: flex;
  flex-direction: column;
  background: var(--surface-strong);
  -webkit-backdrop-filter: var(--surface-blur);
  backdrop-filter: var(--surface-blur);
  border: 1px solid var(--surface-edge);
  border-radius: 14px;
  box-shadow: var(--surface-shadow-pop);
  color: var(--ink);
  /* rows and the field must be cut by the radius, not spill over it */
  overflow: hidden;
  z-index: 2;
}

/* Entrance armed by :not(.hidden), the same way the panel's groups are: the
   declaration exists only while the palette is open, so it plays exactly when
   it should and can never strand the panel at opacity 0. The keyframes restate
   translateX(-50%) — an animated transform replaces the static one outright,
   and dropping it would fling the panel half its width to the right. */

@keyframes quick-add-in {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

@keyframes quick-add-scrim-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

#quick-add:not(.hidden) #quick-add-panel {
  animation: quick-add-in 0.2s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

#quick-add:not(.hidden) #quick-add-scrim {
  animation: quick-add-scrim-in 0.2s ease both;
}

/* ---------- Palette field ---------- */

.quick-add-field {
  position: relative;
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  /* the one hairline that separates the field from the results */
  border-bottom: 1px solid var(--ink-line);
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.quick-add-icon {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-faint);
  /* decorative: clicks must fall through to the input underneath */
  pointer-events: none;
  transition: color 0.2s ease;
}

/* The field is focused for the whole life of the palette (JS focuses it on
   open), so a standing 3px accent ring around the input would sit a hair inside
   the panel's own edge and read as a double border. The focus state is carried
   by the row instead: the separator hairline turns accent and the glyph lights
   up — a full-width indicator, and one that cannot collide with the radius. */
.quick-add-field:focus-within {
  border-bottom-color: var(--accent);
  background: rgba(255, 255, 255, 0.28);
}

.quick-add-field:focus-within .quick-add-icon {
  color: var(--accent);
}

#quick-add-input {
  width: 100%;
  /* 18 (icon left) + 17 (icon) + 10 (breathing room) = 45 */
  padding: 16px 18px 16px 45px;
  background: transparent;
  border: none;
  font: inherit;
  font-size: 1.05rem;
  line-height: 1.3;
  color: var(--ink);
  -webkit-appearance: none;
  appearance: none;
}

#quick-add-input:focus,
#quick-add-input:focus-visible {
  outline: none; /* deliberate — see .quick-add-field:focus-within above */
}

#quick-add-input::placeholder {
  /* --ink-faint is reserved for glyphs; as prose it drops under the floor */
  color: var(--ink-soft);
}

/* ---------- Palette results ---------- */

#quick-add-results {
  flex: 1 1 auto;
  /* without this a flex item refuses to shrink past its content, so the list
     would push the panel past max-height instead of scrolling inside it */
  min-height: 0;
  overflow-y: auto;
  /* the board does not scroll, but stop any chaining outright */
  overscroll-behavior: contain;
  list-style: none;
  /* 6px inset rather than 0, so an active row never touches the panel's edge */
  padding: 6px;
  margin: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--ink-track) transparent;
}

/* nothing typed yet -> no empty band between the field and the footer */
#quick-add-results:empty {
  display: none;
}

#quick-add-results::-webkit-scrollbar {
  width: 6px;
}

#quick-add-results::-webkit-scrollbar-track {
  background: transparent;
}

#quick-add-results::-webkit-scrollbar-thumb {
  background: var(--ink-track);
  border-radius: 3px;
}

#quick-add-results::-webkit-scrollbar-thumb:hover {
  background: rgba(23, 32, 58, 0.32);
}

.quick-add-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: none;
  border-radius: 9px;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.16s ease, box-shadow 0.16s ease;
}

/* Two lines like the settings rows. The shared .place-text/.loc-name/
   .loc-country/.place-time rules already carry the flex column, the min-width:0
   and the ellipsis — only the sizes are palette-specific. */
.quick-add-item .loc-name {
  font-size: 0.95rem;
}

.quick-add-item .loc-country {
  font-size: 0.75rem;
}

.quick-add-item:hover:not(:disabled) {
  background: var(--ink-wash);
}

/* Keyboard selection. This is the row ENTER will act on, so it has to beat
   hover outright rather than match it: hover is a neutral grey wash, the active
   row takes the accent wash, an accent left edge and a faint accent ring. When
   the pointer rests on one row while the keyboard cursor sits on another, the
   two states are different colours and only one has the bar.
   Listed with :hover as well because .quick-add-item:hover:not(:disabled) is
   the more specific selector — without it, hovering the active row would repaint
   it as an ordinary hover. */
.quick-add-item.is-active,
.quick-add-item.is-active:hover {
  background: var(--accent-wash);
  box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 1px var(--accent-ring);
}

.quick-add-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.quick-add-item:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ---------- Palette footer ---------- */

.quick-add-foot {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* row gap first: the hints wrap onto a second line on a narrow window */
  gap: 6px 14px;
  padding: 9px 14px;
  border-top: 1px solid var(--ink-line);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

/* each hint stays whole when the row wraps; the keys themselves are already
   spaced by the global `kbd + kbd` rule */
.quick-add-foot > span {
  white-space: nowrap;
}

/* ---------- Settings button ---------- */

#settings-btn {
  position: fixed;
  right: 20px;
  bottom: 20px;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: rgba(255, 255, 255, 0.55);
  border: none;
  transition: color 0.25s ease, background-color 0.25s ease,
    transform 0.4s ease;
  z-index: 30;
}

#settings-btn:hover,
#settings-btn:focus-visible {
  color: rgba(255, 255, 255, 1);
  background: rgba(255, 255, 255, 0.16);
  transform: rotate(45deg);
}

#settings-btn:focus-visible {
  outline: 2px solid rgba(111, 141, 255, 0.9);
  outline-offset: 2px;
}

#settings-btn svg {
  display: block;
}

/* ---------- Overlay ---------- */

#panel-overlay {
  position: fixed;
  inset: 0;
  /* barely-there veil: enough to push the board back, light enough that the
     gradients stay colourful instead of going flat behind a dark scrim */
  background: rgba(12, 18, 40, 0.16);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 1;
  visibility: visible;
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    visibility 0s linear 0s;
  z-index: 40;
}

#panel-overlay.hidden {
  display: block; /* keep in flow so opacity can transition */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    visibility 0s linear 0.3s;
  pointer-events: none;
}

/* ---------- Settings panel ---------- */

#settings-panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100%;
  width: min(370px, 92vw);
  background: var(--surface);
  -webkit-backdrop-filter: var(--surface-blur);
  backdrop-filter: var(--surface-blur);
  border-left: 1px solid var(--surface-edge);
  box-shadow: var(--surface-shadow);
  color: var(--ink);
  padding: 26px 24px 32px;
  overflow-y: auto;
  overflow-x: hidden;
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0s;
  z-index: 50;
  scrollbar-width: thin;
  scrollbar-color: var(--ink-track) transparent;
}

#settings-panel.hidden {
  display: block; /* keep rendered so the slide can transition */
  transform: translateX(105%);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0.3s;
  pointer-events: none;
}

#settings-panel::-webkit-scrollbar {
  width: 6px;
}

#settings-panel::-webkit-scrollbar-track {
  background: transparent;
}

#settings-panel::-webkit-scrollbar-thumb {
  background: var(--ink-track);
  border-radius: 3px;
}

#settings-panel::-webkit-scrollbar-thumb:hover {
  background: rgba(23, 32, 58, 0.32);
}

/* ---------- Panel entrance ----------
   The sections arrive a beat behind the panel itself, in order, so opening
   settings has a direction rather than just appearing.

   Driven from :not(.hidden) deliberately. The animation is not merely paused
   while the panel is closed — the declaration does not exist, so nothing can
   run, replay on a background re-layout, or leave the sections stranded at
   opacity 0 if the panel is closed mid-flight. Removing .hidden is what brings
   the rule into being, which is exactly the moment it should play.

   translateY only: a translateX would push the sections past the panel's right
   edge. The panel is overflow-x:hidden, but relying on that to hide an overshoot
   is how you get a phantom scrollbar on the first frame. */

@keyframes group-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

#settings-panel:not(.hidden) .setting-group {
  animation: group-in 0.22s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

/* nth-of-type, not nth-child: .panel-header is the panel's first child, so
   nth-child would number every section one too high. Counting <section> alone
   keeps the ladder anchored to the groups themselves. */
#settings-panel:not(.hidden) .setting-group:nth-of-type(1) {
  animation-delay: 0.05s;
}

#settings-panel:not(.hidden) .setting-group:nth-of-type(2) {
  animation-delay: 0.09s;
}

#settings-panel:not(.hidden) .setting-group:nth-of-type(3) {
  animation-delay: 0.13s;
}

#settings-panel:not(.hidden) .setting-group:nth-of-type(4) {
  animation-delay: 0.17s;
}

/* ---------- Panel header ----------
   The title is the only place in the panel that speaks at a normal reading
   size; everything below it is either a label (uppercase, tracked) or a row.
   That one step up is what stops the panel reading as an undifferentiated
   stack. The hairline underneath closes the header off as its own band. */

.panel-header {
  display: flex;
  /* flex-start, not center: the title block is now two lines, and centring it
     against a 30px button would leave the button floating mid-paragraph */
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--ink-line);
}

.panel-title {
  min-width: 0;
}

.panel-header h2 {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.2;
  text-transform: none;
  color: var(--ink);
}

.panel-subtitle {
  margin-top: 3px;
  font-size: 0.72rem;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}

#close-settings {
  flex: 0 0 auto;
  /* nudged up so the glyph's optical centre lines up with the title's cap
     height rather than with the block that includes the subtitle */
  margin-top: -1px;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  line-height: 1;
  border-radius: 50%;
  color: var(--ink-soft);
  transition: color 0.2s ease, background-color 0.2s ease;
}

#close-settings:hover,
#close-settings:focus-visible {
  color: var(--ink);
  background: var(--ink-wash);
}

#close-settings:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---------- Setting groups ----------
   Zones, not one column. Each group owns a fixed block of padding and the
   hairline lives BETWEEN groups (adjacent-sibling), so the panel never opens
   or closes on a stray rule. Padding rather than margin keeps the hairline
   equidistant from the content on both sides of it — margins would collapse
   and pull the rule off-centre. */

.setting-group {
  margin: 0;
  padding-block: 24px;
}

.setting-group + .setting-group {
  border-top: 1px solid var(--ink-line);
}

.setting-group:last-of-type {
  padding-bottom: 4px;
}

.group-title {
  /* flex so #place-count sits as a real chip beside the label instead of
     inheriting the title's uppercase tracking mid-line */
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ink-soft);
  margin-bottom: 12px;
}

#place-count {
  flex: 0 0 auto;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--ink-wash);
  /* --ink, not --ink-soft: soft ink over the wash composites to 4.49:1 against
     the darkest backdrop, a hair under the 4.5 floor. Full ink at 500 keeps the
     count quiet next to the tracked label while measuring ~9:1. */
  color: var(--ink);
  font-size: 0.65rem;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0.03em;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}

/* no count yet -> no empty pill on first paint */
#place-count:empty {
  display: none;
}

/* ---------- Preference switches ---------- */

.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  cursor: pointer;
  color: var(--ink);
  /* pulled out into the panel's gutter so the hover wash reads as a full-width
     band while the label still aligns with the group title above it */
  padding: 10px;
  margin-inline: -10px;
  border-radius: 10px;
  -webkit-user-select: none;
  user-select: none;
  transition: background-color 0.18s ease;
}

.switch-row:hover {
  background: var(--ink-wash);
}

.switch-row + .switch-row {
  margin-top: 2px;
}

.switch-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.switch-name {
  font-size: 0.88rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink);
}

.switch-desc {
  font-size: 0.7rem;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: var(--ink-soft);
}

.switch {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.switch input {
  /* visually hidden but focusable */
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.switch-track {
  display: block;
  width: 40px;
  height: 22px;
  border-radius: 11px;
  background: var(--ink-track);
  /* The fill alone is only 1.40:1 against the panel — on a bright board the
     switch all but vanished. A hairline gives it an edge (3.49:1) without
     darkening the frosted-light fill. */
  box-shadow: inset 0 0 0 1px var(--ink-faint);
  transition: background-color 0.2s ease;
}

.switch-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #ffffff;
  /* the knob needs its own lift now that the track is light too */
  box-shadow: 0 1px 3px rgba(15, 20, 40, 0.28);
  transition: transform 0.2s ease;
}

.switch input:checked + .switch-track {
  /* darker than --accent so the "on" fill clears 3:1 against both the panel
     behind it and the white knob riding on it */
  background: #4a5cc9;
}

.switch input:checked + .switch-track::after {
  transform: translateX(18px);
}

.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- City search ----------
   The panel's primary action, so it is the one control that carries a filled
   plate: a white lift out of the frosted glass rather than another hairline
   outline. The icon lives inside the field on the left and the input is padded
   to clear it, so the glyph never collides with typed text. */

.search-field {
  position: relative;
  display: flex;
  align-items: center;
}

.search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-faint);
  /* decorative: clicks must fall through to the input underneath */
  pointer-events: none;
  transition: color 0.2s ease;
}

/* :focus-within, not :focus — the icon is a sibling of the input, and the
   state belongs to the field as a whole */
.search-field:focus-within .search-icon {
  color: var(--accent);
}

#city-search {
  width: 100%;
  /* 12 (icon left) + 15 (icon) + 7 (breathing room) = 34 */
  padding: 11px 13px 11px 34px;
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid var(--ink-line);
  border-radius: 11px;
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  /* the faintest inner shadow, so the plate reads as a well rather than a card */
  box-shadow: inset 0 1px 2px rgba(15, 20, 40, 0.05);
  transition: border-color 0.2s ease, box-shadow 0.2s ease,
    background-color 0.2s ease;
}

#city-search::placeholder {
  /* --ink-faint is reserved for glyphs; as 14.4px prose it fell to 3.85:1 */
  color: var(--ink-soft);
}

#city-search:focus {
  background: rgba(255, 255, 255, 0.82);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

#city-search::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* ---------- Search results & location list ----------
   Both lists are pulled 10px into the panel's gutter and every row is padded
   10px back in. Net effect: the row TEXT lines up exactly with the group title
   above it, while the hover wash runs wider than the text — the row reads as a
   band you can hit, not as a box drawn around a label. */

#search-results,
#location-list {
  list-style: none;
  padding: 0;
  margin: 0 -10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

#search-results {
  margin-top: 8px;
}

/* nothing typed yet -> the list contributes no space at all */
#search-results:empty,
#location-list:empty {
  margin-top: 0;
  gap: 0;
}

.location-item,
.search-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  /* 9px against the swatch's 8px: the swatch sits inside the row's corner
     radius, so its own curve has to be a touch tighter to look concentric */
  border-radius: 9px;
  color: inherit;
  text-align: left;
  font-size: 0.88rem;
  transition: background-color 0.18s ease, opacity 0.2s ease;
}

.search-item:hover:not(:disabled),
.search-item:focus-visible,
.location-item:hover {
  background: var(--ink-wash);
}

/* The wash darkens the plate just enough to push secondary text to 4.48:1, so
   the row's own text steps up while it is hovered or a drop target. */
.location-item:hover .loc-country,
.location-item:hover .place-time,
.location-item.drag-over .loc-country,
.location-item.drag-over .place-time {
  color: rgba(23, 32, 58, 0.74);
}

.search-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.search-item:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ---------- The place swatch ----------
   A live chip of that city's sky: JS paints the very gradient the column behind
   the panel is wearing straight onto its background.

   26x26 rather than a 4x26 bar. The gradient is a 180deg two-stop ramp, and a
   4px-wide sliver reads as a coloured rule — a marker, not a sky. A square has
   the area for both stops to register as a graded thing, and it echoes the
   proportion of the columns it stands for.

   The inset hairline is load-bearing, not decoration: a midday gradient is very
   nearly the colour of the frosted panel itself, so without an edge the swatch
   dissolves and the row loses its left anchor. */

.place-swatch {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  box-shadow: inset 0 0 0 1px var(--ink-line),
    0 1px 3px rgba(15, 20, 40, 0.12);
}

/* ---------- Row text ----------
   Two lines, name over subtitle. min-width:0 on the column is what lets the
   children's ellipsis actually fire — a flex item's default min-width:auto
   would let "Nevada, United States" push the whole block wider than the row and
   squeeze the name out instead of truncating. */

.place-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1 1 auto;
  min-width: 0;
}

/* No font-size here on purpose: .loc-name is shared with the in-column city
   picker, which sets its own 0.82rem on .city-edit-item. It inherits from
   whichever parent it lands in. */
.loc-name {
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink);
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.loc-country {
  font-size: 0.7rem;
  line-height: 1.35;
  color: var(--ink-soft);
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The right-hand column. margin-left:auto is belt-and-braces — .place-text
   already grows to eat the free space — but it keeps the time pinned right even
   if a row ever renders without a text block. */
.place-time {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 0.82rem;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: 0.01em;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- Location items (drag to reorder) ---------- */

.location-item {
  border-top: 2px solid transparent; /* reserved for .drag-over hint */
}

.drag-handle {
  flex: 0 0 auto;
  width: 9px;
  color: var(--ink-faint);
  cursor: grab;
  font-size: 0.9rem;
  line-height: 1;
  text-align: center;
  -webkit-user-select: none;
  user-select: none;
  transition: color 0.18s ease;
}

/* Dimmed with opacity this read as 1.86:1 — under the 3:1 floor for a control
   that is the whole affordance for reordering. Full --ink-faint measures
   3.46:1, and the grab cue comes from a colour step on hover instead. */
.location-item:hover .drag-handle {
  color: var(--ink-soft);
}

.location-item:active .drag-handle {
  cursor: grabbing;
}

/* Sort is by offset, so the handle is hidden by JS and there is nothing to grab */
.location-item.sort-locked {
  cursor: default;
}

.remove-btn {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
  border-radius: 7px;
  color: var(--ink-soft);
  transition: color 0.2s ease, background-color 0.2s ease;
}

.remove-btn:hover,
.remove-btn:focus-visible {
  color: var(--danger);
  background: rgba(217, 79, 104, 0.11);
}

.remove-btn:focus-visible {
  outline: 2px solid var(--danger);
  outline-offset: 1px;
}

.location-item.dragging {
  opacity: 0.4;
}

.location-item.drag-over {
  border-top-color: var(--accent);
  background: var(--accent-wash);
}

/* ---------- Hint ---------- */

.hint {
  font-size: 0.72rem;
  line-height: 1.55;
  /* real prose, so it takes the 4.6:1 ink rather than the glyph-only faint */
  color: var(--ink-soft);
  margin-top: 12px;
  letter-spacing: 0.03em;
}

/* ---------- Tip list (time travel) ----------
   Grouped as a soft card. These are five sibling facts about one subject, and
   left loose they read as five stray captions; the wash draws a boundary round
   them so the section has an object in it, matching the weight the other three
   sections get from their controls. */

.tip-list {
  list-style: none;
  margin: 0;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  /* A white plate, NOT --ink-wash. Measured over the darkest gradient, the
     panel composites to rgb(204,205,208) where --ink-soft holds 4.75:1; laying
     --ink-wash under it drops the same text to 4.42:1 and puts five lines of
     prose under the floor. Lifting the card with white instead RAISES it to
     6.0:1, and it puts the card in the same family as #city-search's plate:
     washes are for transient states, white plates for standing surfaces.
     Over a pale midday board the plate all but matches the panel — the inset
     hairline is what keeps its edge, the same trick .place-swatch uses. */
  background: rgba(255, 255, 255, 0.5);
  border-radius: 12px;
  /* inset rather than border, so the card's size is independent of the rule */
  box-shadow: inset 0 0 0 1px var(--ink-line);
}

.tip-list li {
  position: relative;
  padding-left: 16px;
  font-size: 0.72rem;
  line-height: 1.55;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

/* Custom marker: list-style markers cannot be positioned reliably against a
   1.55 line-height, and this one has to sit on the first line's optical centre
   however many lines the item wraps to. */
.tip-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.56em; /* (1.55/2) - (5px / 0.72rem / 2), measured against the first line */
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.55;
}

kbd {
  font-family: inherit;
  font-size: 0.65rem;
  line-height: 1.4;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(23, 32, 58, 0.08);
  border: 1px solid var(--ink-line);
  color: var(--ink-soft);
  text-transform: none;
  white-space: nowrap;
}

/* ↑↓ are authored with no whitespace between them, so they would otherwise
   touch borders and read as one wide key */
kbd + kbd {
  margin-left: 3px;
}

/* ---------- Responsive: narrow or portrait -> stacked rows ---------- */

@media (max-width: 720px), (orientation: portrait) {
  #board {
    flex-direction: column;
  }

  /* Rows fill the screen top to bottom, so there is no centred block to hang
     off — re-pin the controls to the top and undo the centre-anchored offset.
     Only the wrapper needs restating: the pill's own transforms are relative
     (0 / -1px lift / -8px exit) and read correctly under either anchor. */
  #board-controls {
    top: 12px;
    transform: translateX(-50%) translateY(0);
  }

  /* Stacked rows start at the very top, so a top-centred pill would land on the
     first row's name. Only while the pill is actually showing, shrink the board
     to leave it a lane — box-sizing keeps the total at 100dvh, so the rows just
     give up a few pixels each. */
  /* Unconditional: the + is ALWAYS in this lane, unlike the pill. Reserving the
     space only while travelling left the + sitting on the first row's name. */
  #board {
    padding-top: 54px;
  }

  .clock-column {
    width: 100%;
    min-height: 0;
  }

  .clock-inner {
    gap: 0.2rem;
    padding: 0.5rem 1rem;
  }

  .clock-time {
    margin-block: 0.15rem;
  }

  .time-main,
  .time-edit {
    font-size: clamp(1.6rem, 8vh, 3.2rem);
  }

  .time-meridiem {
    font-size: clamp(0.6rem, 2.8vh, 1.1rem);
  }

  /* Rows are short, so the controls tighten up rather than moving: they are in
     the flow now, and every extra pixel here comes off the clock. Being centred
     in the row also keeps them clear of the fixed gear at bottom-right, which
     the old right-pinned placement used to collide with. */
  .col-actions {
    gap: 8px;
    margin-top: 0.45rem;
  }

  .col-action {
    width: 22px;
    height: 22px;
  }

  .col-action svg {
    width: 12px;
    height: 12px;
  }

  /* the dropdown escapes the row (see :has() rule above) — keep the spill short */
  .city-edit-results {
    max-height: 160px;
  }

  /* On the last row there is no room below, so the popover opened off the bottom
     of the screen with no way to scroll to it. Flip it above the field; the
     :has() unclip plus the column's z-index let it cover the rows above. */
  .clock-column:last-child .city-edit-results {
    top: auto;
    bottom: 100%;
    margin-top: 0;
    margin-bottom: 6px;
  }

  /* Near full-width, and higher up the screen. The height is the real
     constraint: the input is focused for the whole life of the palette, so on a
     phone the on-screen keyboard is ALWAYS up while it is open, covering
     roughly the bottom 45%. Neither vh nor dvh shrinks for it (Chrome's default
     interactive-widget resizes only the visual viewport), so the panel has to
     be sized to clear it by hand: 8vh + 48dvh puts the last row at ~56% of the
     screen, above the keyboard, and the list scrolls inside that. */
  #quick-add-panel {
    top: 8vh;
    width: 94vw;
    max-height: 48dvh;
    border-radius: 13px;
  }

  #quick-add-input {
    /* 16px+ or iOS zooms the page on focus, which would break the fixed layout */
    font-size: 1rem;
    padding: 14px 16px 14px 42px;
  }

  .quick-add-icon {
    left: 16px;
  }

  .quick-add-foot {
    padding: 8px 12px;
    gap: 5px 12px;
  }

  /* Columns are stacked, so scrubbing is a vertical gesture here */
  body.scrubbing,
  body.scrubbing .clock-column,
  body.scrubbing .clock-inner,
  body.scrubbing .clock-time,
  body.scrubbing .time-edit {
    cursor: ns-resize;
  }
}

/* ---------- Very narrow: trim the palette hints ----------
   Three hints will wrap to a second line long before this, which is fine, but
   below ~420px the footer starts eating the results list. Drop the arrow-keys
   hint first: it is the one that means least on a screen this size, where the
   list is being driven by taps. Adding and closing stay documented. */

@media (max-width: 420px) {
  .quick-add-foot > span:first-child {
    display: none;
  }
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }

  /* No slide on the Now pill and no width animation as it collapses out of
     #board-controls — it appears and disappears in place, and the + jumps
     between its two positions rather than sliding.

     `transform: none` is safe here now, and is the correct fix: the centring
     lives on #board-controls, which never animates, so this drops only the
     pill's own lift and exit slide. (Restating the centring offset on the pill
     was also what used to fling it off-screen in reduced motion + portrait,
     where the row layout re-pins the anchor to the top.) */
  #now-btn,
  #now-btn:hover,
  #now-btn:focus-visible,
  #now-btn.hidden {
    transform: none;
    transition: opacity 0.01ms linear, visibility 0s linear;
  }

  #quick-add-btn,
  #quick-add-btn:hover,
  #quick-add-btn:focus-visible {
    transform: none;
    transition: none;
  }

  /* No easing between gradients */
  .clock-column,
  .time-edit {
    transition: none;
  }

  /* Hover controls / picker: appear and disappear flat — no fade, no zoom.
     The scale must be dropped outright, not just un-transitioned. */
  .col-actions,
  .city-edit,
  .city-edit-item {
    transition: none;
  }

  .col-action,
  .col-action:hover,
  .col-action:focus-visible {
    transform: none;
    transition: none;
  }

  .col-enter {
    animation: none;
  }

  /* Panel sections land in place. Same specificity as the rule that arms the
     animation and later in the sheet, so it wins outright rather than relying
     on the blanket 0.01ms duration above — the transform and the opacity have
     to be dropped, not merely hurried. */
  #settings-panel:not(.hidden) .setting-group {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* The palette arrives fully formed: no rise, no fade, on the panel or the
     scrim. Same specificity as the rules that arm the entrance and later in the
     sheet, so this wins outright rather than leaning on the blanket 0.01ms —
     the transform has to be dropped, not merely hurried. The panel keeps its
     static translateX(-50%) from the base rule, so it stays centred. */
  #quick-add:not(.hidden) #quick-add-panel,
  #quick-add:not(.hidden) #quick-add-scrim {
    animation: none;
  }

  .switch-row,
  .location-item,
  .search-item,
  .drag-handle,
  .search-icon,
  #city-search,
  .quick-add-field,
  .quick-add-icon,
  .quick-add-item {
    transition: none;
  }
}
