/* HiFinder design system v8: "The Clearing".
   A forest at rest rather than a void: the background is a real dark green,
   not near-black, and every surface above it is a lighter step of the same
   hue instead of a different one. Soft glow instead of hard shadows, rounded
   shapes, sentence case. Archivo Black for headings, Inter for everything you
   read, JetBrains Mono for small meta labels. Everything self-hosted.

   Colour is still split three ways so no single hue dominates, but the
   identity and action roles are now close kin, both green, since that is the
   whole vibe: this is a place, not a control panel.
     sage   the app's resting identity (ambient marks, borders, glow)
     amber  anything you can act on (buttons, links, active states). The name
            is legacy from v7's warm accent; the token stayed so the rest of
            this file did not have to be rewritten, only its value did.
     lilac  small punctuation (chips, badges, secondary meta)
   Every text colour below was re-checked against WCAG AA on the new,
   lighter background and against --panel, not carried over by eye. */

@font-face {
  font-family: "Archivo Black";
  src: url("../fonts/archivo-black.4b6914a69e6a.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  /* Variable file: real weights from 100 to 900, no synthetic bolding. */
  font-family: "Inter";
  src: url("../fonts/inter.260c81a4759b.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("../fonts/jetbrains-mono.b058178d7f30.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Dark-only design, and nothing here ever declared it: native chrome this
     stylesheet cannot reach directly (a <select>'s own open dropdown list,
     scrollbars) was rendering with the browser's light-theme default, a
     jarring white-and-blue popup on top of an otherwise all-dark page. */
  color-scheme: dark;
  --ink: #070a08;
  --background: #1a3c2a;
  --panel: rgba(46, 95, 64, 0.72);
  --panel-2: rgba(60, 114, 80, 0.75);
  --bone: #e9ede9;

  /* Identity. Light enough to read as the brand colour on the new, lighter
     background, verified at 8.2:1 there and 5:1 on --panel. */
  --sage: #b7dfb2;
  --sage-soft: rgba(183, 223, 178, 0.12);
  /* Action. A shade brighter and slightly warmer than sage, so clickable
     things still separate from ambient ones, just by less than v7's amber
     did. 7.7:1 on background, 4.7:1 on panel. */
  --amber: #93e08a;
  --amber-soft: rgba(147, 224, 138, 0.12);
  /* Punctuation. Kept a distinct hue on purpose: an all-green palette still
     needs one colour that is obviously not terrain or action. */
  --lilac: #cbc2ee;

  /* --ember is the old name for the identity colour. Kept as an alias so the
     whole stylesheet did not have to be rewritten in one go. */
  --ember: var(--sage);
  --border: rgba(90, 138, 92, 0.15);
  --border-strong: rgba(90, 138, 92, 0.35);
  --muted: #a8d5a6;
  --danger: #f0876a;
  --info: #7ab8d7;
  --gold: #d9b877;
  /* #2a5138 nearly matches --panel's own rgb(46, 95, 64): an empty star sat
     almost exactly the colour of the card behind it, so a 3-star review
     could read as a full 5-star one at a glance. A translucent sage keeps
     it visibly dimmer than --gold without picking a colour close enough to
     any one background to disappear into it again. */
  --star-dim: rgba(183, 223, 178, 0.28);
  --display: "Archivo Black", "Segoe UI", system-ui, sans-serif;
  --body-font: "Inter", "Segoe UI", system-ui, sans-serif;
  --mono: "JetBrains Mono", Consolas, monospace;
  --radius: 14px;
  --radius-lg: 20px;
  --radius-pill: 999px;
  /* Depth comes from soft shadow plus a faint glow, never hard offsets. */
  --shadow-brutal: 0 6px 24px rgba(0, 0, 0, 0.45);
  --shadow-brutal-lg: 0 14px 44px rgba(0, 0, 0, 0.55);
  --shadow-ember: 0 0 0 1px rgba(147, 224, 138, 0.32), 0 8px 28px rgba(147, 224, 138, 0.16);
  --glow: 0 0 24px rgba(183, 223, 178, 0.22);

  /* Motion. Built-in CSS easings are too weak to read as deliberate, and the
     bare "ease" keyword ramps in, which delays the exact moment the eye is on
     the element. These are the curves the animation audit prescribes. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 260ms;
  /* One knob for every press. Reduced motion sets it to 1, which switches off
     press feedback everywhere without a rule per element. */
  --press: 0.97;
}

* { box-sizing: border-box; }

/* The hidden attribute must always win, even over elements whose class sets a
   display value (e.g. the route bar). Otherwise those show as empty boxes. */
[hidden] { display: none !important; }

html { scrollbar-color: var(--ember) var(--ink); }

body {
  margin: 0;
  background: var(--background);
  color: var(--bone);
  font-family: var(--body-font);
  font-size: 15.75px;
  line-height: 1.6;
  letter-spacing: -0.006em;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
}

/* The haze: three coloured clouds that drift and breathe behind everything.
   Slow enough (40s) to read as atmosphere rather than animation. This is the
   only "trippy" element; the interface on top of it stays plain. */
body::before {
  content: "";
  position: fixed;
  inset: -25%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(38% 44% at 22% 18%, rgba(183, 223, 178, 0.13), transparent 70%),
    radial-gradient(34% 40% at 80% 26%, rgba(203, 194, 238, 0.15), transparent 70%),
    radial-gradient(44% 46% at 55% 88%, rgba(147, 224, 138, 0.09), transparent 70%);
  filter: blur(16px);
  animation: haze-drift 40s var(--ease-in-out) infinite alternate;
}
@keyframes haze-drift {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1); }
  50%  { transform: translate3d(3%, 2%, 0) scale(1.08); }
  100% { transform: translate3d(-1%, 3%, 0) scale(1.03); }
}

/* A whisper of grain so the gradients do not band on cheap panels. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
  opacity: 0.025;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

::selection { background: var(--ember); color: var(--ink); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--ember); }
::-webkit-scrollbar-track { background: var(--ink); }

a { color: var(--amber); text-decoration: none; font-weight: 600; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  color: var(--bone);
  line-height: 1.12;
  letter-spacing: -0.025em;
}
/* Fluid type: scales smoothly with the viewport instead of jumping at
   breakpoints, so there is no awkward size between phone and desktop. */
h1 { font-size: clamp(1.42rem, 1.08rem + 1.6vw, 2.05rem); margin: 0.1em 0 0.4em; }
h2 { font-size: clamp(1.02rem, 0.94rem + 0.45vw, 1.24rem); margin: 1.7em 0 0.6em; }
h3 { font-size: clamp(0.92rem, 0.89rem + 0.16vw, 1.02rem); margin: 0 0 0.4em; }
.kicker { color: var(--muted); margin-top: -0.15rem; }
.dot-accent { color: var(--ember); }

.mono {
  font-family: var(--mono);
  letter-spacing: 0.02em;
}

.skip {
  position: absolute;
  left: -999px;
  top: 8px;
  z-index: 1001;
  background: var(--ember);
  color: var(--ink);
  padding: 0.5rem 1rem;
  font-family: var(--mono);
}
.skip:focus { left: 8px; }

.visually-hidden {
  /* !important throughout, not just on position: a plain input[type="file"]
     selector is more specific than this one class (attribute selectors
     outrank a lone class), so it was winning the width and height here and
     an outwardly 1x1px file input was actually rendering at its normal
     full size, wide enough to push a cramped mobile row past the viewport. */
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --- Styled file input (core.widgets.StyledFileInput) --- */
.file-picker { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; }
.file-picker-btn { display: inline-flex; align-items: center; gap: 0.5rem; }
.file-picker-name {
  color: var(--muted);
  font-size: 0.82rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
}
.file-picker-current {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  flex-basis: 100%;
  font-size: 0.82rem;
  color: var(--muted);
  margin-bottom: 0.2rem;
}
.file-picker-current a { color: var(--bone); }
.file-picker-clear { display: inline-flex; align-items: center; gap: 0.3rem; cursor: pointer; }

/* --- Navigation --- */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.6rem 1.3rem;
  background: rgba(10, 18, 15, 0.72);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-bottom: 1px solid var(--border);
}
.brand { display: inline-flex; align-items: center; gap: 0.65rem; color: var(--bone); }
.brand:hover { text-decoration: none; }
.logo-sq {
  width: 36px;
  height: 36px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(145deg, var(--sage), #6ba07d);
  color: var(--ink);
  border: none;
  border-radius: 12px;
  box-shadow: var(--glow);
}
.brand-block { display: flex; flex-direction: column; line-height: 1.2; }
.brand-block > span:first-child {
  font-family: var(--display);
  font-weight: 400;
  font-size: 0.98rem;
  letter-spacing: -0.015em;
}
.brand-block .slash { color: var(--ember); }
.brand-sub {
  font-size: 0.68rem;
  color: var(--muted);
}

.nav-links { display: flex; align-items: center; gap: 0.15rem; flex-wrap: wrap; }
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--muted);
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  padding: 0.45rem 0.85rem;
  transition: background 160ms ease, color 160ms ease;
}
.nav-link svg { width: 16px; height: 16px; }
.nav-link:hover {
  background: var(--amber-soft);
  color: var(--bone);
  text-decoration: none;
}
/* The current section is marked with colour, not a filled slab. */
.nav-link.active {
  background: var(--amber-soft);
  color: var(--amber);
  box-shadow: inset 0 0 0 1px rgba(147, 224, 138,0.28);
}

/* The <details> wrapping .nav-links. Transparent on desktop: the summary
   (the burger) is hidden and .nav-links lays out exactly as it always has.
   Only the max-width: 640px block below turns this into an actual drawer. */
.nav-drawer { position: relative; }
.nav-burger {
  display: none;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  color: var(--bone);
  cursor: pointer;
  list-style: none;
}
.nav-burger::-webkit-details-marker { display: none; }
.nav-burger::marker { content: ""; }
.nav-burger:hover { background: var(--amber-soft); }
.nav-drawer[open] .nav-burger { background: var(--amber-soft); color: var(--amber); }
.nav-burger-icon { width: 20px; height: 20px; }

.inline-form { display: inline; }
.linklike {
  display: inline-flex;
  align-items: center;
  gap: 0.42rem;
  background: none;
  border: 1px solid transparent;
  padding: 0.45rem 0.7rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
}
.linklike svg { width: 14px; height: 14px; }
.linklike:hover { color: var(--amber); }

/* --- Layout --- */
.container { max-width: 920px; margin: 0 auto; padding: clamp(1rem, 3vw, 1.6rem) clamp(0.9rem, 3vw, 1.3rem) 2.4rem; }
/* Crew/Friends/Messages have no narrower shell of their own the way
   .notif-page or .vote-shell deliberately do, so at 920px they read as a
   small island in a lot of dead space on a wide monitor. Widening just this
   container, rather than every page that shares the class, keeps every
   other .container consumer (forms, settings, terms) at the width they were
   actually designed for. */
.container:has(> .crew-page) { max-width: 1100px; }
.footer {
  max-width: 920px;
  margin: 2.5rem auto 0;
  padding: 1.2rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.02em;
  border-top: 1px solid var(--border);
  text-align: center;
}
.footer-copyright { margin-top: 0.4rem; opacity: 0.7; }
.page-head {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
}
.back-link {
  display: inline-block;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  margin-bottom: 0.5rem;
}
.back-link:hover { color: var(--amber); text-decoration: none; }

/* --- Cards --- */
/* Cards are frosted glass over the haze, so the drift shows faintly through. */
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.3rem 1.5rem;
  margin: 1rem 0;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.card-link {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  color: var(--bone);
  font-weight: 400;
  transition: transform 100ms var(--ease-out), box-shadow 100ms var(--ease-out), border-color 100ms var(--ease-out);
}
.card-link:hover {
  text-decoration: none;
  transform: translateY(-2px);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-ember);
}
.card-link strong { font-size: 0.92rem; font-weight: 700; }
/* auto-fit, not auto-fill: with a short list, auto-fill still reserves empty
   tracks for cards that do not exist, so the real cards are shoved into the
   left part of the row and the rest sits dead. auto-fit collapses those empty
   tracks and lets the real cards stretch to fill the row instead. */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.9rem;
}
.card-grid .card { margin: 0; }

.muted { color: var(--muted); font-size: 0.9rem; }

/* --- Chips & badges --- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  padding: 0.14rem 0.55rem;
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  background: var(--panel-2);
  color: var(--bone);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}
/* currentColor, not a token: it picks up whatever .chip-<category> below has
   already set on this exact chip, so one rule works for all eight without
   naming any of them. Was missing entirely; every category dot on a chip
   rendered as a zero-size, invisible span until now. */
.chip .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
/* One value per category, copied from CATEGORY_COLORS in globe.js. These
   drifted apart once already: migration 0010 renamed four categories, this
   file was not updated, and half the app's categories rendered with no colour
   while the other half disagreed with their own map pin. */
.chip-beach    { color: #5aaecb; border-color: #5aaecb66; }
.chip-forest   { color: #4fa982; border-color: #4fa98266; }
.chip-park     { color: #7cbf7c; border-color: #7cbf7c66; }
.chip-mountain { color: #9aa7b8; border-color: #9aa7b866; }
.chip-city     { color: #d98a5a; border-color: #d98a5a66; }
.chip-lake     { color: #6f9fd8; border-color: #6f9fd866; }
.chip-field    { color: #c9b45e; border-color: #c9b45e66; }
.chip-camp     { color: #c9866a; border-color: #c9866a66; }
.chip-private { color: var(--danger); border-color: rgba(255, 93, 58, 0.45); }

.owner-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0.6rem 0; }
.owner-actions form { margin: 0; }

/* Sub-tabs inside the Crew section (Find people / Friends / Messages): a
   segmented pill, not underlined text, so it reads as one control with a
   position rather than three separate links. */
.subtabs {
  display: inline-flex;
  padding: 4px;
  gap: 4px;
  border-radius: var(--radius-pill);
  background: rgba(7, 10, 8, 0.42);
  border: 1px solid var(--border);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  margin-bottom: 1.2rem;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.subtabs::-webkit-scrollbar { display: none; }
.subtab {
  padding: 0.5rem 0.95rem;
  color: var(--muted);
  font-family: var(--body-font);
  font-weight: 600;
  font-size: 0.72rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  transition: color 140ms ease;
}
.subtab:hover { color: var(--bone); text-decoration: none; }
.subtab.active { background: var(--amber); color: var(--ink); font-weight: 700; }
.crew-tab-count {
  display: inline-flex;
  margin-left: 0.45rem;
  color: var(--lilac);
  font-weight: 700;
}
.subtab.active .crew-tab-count { color: var(--ink); }
.chip-match   { background: var(--lilac); color: var(--ink); border-color: var(--border-strong); }
.chip-coords  { font-variant-numeric: tabular-nums; }

.count-badge {
  display: inline-block;
  min-width: 1.6em;
  text-align: center;
  padding: 0.05rem 0.45rem;
  background: var(--lilac);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 700;
  border-radius: var(--radius-pill);
}

/* --- Stars --- */
.stars {
  position: relative;
  display: inline-block;
  color: var(--star-dim);
  font-size: 1rem;
  line-height: 1;
  letter-spacing: 2px;
  /* The gold overlay (.stars-fill) is absolutely positioned and always
     nowrap, but without this the plain five-star text behind it (the one
     that actually gives .stars its width) could wrap inside a cramped flex
     row, so two reviews with the identical rating rendered at different
     widths depending on what else was in their row. */
  white-space: nowrap;
}
.stars-fill {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  overflow: hidden;
  color: var(--gold);
}

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  /* Used to fade to #d1863a, a warm brown left over from v7's gold amber.
     v8 repointed --amber to green ("a legacy name, not a legacy colour" per
     design.md) and this gradient never followed, so every primary button in
     the app quietly introduced a fourth hue the palette does not have. A
     single colour, with :hover's existing brightness() for depth, is what
     "one accent at a time" actually looks like. */
  background: var(--amber);
  color: #06180d;
  border: none;
  border-radius: var(--radius-pill);
  padding: 0.6rem 1.3rem;
  font-family: var(--body-font);
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 18px rgba(147, 224, 138,0.22);
  transition: transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out), filter 180ms var(--ease-out);
  /* Without this a touch browser waits to see if a second tap is coming
     (the double-tap-to-zoom gesture) before it commits to the first one,
     so every button in the app replied to a tap a couple hundred
     milliseconds late on exactly the devices this project cares most about. */
  touch-action: manipulation;
}
.btn svg { width: 16px; height: 16px; }
.btn:hover {
  text-decoration: none;
  transform: translateY(-1px);
  box-shadow: 0 6px 26px rgba(147, 224, 138,0.32);
  filter: brightness(1.04);
}
.btn:active { transform: translateY(0); box-shadow: 0 2px 10px rgba(147, 224, 138,0.2); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }
.btn-small { padding: 0.4rem 0.85rem; font-size: 0.66rem; }
.btn-big { padding: 0.85rem 1.7rem; font-size: 0.8rem; }
.btn-wide { width: 100%; }
.btn-outline {
  background: var(--panel);
  /* Full shorthand, not border-color alone: .btn sets border: none, and a
     colour on a border-style: none box draws nothing. */
  border: 1px solid var(--border);
  color: var(--bone);
  box-shadow: var(--shadow-brutal);
}
.btn-outline:hover { border: 1px solid var(--border-strong); background: var(--panel-2); box-shadow: var(--shadow-ember); }

/* --- Social login --- */
.social-row { display: flex; flex-direction: column; gap: 0.7rem; margin: 0.9rem 0; }
.social-row form { margin: 0; }
.btn-social { width: 100%; background: var(--bone); color: var(--ink); border: 1px solid var(--border-strong); }
.btn-social:hover { box-shadow: var(--shadow-ember); }
.divider {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.02em;
  margin: 1.2rem 0;
}
.divider::before, .divider::after { content: ""; flex: 1; height: 2px; background: var(--border); }

/* --- Forms --- */
/* Without margin: auto this sat flush against .container's left edge on any
   screen wider than 520px, since a block element does not center itself. */
.form-card { max-width: 520px; margin: 0 auto; }
.form-card-centered { text-align: center; }
.form-card-footnote { margin-top: 0.6rem; }
/* The 500 page is a standalone document with no nav bar (the handler runs
   without context processors, so it cannot extend base.html), so its card
   needs its own top offset rather than the one the nav normally provides. */
.form-card-standalone { margin-top: 3rem; }
form p { margin: 0.9rem 0; }
/* RadioSelect renders as <div><div><input><label>...</div>...</div>, same
   shape as .vibe-chips above. */
.feedback-category { border: none; padding: 0; margin: 0 0 0.8rem; }
.feedback-category > div { display: flex; gap: 0.5rem; }
.feedback-category > div > div { flex: 1; position: relative; }
.feedback-category label {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  color: var(--bone);
  font-family: var(--body-font);
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: normal;
  cursor: pointer;
}
.feedback-category input[type="radio"] { position: absolute; top: 0; left: 0; opacity: 0; width: 1px; height: 1px; }
.feedback-category input:checked + label,
.feedback-category label:has(input:checked) {
  border-color: rgba(147, 224, 138, 0.5);
  background: var(--amber-soft);
  color: var(--amber);
}
.feedback-category input:focus-visible + label { outline: 2px solid var(--amber); outline-offset: 2px; }
form label {
  display: block;
  margin-bottom: 0.3rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
input[type="text"], input[type="password"], input[type="email"], input[type="search"],
input[type="number"], input[type="file"], input[type="url"], textarea, select {
  width: 100%;
  background: rgba(7, 13, 11, 0.6);
  color: var(--bone);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.68rem 0.85rem;
  font: inherit;
  transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
input::placeholder, textarea::placeholder {
  color: #6b7d70;
  font-size: 0.92rem;
}
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(147, 224, 138,0.18);
}
.helptext { display: block; color: var(--muted); font-size: 0.8rem; margin-top: 0.3rem; }
.errorlist {
  color: var(--danger);
  list-style: none;
  padding: 0.45rem 0.7rem;
  margin: 0.3rem 0;
  font-size: 0.88rem;
  background: rgba(255, 93, 58, 0.08);
  border-left: 4px solid var(--danger);
}
form > ul:not(.errorlist), form li { color: var(--muted); font-size: 0.84rem; }

/* --- Flash messages --- */
/* Fixed, not relative: the map and the Lounge are position: fixed full-bleed
   layouts that start right under the nav regardless of what else is on the
   page, so a message sitting in normal flow lands in the exact same screen
   region as their own floating buttons. pointer-events: none is safe because
   a message is status text with no link or button inside it, so nothing is
   ever lost by letting a click fall through to whatever is actually under it
   rather than being swallowed by an invisible banner someone already read. */
.messages {
  position: fixed;
  top: var(--nav-height, 63px);
  left: 0;
  right: 0;
  max-width: 920px;
  margin: 0.7rem auto 0;
  padding: 0 1.3rem;
  z-index: 60;
  pointer-events: none;
}
.message {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.65rem 0.95rem;
  margin: 0.45rem 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 6px solid var(--border);
  font-size: 0.93rem;
  /* Slides in, holds long enough to actually read, slides back out on its
     own. animation-fill-mode: forwards leaves it at opacity: 0 afterward
     rather than snapping back to visible; harmless to leave in the DOM at
     that point since .messages is already pointer-events: none and fixed,
     so an invisible leftover node affects nothing. Reduced motion turns
     animation off globally (see the media query below), which means this
     one stops disappearing on its own too, not just stops sliding: that is
     the right trade for anyone who finds timed, moving UI hard to track,
     not a bug to route around.  */
  animation: toast-lifecycle 6s ease forwards;
  border-radius: var(--radius);
}
.message::before { font-weight: 700; font-family: var(--mono); }
.message.success { border-left-color: var(--ember); }
.message.success::before { content: "✓"; color: var(--ember); }
.message.info { border-left-color: var(--info); }
.message.info::before { content: "i"; color: var(--info); }
.message.error { border-left-color: var(--danger); }
.message.error::before { content: "!"; color: var(--danger); }

/* A message that belongs to the page rather than to the last click. Same
   left-rule idiom as .message so the two read as one family, but it does not
   slide in and it does not go away, because the state it describes does not
   either. Used for a spot waiting on a moderator, and for a private spot. */
.notice {
  padding: 0.7rem 0.95rem;
  /* Both sides: whatever follows (the vote-of-the-month strip, the
     composer) has no top margin of its own to fall back on, so a
     bottom-only or top-only margin here left them touching directly. */
  margin: 0.8rem 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 6px solid var(--info);
  border-radius: var(--radius);
  font-size: 0.92rem;
  color: var(--bone);
  line-height: 1.5;
}

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

/* Same entrance as slide-in above, held, then the same move in reverse.
   One animation instead of an entrance animation plus a separate JS timer
   plus a separate exit animation, because nothing here needs to run after
   the element appears: the whole lifecycle is known up front. */
@keyframes toast-lifecycle {
  0%   { opacity: 0; transform: translateY(-6px); }
  5%   { opacity: 1; transform: translateY(0); }
  90%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-6px); }
}

/* --- Photos --- */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 0.7rem;
}
.photo-grid a { display: block; overflow: hidden; border: 1px solid var(--border); }
.photo-grid img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  transition: transform 200ms var(--ease-out);
}
.photo-grid a:hover img { transform: scale(1.04); }

/* --- Age gate --- */
.gate { max-width: 560px; margin: 7dvh auto; text-align: center; padding: 2.6rem 2.2rem; box-shadow: var(--shadow-brutal-lg); }
.gate svg { color: var(--ember); }
.gate h1 { font-size: clamp(1.7rem, 1.2rem + 2.4vw, 2.4rem); }
.gate form { margin: 1.4rem 0 0.9rem; }

/* --- Avatars, profile, people --- */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  background: var(--panel-2);
  border: 1px solid var(--accent, var(--border)); /* per-member accent ring */
  color: var(--accent, var(--sage));
  font-weight: 800;
  font-size: 0.95rem;
  flex: none;
  object-fit: cover;
  overflow: hidden;
  border-radius: var(--radius-pill);
}
img.avatar { border-color: var(--accent, var(--border)); }
.avatar-big { width: 4.4rem; height: 4.4rem; font-size: 1.7rem; }
.avatar-deck { width: 6rem; height: 6rem; font-size: 2.4rem; }
.avatar-chat { width: 26px; height: 26px; font-size: 0.7rem; }
.avatar-first { width: 64px; height: 64px; font-size: 1.5rem; }

.profile-head { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.profile-head h1 { margin: 0; font-size: clamp(1.35rem, 1.1rem + 1.2vw, 1.75rem); }

/* Customizable profile hero: banner + accent (per-member --accent) */
.profile-hero {
  --accent: var(--ember);
  background: var(--panel);
  border: 1px solid var(--border);
  margin: 0.9rem 0;
  border-radius: var(--radius-lg);
}
.profile-banner {
  height: 150px;
  background:
    repeating-linear-gradient(0deg, rgba(0,0,0,0.18) 0 1px, transparent 1px 4px),
    linear-gradient(135deg, color-mix(in srgb, var(--accent) 38%, #0b100e), #0b100e 78%);
  background-size: cover;
  background-position: center;
  border-bottom: 1px solid var(--border);
  /* Matches .profile-hero's own radius on the two corners it actually sits
     in. Not overflow:hidden on the hero itself, which would also clip
     .profile-more-menu below: that dropdown is positioned absolute and
     often extends past the hero's own bottom edge on purpose. */
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.profile-id {
  /* Known stacking hazard: .profile-banner carries the banner image and this
     row overlaps it with a negative margin. Without its own position and a
     z-index above the banner's stacking context, the avatar renders under
     the banner and clips into a flat-topped half circle. */
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  gap: 1rem;
  padding: 0 1.3rem;
  margin-top: -44px;
  flex-wrap: wrap;
}
.avatar-hero {
  width: 96px;
  height: 96px;
  font-size: 2.2rem;
  border: 3px solid var(--accent);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
  background: var(--panel-2);
}
.profile-actions { display: flex; align-items: center; gap: 0.6rem; margin-left: auto; padding-bottom: 4px; flex-wrap: wrap; }
.profile-actions .btn, .profile-actions form { white-space: nowrap; flex: none; }
.profile-edit-btn {
  background: color-mix(in oklch, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  color: var(--accent);
}
.profile-edit-btn:hover { background: var(--accent); color: var(--ink); }
.profile-pending {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--sage);
  border: 1px dashed var(--sage);
  border-radius: var(--radius-pill);
  padding: 0.5rem 0.9rem;
}
.profile-more { position: relative; }
.profile-more-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--panel-2);
  color: var(--bone);
  cursor: pointer;
  font-size: 1.1rem;
}
.profile-more-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 10;
  background: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-brutal);
  min-width: 170px;
  padding: 0.3rem;
  display: flex;
  flex-direction: column;
}
.profile-more-item {
  background: none;
  border: none;
  text-align: left;
  padding: 0.6rem 0.7rem;
  min-height: 44px;
  color: var(--bone);
  font-size: 0.82rem;
  cursor: pointer;
  border-radius: var(--radius);
}
.profile-more-item:hover { background: var(--panel-2); }
.profile-substrip { padding: 0 1.3rem; margin: 0.4rem 0 0; font-size: 0.78rem; }
.profile-blocked-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  margin: 0.7rem 1.3rem 0;
  padding: 0.7rem 0.9rem;
  background: rgba(240, 135, 106, 0.1);
  border: 1px solid rgba(240, 135, 106, 0.35);
  border-radius: var(--radius);
  font-size: 0.82rem;
  flex-wrap: wrap;
}
.profile-safety-line { padding: 0 1.3rem; margin: 0.6rem 0 0; font-size: 0.8rem; }
.profile-safety-line .linklike { font-size: inherit; }

.profile-stats-row {
  display: flex;
  align-items: baseline;
  gap: 1.1rem;
  flex-wrap: wrap;
  padding: 0.9rem 1.3rem;
  margin-top: 0.6rem;
  border-top: 1px solid var(--border);
}
.profile-stat {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  color: inherit;
  text-decoration: none;
}
.profile-stat strong { font-family: var(--display); font-size: 1.15rem; color: var(--accent); }
.profile-stat .mono { color: var(--muted); font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.03em; }
a.profile-stat:hover strong { text-decoration: underline; }
.profile-meta-line { display: flex; gap: 0.9rem; margin-left: auto; flex-wrap: wrap; }
.profile-meta-line .mono { color: var(--muted); font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; }
.profile-vibe { color: var(--accent); }
.profile-bio { padding: 0 1.3rem 1.2rem; max-width: 62ch; font-size: 0.86rem; line-height: 1.6; }

/* --- Edit panel --- */
.profile-edit-panel {
  border-radius: var(--radius-lg);
  border: 1px solid color-mix(in oklch, var(--accent) 35%, transparent);
  box-shadow: var(--shadow-brutal-lg);
  overflow: hidden;
}
.profile-edit-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 1rem 1.3rem;
  background: rgba(7, 10, 8, 0.35);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.profile-edit-head h2 { margin: 0; flex: 1; min-width: 0; }
.profile-edit-head-actions { display: flex; gap: 0.6rem; flex: none; }
@media (max-width: 560px) {
  /* h2's flex-basis: 0 (from flex: 1) meant it contributed ~0 width to the
     wrap decision, so the row itself never wrapped: the heading's own text
     wrapped instead, inside whatever sliver of space was left after Cancel
     and Save profile claimed theirs, and align-items: center then centred
     those buttons vertically across that wrapped height, on top of the
     middle line of the title. Stacking the row removes the competition for
     width entirely instead of trying to win it. */
  .profile-edit-head { flex-direction: column; align-items: flex-start; }
  .profile-edit-head-actions { width: 100%; }
  .profile-edit-head-actions .btn { flex: 1; }
}
.profile-edit-panel form { padding: 0 1.3rem; }
.avatar-edit-row .avatar-edit-inner { display: flex; align-items: center; gap: 0.9rem; }
.avatar-edit-preview {
  width: 54px;
  height: 54px;
  border: 2px solid var(--accent);
  background-size: cover;
  background-position: center;
  flex: none;
}
/* RadioSelect renders as <div id="id_vibe"><div><input><label>...</div>...
   </div>, not a flat list of labels, so the flex rules that matter go on
   that inner wrapper (.vibe-chips > div), not on .vibe-chips itself: this
   div was the one actually stacking one choice per line, block being a
   div's default display, while .vibe-chips's own flex had nothing to lay
   out beyond that single child. One scrollable row, not a wrapping grid:
   the field column this sits in is narrow enough that wrapping would have
   put one chip per line too. */
.vibe-chips > div { display: flex; flex-wrap: nowrap; gap: 0.5rem; overflow-x: auto; padding-bottom: 2px; }
.vibe-chips > div > div { flex: none; }
/* A real fieldset/legend now (a radio group with no programmatic group
   label at all before), not just a fieldset's default browser chrome. */
.vibe-fieldset { border: none; padding: 0; }
/* top/left pin this to the container's corner rather than leaving it at
   the browser's default "static position", the same fix as .star-input's
   radios below and for the same reason: an unpinned hidden input can end up
   positioned somewhere no longer inside the visible row once its siblings
   wrap, and focusing it then scrolls an overflow: hidden ancestor sideways
   to reveal it, dragging real content out of view with it. */
.vibe-chips input[type="radio"] { position: absolute; top: 0; left: 0; opacity: 0; width: 1px; height: 1px; }
.vibe-chips label {
  min-height: 38px;
  display: inline-flex;
  align-items: center;
  padding: 0 0.9rem;
  border-radius: var(--radius-pill);
  background: rgba(46, 95, 64, 0.7);
  border: 1px solid var(--border);
  font-size: 0.78rem;
  cursor: pointer;
}
.vibe-chips input:checked + label,
.vibe-chips label:has(input:checked) {
  border-color: var(--accent);
  color: var(--accent);
}
.vibe-chips input:focus-visible + label { outline: 2px solid var(--amber); outline-offset: 2px; }
.profile-edit-note { font-size: 0.78rem; margin: 0.8rem 0; }
.profile-edit-panel .customize-actions { padding-bottom: 1.2rem; }

/* --- Spot card (profile grids) --- */
.spot-card {
  display: block;
  color: inherit;
  text-decoration: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: transform 160ms ease, border-color 160ms ease;
}
.spot-card:hover { transform: translateY(-3px); border-color: color-mix(in oklch, var(--accent) 50%, transparent); }
.spot-card-cover { display: block; height: 104px; background: rgba(7, 10, 8, 0.4); }
.spot-card-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.spot-card-cover-empty {
  display: block;
  height: 100%;
  background: repeating-linear-gradient(135deg, rgba(183, 223, 178, 0.07) 0 10px, transparent 10px 20px);
}
.spot-card-body { display: block; padding: 0.7rem 0.75rem; }
.spot-card-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-family: var(--display);
  font-size: 0.8rem;
  line-height: 1.25;
  margin-bottom: 0.4rem;
}
.spot-card-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.4rem; }
.spot-card-meta { color: var(--muted); font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.02em; }
.profile-review-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.profile-review-cover { width: 52px; height: 52px; flex: none; border-radius: 10px; }
.profile-review-card {
  display: flex;
  gap: 0.7rem;
  align-items: center;
  padding: 0.7rem;
  color: inherit;
  text-decoration: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: transform 160ms ease, border-color 160ms ease;
}
.profile-review-card:hover { transform: translateY(-3px); border-color: color-mix(in oklch, var(--accent) 50%, transparent); }
.profile-review-card-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.profile-hero .avatar-big {
  width: 92px;
  height: 92px;
  font-size: 2.1rem;
  border: 3px solid var(--accent);
  box-shadow: var(--shadow-brutal);
  background: var(--panel-2);
}
.profile-id-text { padding-bottom: 0.35rem; flex: 1; min-width: 0; }
.profile-id-text h1 { margin: 0; font-size: 1.6rem; }
.profile-id .tagline { margin: 0.15rem 0 0; color: var(--accent); font-weight: 600; }
.profile-id .chip-match { align-self: center; margin-bottom: 0.4rem; }
.profile-meta { display: flex; flex-wrap: wrap; gap: 0.45rem; padding: 0.85rem 1.3rem 1.1rem; }
.vibe-badge { border-color: var(--accent); color: var(--accent); }

/* The small amber mono micro-label used above a heading everywhere in the
   app. Used to exist only scoped to .customize-head and .choice-card,
   redefined whole in both places and nowhere as a bare rule, so every
   other page's eyebrow (Find people's "Step one of three", the spot
   page's "Photos"/"Ratings", the new nearby-people section labels) rendered
   as plain unstyled body text instead. The two scoped rules below now only
   override what actually differs between them: their margin. */
.eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--amber);
  /* The full shorthand, not just margin-bottom: some of this class's uses
     are the only heading a card section has (Photos, Ratings), an h2 in
     practice, not a span. h2's own base rule sets a 1.7em top margin meant
     for a heading that follows other content; this label is always the
     first thing in its card. */
  margin: 0 0 0.3rem;
}

.customize-head { margin-bottom: 1.1rem; }
.customize-head h1, .customize-head h2 { margin: 0; }
.customize-head .muted { font-size: 0.8rem; margin-top: 0.4rem; }
.field { margin: 0.9rem 0; }
.checkbox-field { display: flex; align-items: center; gap: 0.55rem; }
.checkbox-field input[type="checkbox"] { width: 1.1rem; height: 1.1rem; flex: none; }
.checkbox-field label { margin: 0; }
/* A list of "name, pick some" checkboxes: which friends were somewhere, which
   friends a private spot is shared with, which friends a post goes to.
   Written to work whether or not the checkboxes are Django's own widget
   markup, which wraps each one in its own div, or a plain loop that does not. */
.friend-checks { margin-top: 0.3rem; }
.friend-checks label { display: flex; align-items: center; gap: 0.5rem; font-weight: 400; margin: 0.3rem 0; }
.friend-checks input[type="checkbox"] { width: 1.1rem; height: 1.1rem; flex: none; }
.banner-field {
  position: relative;
  height: 200px;
  margin-bottom: 0.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background:
    repeating-linear-gradient(0deg, rgba(0,0,0,0.18) 0 1px, transparent 1px 4px),
    linear-gradient(135deg, color-mix(in srgb, var(--accent) 38%, #0b100e), #0b100e 78%);
  background-size: cover;
  background-position: center;
}
/* Positioned individually, not by absolutely placing .file-picker itself:
   that wrapper still contains .file-picker-current, and an absolute child
   of an absolute parent measures top/left against that parent, not against
   .banner-field. Two nested position: absolute elements is how "top: 12px;
   left: 12px" ended up next to the button instead of the opposite corner. */
.banner-field .file-picker-btn { position: absolute; top: 12px; right: 12px; }
.banner-field .file-picker-name { display: none; }
/* The plain "Currently: banners/2026/xyz.jpg" link is redundant once the
   image itself is the background here, and unreadable in dark text over an
   arbitrary photo besides. The Clear control still has to survive, as a
   small dark pill mirroring Replace banner on the opposite corner, since it
   is the only way to remove a banner without replacing it with another. */
.banner-field .file-picker-current {
  position: absolute;
  top: 12px;
  left: 12px;
  right: auto;
  margin: 0;
  flex-basis: auto;
  z-index: 1;
}
.banner-field .file-picker-current > a { display: none; }
.banner-field .file-picker-clear {
  padding: 0.5rem 0.75rem;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: var(--radius-pill);
  color: var(--bone);
  font-size: 0.78rem;
}
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1rem; }
.field-row .field { min-width: 0; }
.customize-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.3rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
.customize-actions .btn { flex: none; }
@media (max-width: 560px) {
  .field-row { grid-template-columns: 1fr; }
}

/* Accent swatch picker */
.swatch-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.swatch { cursor: pointer; }
.swatch input { position: absolute; opacity: 0; width: 0; height: 0; }
.swatch span {
  display: block;
  width: 34px;
  height: 34px;
  background: var(--sw);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-brutal);
  transition: transform 100ms var(--ease-out);
}
.swatch:hover span { transform: translateY(-1px); }
.swatch input:checked + span { border-color: var(--bone); outline: 2px solid var(--sw); outline-offset: 2px; }
.swatch input:focus-visible + span { outline: 2px solid var(--bone); outline-offset: 2px; }

.deck-card { max-width: 460px; margin: 1.2rem auto; text-align: center; padding: 2.2rem 1.8rem; box-shadow: var(--shadow-brutal-lg); }
.deck-card h2 { margin: 0.7rem 0 0.1rem; font-size: 1.3rem; }
.deck-card .tagline { color: var(--accent); font-weight: 600; margin: 0.1rem 0 0.5rem; }
.deck-meta { display: flex; justify-content: center; flex-wrap: wrap; gap: 0.4rem; margin: 0.5rem 0 0.2rem; }
.deck-bio { color: var(--bone); margin: 0.7rem 0 0.3rem; }
.deck-actions { display: flex; gap: 0.8rem; justify-content: center; margin-top: 1.4rem; }
.deck-actions form { margin: 0; flex: 1; max-width: 180px; }
.deck-actions .btn { width: 100%; padding: 0.7rem 1rem; }

.person { display: flex; align-items: center; gap: 0.75rem; }
.person .btn { margin-left: auto; }

/* --- Empty states --- */
.empty {
  border: 2px dashed var(--border);
  padding: 2rem 1.4rem;
  text-align: center;
  color: var(--muted);
  margin: 0.9rem 0;
  background: var(--panel);
  border-radius: var(--radius-lg);
}
.empty .big { font-size: 1.6rem; display: block; margin-bottom: 0.35rem; }
.empty .big svg { width: 28px; height: 28px; }
.empty .btn { margin-top: 0.9rem; }

/* --- Misc --- */
details { margin: 1rem 0; }
details summary {
  cursor: pointer;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.3rem 0;
}
details summary:hover { color: var(--amber); }
details[open] summary { color: var(--bone); margin-bottom: 0.4rem; }

/* The pagination links used to be styled directly (.pagination a), a
   class+element selector that always beat .btn/.btn-outline/.btn-small
   (each a single class) regardless of source order, no matter which
   loaded last. includes/pagination.html has rendered plain
   <a class="btn btn-small btn-outline"> for a while now, so every "Newer/
   Older" pair (Friends, Messages, the Overview page's Members list) was
   quietly stuck in mono font and, on hover, filled solid amber like an
   active primary button, contradicting design.md's "one accent at a time,
   on the thing you are meant to press." The .btn classes are sufficient on
   their own; see the second .pagination rule below for layout. */
.pagination { display: flex; gap: 0.8rem; justify-content: center; margin: 1.4rem 0; }

@media (pointer: coarse), (max-width: 640px) {
  /* Touch targets: aim for the 44px minimum so controls are comfortable with
     a thumb rather than needing a precise tap. */
  .nav-link, .linklike { min-height: 44px; padding-top: 0.7rem; padding-bottom: 0.7rem; }
  .btn { min-height: 44px; }
  .btn-small { min-height: 38px; }
  .map-pill { min-height: 44px; }
  .spot-item { min-height: 52px; }
  .rail-btn { width: 52px; height: 52px; }
  .swatch span { width: 44px; height: 44px; }
  .filter-row select { min-height: 44px; }
  .quick-cat { min-height: 44px; padding: 0.6rem 0.9rem; }
  /* Controls that were below the 44px minimum design.md calls
     non-negotiable. .btn-small in particular is Accept, Decline, Delete spot
     and Remove friend. */
  .btn-small { min-height: 44px; }
  .spot-card-close, .route-clear, .notif-dismiss,
  .thread-back, .thread-more { min-width: 44px; min-height: 44px; }
  .subtab, .geo-result, .icon-btn, .like-btn, .action-link,
  details summary { min-height: 44px; display: flex; align-items: center; }
}

@media (max-width: 640px) {
  .nav { padding: 0.5rem 0.8rem; }
  .brand-sub { display: none; }
  h1 { font-size: 1.6rem; }
  .container { padding: 1.1rem 1rem; }

  /* The nav drawer. Above this width .nav-burger stays display: none from
     its base rule and .nav-links is the plain row it always was; only here
     do they swap roles. This used to instead try to fit every nav-link on
     one row as icon-only, which still did not fit and forced the whole
     page 35+px wider than the viewport, so every other centred, calc()-based
     element on the page (the map's pills, the Lounge's compose button) was
     laid out against the wrong width and sat partly off-screen. */
  .nav-burger { display: flex; width: 44px; height: 44px; }
  .nav-links {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0.2rem;
    width: min(260px, calc(100vw - 1.6rem));
    max-height: calc(100vh - 76px);
    overflow-y: auto;
    padding: 0.5rem;
    /* Solid, not var(--panel-2)'s usual translucent glass: this menu is the
       one floating panel that can open directly over the globe or the map,
       and 0.75 opacity plus a blur still let enough of a bright ocean or a
       pale country label bleed through to fight with the nav text sitting
       on top of it. A menu never needs to be seen through. */
    background: rgba(26, 60, 42, 0.97);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-brutal-lg);
    z-index: 60;
  }
  .nav-drawer[open] .nav-links { display: flex; }
  .nav-links .nav-link,
  .nav-links .linklike,
  .nav-links .btn-small {
    width: 100%;
    justify-content: flex-start;
  }
  .nav-links .nav-link span, .nav-links .linklike span { display: inline; }
  .nav-links .install-btn { border-top: 1px solid var(--border); border-radius: 0; margin-top: 0.3rem; padding-top: 0.7rem; }

  /* Profile hero: avatar (96px, fixed) plus Edit profile / Message (fixed,
     pushed right by margin-left: auto) already claim most of a phone's
     width, and .profile-id-text's min-width: 0 means the row never had to
     wrap to fit the username, it just squeezed that flex item down to a
     sliver and let the text run over the button instead. flex-basis: 100%
     forces the actions onto their own line so the username gets the room
     the first line actually has. */
  .profile-actions { flex-basis: 100%; margin-left: 0; justify-content: flex-start; }

  /* Same shape of bug, the edit panel's avatar row: preview, file input
     button and help text in one unwrapping flex row was wider than a phone
     long before the help text had anywhere to go. */
  .avatar-edit-row .avatar-edit-inner { flex-wrap: wrap; }
  .avatar-edit-row .helptext { flex-basis: 100%; }
}

/* --- Motion: htmx swaps and page transitions --- */

/* htmx fades a fragment out before swapping in the replacement. The deck card
   uses this so a pass or like glides instead of snapping. */
.deck-swap.htmx-swapping { opacity: 0; transform: translateY(6px) scale(0.99); }
.deck-swap { transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out); }

/* A liked heart pops once when its fragment is swapped in. */
.like-form.htmx-added .like-btn.liked svg { animation: like-pop 320ms ease; }
@keyframes like-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.35); }
  100% { transform: scale(1.12); }
}

/* Any element htmx is waiting on dims slightly, so a slow network still
   feels answered rather than ignored. */
.htmx-request .btn, .btn.htmx-request { opacity: 0.65; }

/* Native cross-document view transitions: pages cross-fade instead of
   hard-cutting. Browsers without support simply navigate as before. */
@view-transition { navigation: auto; }

/* Press feedback. A phone has no hover, so until now a tap was answered with
   nothing at all: ten elements moved on :hover and exactly one moved on
   :active. --press is the single knob for all of it. */
.btn:active,
.card-link:active,
.chip:active,
.nav-link:active,
.subtab:active,
.icon-btn:active,
.linklike:active,
.action-link:active,
.rail-btn:active,
.reel-arrow:active,
.reel-compose:active,
.reel-tab:active,
.compose-close:active,
.map-pill:active,
.list-toggle:active,
.spot-card-close:active,
.swatch:active span {
  transform: scale(var(--press));
  transition-duration: var(--dur-fast);
}
/* .map-pill is centred by a translate, so a bare scale would jump it left. */
.map-pill:active { transform: translateX(-50%) scale(var(--press)); }
.btn:active { box-shadow: 0 2px 10px rgba(147, 224, 138,0.2); }

/* A touch screen fires a hover on tap and then leaves the element stuck in
   that state until you tap somewhere else, so on a phone every card you
   touched stayed lifted. Movement on hover is a desktop affordance. */
@media (hover: none), (pointer: coarse) {
  .btn:hover, .card-link:hover, .notif:hover, .swatch:hover span,
  .photo-grid a:hover img, .map-pill:hover, .list-toggle:hover,
  .spot-card-close:hover, .rail-btn:hover, .reel-arrow:hover,
  .reel-compose:hover, .compose-close:hover, .profile-stat:hover {
    transform: none;
  }
}

/* Reduced motion means gentler, not dead. The old rule was animation and
   transition none on every element, which also deleted the htmx swap feedback
   and the deck glide, so the app stopped answering clicks at all. Movement
   goes; opacity, colour and shadow stay, because those are the answer. */
@media (prefers-reduced-motion: reduce) {
  :root { --press: 1; }
  *, *::before, *::after {
    animation: none !important;
    transition-property: opacity, background-color, border-color, color, box-shadow, filter !important;
    transition-duration: 140ms !important;
  }
  /* The haze drifts forever and is pure decoration. */
  body::before { animation: none !important; }
  /* A cross-document view transition is neither an animation nor a
     transition, so the old block never touched it: the one whole-page
     movement in the app was the only one that survived it. */
  @view-transition { navigation: none; }
}

/* --- Notifications --- */
.nav-bell { position: relative; }
.bell-dot {
  position: absolute;
  top: 2px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--amber);
  color: #06180d;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  box-shadow: 0 0 10px rgba(147, 224, 138,0.45);
}
/* --- Alerts page --- */
.notif-page {
  position: relative;
  overflow: hidden;
  max-width: 660px;
  margin: 0 auto;
  background: var(--background);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-brutal-lg);
  padding: 1.5rem;
}
.notif-aurora {
  position: absolute;
  inset: -20%;
  pointer-events: none;
  filter: blur(38px);
  opacity: 0.4;
  background:
    radial-gradient(52% 46% at 6% 2%, rgba(183, 223, 178, 0.5), transparent 70%),
    radial-gradient(44% 40% at 94% 26%, rgba(203, 194, 238, 0.5), transparent 70%);
  animation: haze-drift 44s var(--ease-in-out) infinite alternate;
}
.notif-page-inner { position: relative; }

.notif-head-actions { display: flex; gap: 0.6rem; flex: none; }
.notif-unread-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--amber);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 0.3rem;
}
.notif-pulse {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--amber);
  animation: notif-pulse 2.4s ease-in-out infinite;
}
@keyframes notif-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
.notif-mark-read {
  background: rgba(147, 224, 138, 0.12);
  border: 1px solid rgba(147, 224, 138, 0.5);
  color: var(--amber);
}
.notif-mark-read:hover { background: var(--amber); color: var(--ink); }
.notif-clear:hover { color: var(--danger); border-color: var(--danger); background: var(--panel); }

/* Kind filter chips: shown only past a handful of alerts, per notifications.js. */
.notif-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding-bottom: 0.9rem;
  margin-bottom: 0.4rem;
  border-bottom: 1px solid var(--border);
}
.notif-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 34px;
  padding: 0 0.7rem;
  border-radius: var(--radius-pill);
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--bone);
  font-family: var(--mono);
  font-size: 0.64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.notif-chip.is-active { background: var(--amber); color: var(--ink); border-color: var(--amber); }
.notif-chip.is-active .notif-chip-dot { display: none; }
.notif-chip-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }

.notif-day-group + .notif-day-group { margin-top: 1.4rem; }
.notif-day-label {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--sage);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 0.5rem;
}
.notif-day-label::after { content: ""; flex: 1; height: 1px; background: var(--border); }

.notif-list { display: flex; flex-direction: column; gap: 0.5rem; }
.notif {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.8rem 0.9rem;
  background: rgba(46, 95, 64, 0.45);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--bone);
  font-weight: 400;
  transition: transform 140ms var(--ease-out), border-color 140ms var(--ease-out);
}
.notif:hover { transform: translateX(3px); border-color: rgba(183, 223, 178, 0.45); }
/* Unread ones carry a filled panel until dismissed or marked read, not a
   left stripe: a stripe reads as a category, and this already has one. */
.notif.is-new { background: var(--panel-2); border-color: rgba(147, 224, 138, 0.4); }

.notif-avatar { position: relative; flex: none; }
.notif-avatar-img { width: 38px; height: 38px; }
.notif-avatar-system {
  display: block;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: rgba(183, 223, 178, 0.14);
}
.notif-kind-dot {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--background);
}
.notif-kind-dot.kind-match { background: var(--amber); }
.notif-kind-dot.kind-message, .notif-kind-dot.kind-vote { background: var(--lilac); }
.notif-kind-dot.kind-like, .notif-kind-dot.kind-comment, .notif-kind-dot.kind-review { background: var(--sage); }

.notif-main { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; flex: 1; }
.notif-link {
  color: var(--bone);
  font-size: 0.84rem;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.notif-link:hover { text-decoration: none; }
/* Stretched link: the row has one real <a> (this one) plus a real <button>
   (dismiss), which cannot nest inside it, so the link is extended over the
   whole row with ::after instead. The button stays position: relative so it
   sits above the overlay and keeps taking its own clicks. */
.notif-link::after { content: ""; position: absolute; inset: 0; }
.notif-meta { display: flex; align-items: center; gap: 0.6rem; }
.notif-kind {
  font-family: var(--mono);
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.notif-kind.kind-match { color: var(--amber); }
.notif-kind.kind-message, .notif-kind.kind-vote { color: var(--lilac); }
.notif-kind.kind-like, .notif-kind.kind-comment, .notif-kind.kind-review { color: var(--sage); }
.notif-when { color: var(--muted); font-size: 0.62rem; }

.notif-dismiss-form { position: relative; flex: none; margin: 0; }
.notif-dismiss {
  width: 30px;
  height: 30px;
  border-radius: var(--radius);
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--muted);
  cursor: pointer;
}
.notif-dismiss:hover { color: var(--bone); }

/* Three fading dots from one element: a centre dot plus two box-shadow
   copies at falling opacity, rather than three elements for one motif. */
.notif-empty-motif {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--sage);
  box-shadow: -14px 0 0 rgba(183, 223, 178, 0.5), 14px 0 0 rgba(183, 223, 178, 0.2);
  margin-bottom: 0.9rem;
}
.notif-page .empty { border: 1px dashed rgba(183, 223, 178, 0.35); }
.notif-empty-actions { display: flex; justify-content: center; gap: 0.6rem; margin-top: 0.9rem; }

/* --- Installable app: offline card and the install invitation --- */
.offline-card { max-width: 30rem; margin: 4rem auto; text-align: center; }
.offline-mark {
  width: 64px;
  height: 64px;
  margin: 0 auto 1rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--ember);
  background: var(--sage-soft);
  border: 1px solid rgba(183, 223, 178,0.22);
}
.offline-mark svg { width: 30px; height: 30px; }
/* Quiet in the nav until the browser says the app is installable. It is a
   button, not a link, so it needs the surrounding nav styling reset. */
.install-btn { background: none; border: none; font: inherit; cursor: pointer; }
.install-btn[hidden] { display: none; }

/* --- Moderation queue --- */
/* A destructive button reads as destructive before it is pressed, not after. */
.btn-danger {
  color: var(--danger);
  border: 1px solid rgba(240, 135, 106, 0.5);
  background: rgba(255, 122, 92, 0.08);
  box-shadow: none;
}
.btn-danger:hover {
  color: #0b0f0c;
  background: var(--danger);
  border-color: var(--danger);
}
.mod-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
  margin: 1rem 0 2rem;
}
.mod-card {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.8rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.mod-shot {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.35);
}
.mod-shot img { width: 100%; height: 100%; object-fit: cover; }
.mod-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.78rem;
}
.mod-caption { color: var(--muted); font-size: 0.85rem; margin: 0; }
.mod-actions { display: flex; gap: 0.5rem; margin-top: auto; }
.mod-actions form { margin: 0; flex: 1; }
/* Approve and reject sit next to each other, so both need a thumb-sized
   target: a mis-tap here deletes somebody's photo. */
.mod-actions .btn { width: 100%; justify-content: center; min-height: 44px; }
.mod-reports { list-style: none; padding: 0; margin: 0 0 2rem; }
.mod-reports p { margin: 0.4rem 0 0; }

/* A photo of your own that is still waiting on a moderator. */
.photo-cell { position: relative; display: block; }
/* "In review". Two forms: a chip that sits in a normal row, and an overlay
   pinned to the corner of a photo. The overlay used to be the only form, so
   using it anywhere else dropped it out of the flow. */
.review-tag {
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--ink);
  border: 1px solid rgba(147, 224, 138,0.5);
  color: var(--amber);
  font-size: 0.68rem;
  letter-spacing: 0.02em;
}
.photo-cell .review-tag {
  position: absolute;
  left: 0.5rem;
  bottom: 0.5rem;
  background: rgba(6, 12, 9, 0.82);
}
.review-tag-float { position: absolute; left: 0.8rem; top: 0.8rem; bottom: auto; z-index: 3; }

/* Sits in the corner of any photo the viewer is allowed to remove: the
   spot's owner over every photo on their own spot, or anyone over a photo
   they uploaded themselves. A real form, not a link, so it carries a CSRF
   token, which means the photo cell it sits on can no longer be the
   clickable anchor itself, an anchor cannot contain another piece of
   interactive content. */
.photo-delete-form { position: absolute; top: 0.5rem; right: 0.5rem; z-index: 4; }
.photo-delete {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(240, 135, 106, 0.5);
  background: rgba(6, 12, 9, 0.82);
  color: var(--bone);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}
.photo-delete:hover { color: var(--danger); border-color: var(--danger); }
.rail-photo-cell .photo-delete-form { top: 0.25rem; right: 0.25rem; }
.rail-photo-cell .photo-delete { width: 20px; height: 20px; font-size: 0.7rem; }

/* An empty list is a moment to offer the way out, not just to state the
   obvious. The button clears every filter at once. */
.list-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.9rem 0 0.2rem;
}
.list-empty p { margin: 0; font-size: 0.85rem; }

/* A photo the automatic check flagged. Amber, not red: it is a reason to
   look, not a verdict. The moderator still decides. */
.mod-flag {
  margin: 0;
  padding: 0.35rem 0.55rem;
  border-radius: var(--radius);
  background: var(--amber-soft);
  border: 1px solid rgba(147, 224, 138,0.3);
  color: var(--amber);
  font-family: var(--mono);
  font-size: 0.64rem;
}

/* Closing a report. Sits under the reason, not beside it, so a long reason
   never squeezes the buttons. */
.report-actions { display: flex; gap: 0.5rem; margin-top: 0.7rem; }
.report-actions form { margin: 0; }

/* --- People and friend requests --- */
/* Replaced the swipe deck. A list you can scroll and ignore is less
   pressuring than a card that demands a decision before showing the next. */
.person-card { display: flex; flex-direction: column; gap: 0.6rem; }
.person-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--bone);
}
.person-head:hover { text-decoration: none; color: var(--amber); }
.person-head span { display: flex; flex-direction: column; min-width: 0; }
.person-head .muted { font-size: 0.78rem; }
.person-meta { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.person-stats { font-size: 0.76rem; margin: 0; }
.person-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: auto; }
.person-actions form { margin: 0; }

/* --- Analytics dashboard --- */
.stat-row { display: flex; flex-wrap: wrap; gap: 1rem; }
.stat-card {
  flex: 1 1 8rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  text-align: center;
}
.stat-num {
  font-family: var(--mono);
  font-size: 1.8rem;
  font-weight: 700;
  color: var(--sage);
}
.bar-list {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 6rem;
}
.bar-list-hourly { gap: 3px; }
.bar-row { flex: 1 1 0; height: 100%; display: flex; align-items: flex-end; }
.bar-track {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: flex-end;
  border-radius: 2px;
  overflow: hidden;
  background: var(--panel-2);
}
.bar-fill {
  width: 100%;
  min-height: 2px;
  background: var(--sage);
  border-radius: 2px 2px 0 0;
}
.bar-list-labels {
  display: flex;
  justify-content: space-between;
  margin-top: 0.35rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.68rem;
}
.country-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.country-list li { display: grid; grid-template-columns: 5rem 1fr 3rem; align-items: center; gap: 0.6rem; }
.country-name { font-family: var(--mono); font-size: 0.82rem; }
.bar-track-h { height: 0.55rem; }
.bar-fill-h {
  height: 100%;
  background: var(--amber);
  border-radius: var(--radius-pill);
}

.member-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.member-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}
.member-row:last-child { border-bottom: none; }
.chip-problem { background: rgba(240, 135, 106, 0.14); border-color: rgba(240, 135, 106, 0.4); color: var(--danger); }
.feedback-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.7rem; }
.feedback-row { padding: 0.7rem 0; border-bottom: 1px solid var(--border); }
.feedback-row:last-child { border-bottom: none; }
.feedback-row-unread { border-left: 2px solid var(--amber); padding-left: 0.7rem; }
.feedback-row-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.4rem; font-size: 0.78rem; }
.feedback-row-message { margin: 0 0 0.5rem; font-size: 0.85rem; white-space: pre-wrap; }
.member-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.member-name { font-weight: 700; }
.member-meta { font-size: 0.7rem; }
.country-total {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* --- Spot page --- */
.spot-shell {
  position: relative;
  /* overflow: hidden, not clip: clip has no scroll semantics for a focused
     descendant (a star rating radio, a hidden file input) to exploit, but
     it also cuts .spot-aurora's blur off in a hard-edged rectangle instead
     of letting it fade, which read as a worse bug than the one it fixed.
     nav.js resets any scroll a focus event causes here instead; see its
     "aurora shells" comment. */
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--background);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-brutal-lg);
  padding: 1.4rem;
}
.spot-aurora {
  position: absolute;
  inset: -20%;
  pointer-events: none;
  filter: blur(38px);
  opacity: 0.4;
  background:
    radial-gradient(50% 40% at 6% 4%, rgba(183, 223, 178, 0.4), transparent 70%),
    radial-gradient(46% 38% at 92% 30%, rgba(203, 194, 238, 0.28), transparent 70%);
  animation: haze-drift 40s var(--ease-in-out) infinite alternate;
}
.spot-shell > *:not(.spot-aurora) { position: relative; }

.spot-hero {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 1.4rem;
  align-items: start;
}
/* The spec's own "one column under 860px, photo first" was never actually
   wired up: this grid had no responsive override at all, so a phone got the
   full desktop 1.15fr/1fr split squeezed into its width, the photo reduced
   to a sliver and the title, rating and buttons crushed into what was left. */
@media (max-width: 860px) { .spot-hero { grid-template-columns: 1fr; } }
/* A grid item's implicit min-width is auto, not 0: it will not shrink
   narrower than its widest child's own content, so a flex row inside that
   refuses to wrap (the owner strip's Edit/Delete controls, which stay on
   one line on purpose) can force the whole column wider than the viewport.
   .spot-shell's overflow: hidden then silently crops whatever does not fit,
   title included, rather than showing a scrollbar to explain it. */
.spot-hero-details { min-width: 0; }
.spot-hero-photo {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border);
}
.spot-hero-photo .photo-cell { display: block; }
.spot-hero-photo img { width: 100%; height: 320px; object-fit: cover; display: block; }
.spot-hero-photo-empty {
  height: 320px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  background: repeating-linear-gradient(135deg, rgba(183, 223, 178, 0.06) 0 10px, transparent 10px 20px);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.spot-hero-chip {
  /* No color or border-color here: chip-{{ category }} (styles.css, "One
     value per category") supplies both, same as every other category chip
     in the app. Also gives .dot something to inherit via currentColor,
     which it had nothing to inherit before this had color: var(--bone). */
  position: absolute;
  top: 10px;
  left: 10px;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: rgba(7, 10, 8, 0.6);
  border-width: 1px;
  border-style: solid;
  padding: 0.28rem 0.65rem;
  font-family: var(--mono);
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-radius: var(--radius-pill);
}
.spot-hero-chip .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.spot-hero-count {
  position: absolute;
  bottom: 10px;
  right: 10px;
  background: rgba(7, 10, 8, 0.6);
  color: var(--bone);
  padding: 0.22rem 0.55rem;
  font-size: 0.58rem;
  border-radius: var(--radius-pill);
}
.spot-hero-details h1 { text-wrap: pretty; margin-bottom: 0.5rem; }
.spot-hero-rating { display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0.7rem; }
.spot-hero-rating .stars-fill { color: var(--gold); }
.spot-hero-desc { max-width: 46ch; margin: 0 0 1rem; }
.spot-owner-strip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.7rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.6rem 0.8rem;
  margin-bottom: 0.9rem;
}
.spot-owner-strip > span:not(.spot-owner-manage) { min-width: 0; }
@media (max-width: 480px) {
  /* Avatar plus name plus date already asks for most of a phone's width;
     Edit and Delete squeezed onto that same line either crowded the date
     onto an orphan third line or sat close enough to invite a mis-tap
     between "Delete" and the row above it. A second line under the owner's
     own info, right-aligned, reads as "manage this" without fighting it for
     the same row. */
  .spot-owner-manage { flex-basis: 100%; margin-left: 0; justify-content: flex-end; }
}
.spot-owner-date { display: block; }
.spot-hero-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
/* Deliberately not another pair of .btn pills: Show on the map and
   Directions right below are the actions everyone came for, and matching
   their full button treatment here made owner-only housekeeping read as
   equally important, a wall of identical pills with no sense of which one
   mattered. This sits inside the owner strip itself instead of its own
   bordered row, small and clearly coloured (amber to edit, the same red
   every other destructive action in the app uses) rather than invisible,
   but visually a rung below the real calls to action. */
.spot-owner-manage { display: flex; align-items: center; gap: 0.9rem; margin-left: auto; flex: none; }
.spot-owner-manage form { margin: 0; }
.spot-owner-edit, .spot-owner-delete {
  background: none;
  border: none;
  padding: 0;
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
}
.spot-owner-edit { color: var(--amber); }
.spot-owner-edit:hover { text-decoration: none; color: var(--bone); }
.spot-owner-delete { color: var(--danger); }
.spot-owner-delete:hover { text-decoration: underline; }

.notice-danger { border-left-color: var(--danger); background: rgba(240, 135, 106, 0.08); }
.notice-lilac { border-left-color: var(--lilac); background: rgba(203, 194, 238, 0.08); }

/* --- Composer --- */
.composer {
  background: rgba(7, 10, 8, 0.4);
  border: 1px solid rgba(90, 138, 92, 0.3);
  border-radius: var(--radius-lg);
  padding: 1rem;
  margin: 1.2rem 0;
}
.composer-switch {
  display: none;
  padding: 4px;
  gap: 4px;
  border-radius: var(--radius-pill);
  /* .subtabs (the same pattern for Find people / Friends / Messages) is
     near-black on the page's own lighter green, real contrast between the
     track and what is behind it. This sits on .composer instead, which is
     that same near-black at a barely different opacity, so the track never
     separated from its own card: "Add a photo" read as a stray pill and
     "Log a visit" / "Rate it" as plain text next to it, not three options
     in one control. panel-2 (a raised, lighter surface) actually reads as
     a distinct layer here. */
  background: var(--panel-2);
  border: 1px solid var(--border-strong);
  margin-bottom: 0.9rem;
  width: fit-content;
}
.composer.js-ready .composer-switch { display: inline-flex; }
.composer-tab {
  background: none;
  border: none;
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-family: var(--body-font);
  font-weight: 600;
  font-size: 0.72rem;
  cursor: pointer;
  min-height: 32px;
}
.composer-tab.is-active { background: var(--amber); color: var(--ink); font-weight: 700; }
.composer-note { font-size: 0.72rem; }
.composer-actions { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; margin-top: 0.7rem; flex-wrap: wrap; }
.composer-visit-cols { display: grid; grid-template-columns: 1.4fr 1fr; gap: 1rem; }
.composer-visit-row { display: flex; align-items: center; gap: 0.7rem; margin-top: 0.5rem; flex-wrap: wrap; }
.composer-visit-save { margin-left: auto; }
.composer-signedout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1.1rem;
  margin: 1.2rem 0;
  flex-wrap: wrap;
}
@media (max-width: 720px) { .composer-visit-cols { grid-template-columns: 1fr; } }

/* flex-wrap: wrap, not nowrap: five 44px star buttons plus the readout text
   do not fit one row under about 360px. Unwrapped, the readout shrank to a
   sliver and wrapped one word per line instead, which was still wider than
   the row had space for, and the overflow made .spot-shell itself
   horizontally scrollable, no page-level overflow, so nothing upstream
   caught it, but focusing a star scrolled the whole shell sideways to bring
   its (invisible, 1x1px) radio input into view. */
.star-input { border: none; padding: 0; margin: 0 0 0.7rem; display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }
.star-input-readout { flex-basis: 100%; }
.star-input input[type="radio"] { position: absolute; top: 0; left: 0; opacity: 0; width: 1px; height: 1px; }
.star-input-star {
  font-size: 1.6rem;
  line-height: 1;
  color: rgba(217, 184, 119, 0.28);
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.star-input-star.is-filled { color: var(--gold); }
.star-input input:focus-visible + .star-input-star { outline: 2px solid var(--amber); outline-offset: 2px; }
.star-input-readout { color: var(--muted); margin-left: 0.4rem; }

/* --- Two columns below the composer --- */
.spot-cols { display: grid; grid-template-columns: 1.55fr 1fr; gap: 1.5rem; align-items: start; margin-top: 1.4rem; }
@media (max-width: 900px) { .spot-cols { grid-template-columns: 1fr; } }

.section-head { display: flex; align-items: baseline; gap: 0.6rem; margin: 1.3rem 0 0.8rem; }
.section-head h2 { margin: 0; font-size: 1.2rem; }
.section-head-rule { flex: 1; height: 1px; background: var(--border); }

.visit-card, .review-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem;
  margin-bottom: 0.65rem;
  position: relative;
  transition: border-color 140ms ease;
}
.visit-card:hover, .review-card:hover { border-color: rgba(183, 223, 178, 0.4); }
.visit-card-head, .review-card-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; }
/* align-items: flex-start overrides the column-flex default of stretch,
   which otherwise stretches .stars to this container's own width. .stars-fill
   clips to a percentage of whatever width it is given, so a stretched .stars
   (wider or narrower than its actual "★★★★★" content, depending on what else
   is in the row) put that clip boundary mid-glyph instead of between stars,
   the same rating rendering at a different, sometimes visibly cut-off, width
   from one review to the next. */
.visit-card-who, .review-card-who { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem; }
.visit-card-date, .review-card-date { font-size: 0.6rem; }
.review-card-head { justify-content: space-between; }
.review-card-date { margin-left: auto; }
.visit-card-note, .review-card-text { font-size: 0.85rem; line-height: 1.55; margin: 0; }
.visit-card-photo { display: block; margin-top: 0.6rem; border-radius: var(--radius); overflow: hidden; height: 132px; }
.visit-card-photo img { width: 100%; height: 100%; object-fit: cover; }
.card-hover-action { position: absolute; top: 8px; right: 8px; }
.card-hover-btn {
  min-width: 44px;
  min-height: 44px;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  border-radius: var(--radius);
}
.card-hover-btn:hover { color: var(--danger); }

.spot-empty {
  background: rgba(46, 95, 64, 0.55);
  border: 1px dashed rgba(183, 223, 178, 0.35);
  border-radius: var(--radius-lg);
  padding: 1.3rem 1rem;
  text-align: center;
}
.spot-empty-small { padding: 0.9rem 0.7rem; }
.spot-empty-title { font-weight: 700; color: var(--bone); margin: 0 0 0.25rem; }
.fade-stars { display: block; margin-bottom: 0.5rem; letter-spacing: 0.2em; color: rgba(217, 184, 119, 0.35); }

/* --- Rail --- */
.spot-rail { display: flex; flex-direction: column; gap: 0.9rem; }
.rail-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 0.9rem; overflow: hidden; }
.rail-card-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 0.6rem; }
.rail-map { height: 150px; border-radius: var(--radius); overflow: hidden; margin: -0.9rem -0.9rem 0.7rem; background: repeating-linear-gradient(135deg, rgba(183, 223, 178, 0.06) 0 10px, transparent 10px 20px); }
.rail-map-footer { display: flex; justify-content: space-between; align-items: center; }
.rail-map-footer a { color: var(--sage); font-size: 0.66rem; }
.rail-photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.rail-photo-cell { position: relative; display: block; border-radius: 10px; overflow: hidden; height: 62px; }
.rail-photo-cell img { width: 100%; height: 100%; object-fit: cover; }
.rail-photo-more {
  position: absolute;
  inset: 0;
  background: rgba(7, 10, 8, 0.5);
  color: var(--bone);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.85rem;
}
.rating-bars { display: flex; flex-direction: column; gap: 0.4rem; }
.rating-bar-row { display: grid; grid-template-columns: 1rem 1fr 1.4rem; align-items: center; gap: 0.5rem; }
.rating-bar-track { height: 5px; border-radius: var(--radius-pill); background: rgba(7, 10, 8, 0.45); overflow: hidden; }
.rating-bar-fill { display: block; height: 100%; background: var(--gold); }
.rail-report {
  width: 100%;
  min-height: 40px;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
}
.rail-report:hover { color: var(--danger); }

/* Sounds like: one track (of possibly several suggested) that captures how
   a spot feels, built as our own card from Spotify's track data rather
   than their embed, so the loudest thing on the page is never someone
   else's brand. .rail-card already supplies this block's background,
   border and radius; everything below is what sits inside it. */
.vibe-desc { max-width: 46ch; font-size: 0.75rem; line-height: 1.5; margin: 0 0 0.8rem; }

.vibe-track-body { display: flex; gap: 14px; margin-bottom: 0.7rem; }
.vibe-cover {
  flex: none;
  width: 84px;
  height: 84px;
  border-radius: var(--radius);
  object-fit: cover;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}
.vibe-cover-placeholder {
  background: repeating-linear-gradient(135deg, rgba(183, 223, 178, 0.1) 0 10px, transparent 10px 20px);
  background-color: rgba(7, 10, 8, 0.4);
}
.vibe-track-info { min-width: 0; flex: 1; }
.vibe-track-title {
  margin: 0;
  font-family: "Inter", var(--body-font);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--bone);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vibe-track-artist { margin: 0.1rem 0 0.5rem; font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vibe-moods { display: flex; flex-wrap: wrap; gap: 0.35rem; list-style: none; margin: 0; padding: 0; }
.vibe-moods li {
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-pill);
  background: rgba(203, 194, 238, 0.14);
  border: 1px solid rgba(203, 194, 238, 0.32);
  color: var(--lilac);
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.vibe-play-row { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.7rem; flex-wrap: wrap; }
.vibe-play-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 40px;
  padding: 0 1rem;
  background: var(--amber);
  color: #06180d;
  border: none;
  border-radius: var(--radius-pill);
  box-shadow: var(--glow);
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
}
.vibe-play-btn:hover { filter: brightness(1.08); }
.vibe-play-icon { font-size: 0.7rem; }
.vibe-progress {
  flex: 1;
  min-width: 60px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: rgba(7, 10, 8, 0.45);
  overflow: hidden;
}
.vibe-progress span { display: block; height: 100%; width: 0%; background: var(--amber); }
.vibe-no-preview { font-size: 0.66rem; }
.vibe-spotify-link {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}
.vibe-spotify-link:hover { color: var(--sage); }

.vibe-picker-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  border-radius: var(--radius);
  background: var(--panel);
  margin-bottom: 0.7rem;
}
.vibe-picker-avatar { width: 28px; height: 28px; font-size: 0.7rem; }
.vibe-picker-text { flex: 1; font-size: 0.76rem; }
.vibe-picker-text a { color: var(--sage); }
.vibe-picker-date { font-size: 0.62rem; }

.vibe-agreement { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.8rem; }
.vibe-agreement-track {
  flex: 1;
  display: block;
  height: 6px;
  border-radius: var(--radius-pill);
  background: rgba(7, 10, 8, 0.45);
  overflow: hidden;
}
.vibe-agreement-track span { display: block; height: 100%; background: var(--amber); }
.vibe-agreement-count { font-size: 0.66rem; color: var(--sage); white-space: nowrap; }

.vibe-actions { display: flex; gap: 9px; }
.vibe-actions .btn { flex: 1; min-height: 46px; }
.vibe-fits-btn.active,
a.vibe-fits-btn.active {
  background: var(--amber);
  color: #06180d;
  border-color: var(--amber);
}

.vibe-runners-up { margin-top: 0.7rem; }
.vibe-runners-up summary {
  cursor: pointer;
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.vibe-runners-up-list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.vibe-runner-row { display: flex; align-items: center; gap: 0.6rem; }
.vibe-runner-cover { flex: none; width: 34px; height: 34px; border-radius: 8px; object-fit: cover; }
.vibe-runner-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.vibe-runner-title { font-size: 0.78rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vibe-runner-artist { font-size: 0.7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vibe-runner-vote {
  flex: none;
  min-width: 44px;
  min-height: 44px;
  border-radius: var(--radius-pill);
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--bone);
  cursor: pointer;
}
.vibe-runner-vote:hover { border-color: var(--border-strong); }
.vibe-runner-vote.active { background: var(--amber-soft); border-color: rgba(147, 224, 138, 0.35); color: var(--amber); }
.vibe-runner-count { flex: none; padding: 0 0.4rem; }

.vibe-empty {
  padding: 1.2rem 1rem;
  text-align: center;
  border-radius: var(--radius);
  border: 1px dashed rgba(183, 223, 178, 0.35);
}
.vibe-empty-title { font-weight: 700; margin: 0.5rem 0 0.2rem; }
.vibe-empty .btn { margin-top: 0.6rem; }
.vibe-equaliser { display: inline-flex; align-items: flex-end; gap: 3px; height: 20px; }
.vibe-equaliser span {
  width: 3px;
  border-radius: 2px;
  background: var(--sage);
  animation: vibe-eq 900ms ease-in-out infinite;
}
.vibe-equaliser span:nth-child(1) { height: 60%; animation-delay: -600ms; }
.vibe-equaliser span:nth-child(2) { height: 100%; animation-delay: -300ms; }
.vibe-equaliser span:nth-child(3) { height: 40%; }
@keyframes vibe-eq { 0%, 100% { transform: scaleY(0.4); } 50% { transform: scaleY(1); } }
@media (prefers-reduced-motion: reduce) {
  .vibe-equaliser span { animation: none; height: 70%; }
}

.vibe-add-form { margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px solid var(--border); }
.vibe-add-form input[type="url"] { width: 100%; }

/* Search-by-name, the primary way to pick a track: a floating dropdown
   position: relative to this wrapper, the same pattern the address search
   in map.css uses against .geo-box, just scoped to its own class since this
   template does not always load map.css. */
.vibe-search-widget { position: relative; margin-bottom: 0.7rem; }
.vibe-search-input { width: 100%; }
.vibe-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 0.35rem;
  z-index: 5;
  background: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-brutal);
  max-height: 260px;
  overflow-x: hidden;
  overflow-y: auto;
}
.vibe-search-result {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-top: 1px solid var(--border);
  padding: 0.5rem 0.7rem;
  margin: 0;
  font: inherit;
  color: var(--bone);
  cursor: pointer;
}
.vibe-search-result:first-child { border-top: none; }
.vibe-search-result:hover { background: var(--amber-soft); color: var(--amber); }
.vibe-search-result img { width: 34px; height: 34px; border-radius: 4px; flex: none; object-fit: cover; }
.vibe-search-result span { display: block; font-size: 0.78rem; }
.vibe-search-result strong { display: block; font-size: 0.84rem; }
.vibe-search-none { padding: 0.6rem 0.7rem; margin: 0; font-size: 0.8rem; }

.vibe-selected-track {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.6rem;
  padding: 0.5rem 0.7rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.vibe-selected-cover { width: 40px; height: 40px; border-radius: 4px; flex: none; object-fit: cover; }
.vibe-selected-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.vibe-selected-info strong,
.vibe-selected-info span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.82rem; }
.vibe-selected-clear {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.vibe-selected-clear:hover { color: var(--danger); border-color: var(--danger); }

/* Collapsed by default: a link is the fallback for the rare case someone
   already has one open, not the first thing offered. */
.vibe-link-fallback { margin: 0.7rem 0; font-size: 0.8rem; }
.vibe-link-fallback[open] summary { margin-bottom: 0.5rem; }
.vibe-link-fallback input[type="url"] { width: 100%; margin-top: 0.3rem; }

.vibe-mood-picker { border: none; padding: 0; margin: 0.6rem 0; }
.vibe-mood-picker legend { padding: 0; margin-bottom: 0.4rem; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; }
.vibe-mood-picker > div { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.vibe-mood-picker label {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  font-size: 0.72rem;
  cursor: pointer;
}
.vibe-mood-picker input[type="checkbox"] { position: absolute; opacity: 0; width: 1px; height: 1px; }
.vibe-mood-picker input:checked + label,
.vibe-mood-picker label:has(input:checked) {
  border-color: rgba(203, 194, 238, 0.5);
  background: rgba(203, 194, 238, 0.14);
  color: var(--lilac);
}
.vibe-mood-picker input:focus-visible + label { outline: 2px solid var(--amber); outline-offset: 2px; }
.vibe-play-btn:focus-visible,
.vibe-fits-btn:focus-visible,
.vibe-runner-vote:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }

/* --- Crew: Find people, Friends, Messages --- */
/* Every other aurora shell (.notif-page, .spot-shell, .vote-shell,
   .pin-form-card) is a bordered, shadowed card that its own aurora clips
   inside of, so the clip reads as "the edge of a card." This one had none of
   that: just an invisible box clipping the glow to its own bounds, which on
   a wide screen showed up as a hard-edged rectangle of light floating in
   empty page background with nothing marking why it stopped there. */
.crew-page {
  position: relative;
  overflow: hidden;
  background: var(--background);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-brutal-lg);
  padding: 1.6rem 1.7rem 2rem;
}
.crew-aurora {
  position: absolute;
  inset: -20%;
  pointer-events: none;
  filter: blur(38px);
  opacity: 0.5;
  background:
    radial-gradient(52% 46% at 6% 2%, rgba(183, 223, 178, 0.35), transparent 70%),
    radial-gradient(44% 40% at 98% 26%, rgba(203, 194, 238, 0.28), transparent 70%),
    radial-gradient(56% 42% at 22% 100%, rgba(147, 224, 138, 0.22), transparent 70%);
  animation: haze-drift 40s var(--ease-in-out) infinite alternate;
}
.crew-page > *:not(.crew-aurora) { position: relative; }

.crew-page-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.crew-page-head .kicker { max-width: 44ch; }
.crew-stat { text-align: right; }
.crew-stat strong { display: block; font-family: var(--display); font-size: 1.7rem; color: var(--sage); line-height: 1; }

.crew-search {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  border-radius: var(--radius-lg);
  padding: 0.85rem 1.05rem;
  background: rgba(7, 10, 8, 0.4);
  border: 1px solid var(--border-strong);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  margin-bottom: 1.2rem;
}
.crew-search:focus-within { border-color: var(--amber); }
.crew-search-label { color: var(--amber); font-weight: 700; font-size: 0.68rem; }
.crew-search input {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-size: 0.86rem;
}
.crew-search-divider { width: 1px; height: 18px; background: rgba(90, 138, 92, 0.35); }

.crew-two-col { display: grid; grid-template-columns: 1.25fr 1fr; gap: 0.9rem; margin-bottom: 1.4rem; }
@media (max-width: 860px) { .crew-two-col { grid-template-columns: 1fr; } }

.crew-request-panel {
  background: var(--panel-2);
  border: 1px solid rgba(147, 224, 138, 0.45);
  box-shadow: var(--glow);
  border-radius: var(--radius-lg);
  padding: 1rem;
}
.crew-request-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.7rem; color: var(--amber); font-family: var(--mono); font-size: 0.7rem; }
.crew-pulse-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--amber); animation: notif-pulse 2.4s ease-in-out infinite; }
.crew-request { display: flex; flex-direction: column; gap: 0.6rem; }
.crew-request + .crew-request { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--border); }
.crew-request-who { display: flex; align-items: center; gap: 0.6rem; color: inherit; }
.crew-request-who span { display: flex; flex-direction: column; }
.crew-request-actions { display: flex; gap: 0.6rem; }
.crew-request-actions form { flex: 1; }
.crew-request-actions .btn { width: 100%; }

.crew-location-prompt {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  background: var(--panel-2);
  border: 1px solid rgba(147, 224, 138, 0.35);
  border-radius: var(--radius-lg);
  padding: 0.9rem 1.1rem;
  margin-bottom: 1.4rem;
}
.crew-location-prompt p { margin: 0; max-width: 46ch; font-size: 0.85rem; }
.crew-location-prompt .btn { flex: none; }

.crew-reach-panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 1rem; }
.crew-reach-row { margin-top: 0.6rem; }
.crew-reach-label { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.8rem; margin-bottom: 0.3rem; }
.crew-reach-label strong { font-family: var(--display); font-size: 0.95rem; color: var(--bone); }
.crew-reach-track { display: block; height: 4px; border-radius: var(--radius-pill); background: rgba(7, 10, 8, 0.4); overflow: hidden; }
.crew-reach-fill { display: block; height: 100%; }
.crew-reach-fill-amber { background: var(--amber); }
.crew-reach-fill-sage { background: var(--sage); }

.crew-section-label { display: block; margin: 1.6rem 0 0.6rem; }
.crew-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 720px) { .crew-grid { grid-template-columns: 1fr; } }

.crew-card { display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 1rem; backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease; }
.crew-card:hover { transform: translateY(-3px); border-color: rgba(183, 223, 178, 0.5); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35); }
.crew-card-head { display: flex; align-items: center; gap: 0.7rem; color: inherit; }
.crew-card-head strong { display: block; }
.crew-card-tagline { display: block; font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crew-card-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.7rem 0 0.75rem; }
.crew-chip-lilac { background: rgba(203, 194, 238, 0.14); color: var(--lilac); border-color: transparent; }
.crew-chip-sage { background: rgba(183, 223, 178, 0.1); color: var(--sage); border-color: transparent; }
.crew-card-action { margin-top: auto; }
.crew-add-friend {
  width: 100%;
  min-height: 44px;
  background: rgba(147, 224, 138, 0.12);
  border: 1px solid rgba(147, 224, 138, 0.5);
  color: var(--amber);
}
.crew-add-friend:hover { background: var(--amber); color: var(--ink); }

.crew-empty { text-align: center; }
.crew-empty-wide { grid-column: 1 / -1; }
.crew-empty-dots { display: flex; justify-content: center; gap: 0.35rem; margin-bottom: 0.6rem; }
.crew-empty-dots span { width: 8px; height: 8px; border-radius: 50%; background: var(--sage); }
.crew-empty-dots span:nth-child(2) { opacity: 0.6; }
.crew-empty-dots span:nth-child(3) { opacity: 0.3; }
.crew-empty-line { display: block; width: 1px; height: 30px; margin: 0 auto 0.7rem; background: linear-gradient(var(--sage), transparent); }
.crew-empty-bars { display: flex; justify-content: center; align-items: flex-end; gap: 4px; height: 24px; margin-bottom: 0.6rem; }
.crew-empty-bars span { width: 5px; background: var(--sage); border-radius: 2px; }
.crew-empty-bars span:nth-child(1) { height: 60%; opacity: 0.8; }
.crew-empty-bars span:nth-child(2) { height: 100%; opacity: 0.5; }
.crew-empty-bars span:nth-child(3) { height: 40%; opacity: 0.3; }

.crew-incoming-line { margin: 0.4rem 0 1rem; }
.crew-incoming-line a { color: var(--amber); }

.crew-friend-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.6rem; }
@media (max-width: 720px) { .crew-friend-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 420px) { .crew-friend-grid { grid-template-columns: 1fr; } }

.pagination { display: flex; align-items: center; justify-content: center; gap: 0.9rem; margin-top: 1.3rem; }
.pagination-status { font-size: 0.72rem; }
.crew-friend-tile {
  position: relative;
  text-align: center;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform 160ms ease, border-color 160ms ease;
}
.crew-friend-tile:hover { transform: translateY(-3px); border-color: rgba(183, 223, 178, 0.5); }
.crew-friend-avatar { margin-bottom: 0.55rem; }
.crew-friend-name { color: var(--bone); font-weight: 700; font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.crew-friend-count { display: block; margin: 0.15rem 0 0.7rem; }
.crew-friend-message {
  width: 100%;
  min-height: 36px;
  border-radius: var(--radius);
  background: none;
  border: 1px solid rgba(147, 224, 138, 0.45);
  color: var(--amber);
}
.crew-friend-message:hover { background: var(--amber); color: var(--ink); }
.crew-friend-remove-form { position: absolute; top: 6px; right: 6px; }
.crew-friend-remove {
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 140ms ease, color 140ms ease;
}
.crew-friend-tile:hover .crew-friend-remove,
.crew-friend-remove:focus-visible { opacity: 1; }
.crew-friend-remove:hover { color: var(--danger); }
@media (hover: none), (pointer: coarse) {
  .crew-friend-remove { opacity: 1; }
}

/* --- Safety card: five numbered cards, horizontally scrollable --- */
.safety-card {
  background: rgba(7, 10, 8, 0.42);
  border: 1px solid rgba(183, 223, 178, 0.3);
  border-radius: var(--radius-lg);
  padding: 1.1rem;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  margin-bottom: 1.2rem;
}
.safety-card-head { display: flex; align-items: baseline; gap: 0.6rem; margin-bottom: 0.9rem; }
.safety-card-head h2 { margin: 0; font-size: 1rem; color: var(--sage); }
.safety-card-list {
  display: flex;
  gap: 0.65rem;
  overflow-x: auto;
  padding-bottom: 0.3rem;
  margin: 0 0 0.8rem;
  list-style: none;
}
.safety-card-list li {
  flex: none;
  width: 176px;
  background: var(--panel-2);
  border: 1px solid rgba(90, 138, 92, 0.25);
  border-radius: var(--radius);
  padding: 0.8rem;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--muted);
}
.safety-card-list li strong { display: block; margin-bottom: 0.2rem; color: var(--bone); font-size: 0.78rem; }
.safety-card-num { display: block; font-family: var(--display); font-size: 1.2rem; color: rgba(183, 223, 178, 0.45); margin-bottom: 0.3rem; }
.safety-strip {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  border-left: 3px solid rgba(183, 223, 178, 0.5);
  padding: 0.5rem 0.8rem;
  margin-bottom: 1.2rem;
  font-size: 0.8rem;
  flex-wrap: wrap;
}
