/* ==========================================================================
   GitTrip - shared.css
   Code shared by index / wizard / places / packing / shopping / itinerary.

   Two kinds of rules live here:

   1. GLOBAL  - components that exist on every page with the same markup
                (custom dialogs, auth loading, shared @keyframes).
                Colours are read through fallback chains so each page keeps
                using its own token vocabulary:
                  core pages  --surface  / --text      / --primary / --line
                  index       --card-bg  / --text-main / --primary / --border-color
                  shopping    --card-bg  / --text-main / --sea     / --line
                  wizard      --surface  / --text      / --primary / --border

   2. CORE    - the design-token + layout system used by places / packing /
                itinerary. Scoped to <html data-tokens="core"> so it can never
                leak into index / shopping / wizard, which have their own
                palettes. The guard is written with :where(), which adds zero
                specificity, so every selector keeps exactly the weight it had
                when it lived inside the page.

   Per-page knobs (declare in the page's own :root - it wins over this file):
     --nav-max-width      width of the bottom nav              (default 880px)
     --sheet-max-width    width of the bottom sheet            (default 620px)
     --fab-inset          FAB right offset                     (default 422px)
     --nav-label-size     bottom-nav label size                (default .62rem)
     --field-label-weight sheet field label weight             (default 750)
     --auth-loading-z     auth splash z-index                  (default 150)
     --auth-spinner-size  auth spinner size                    (default 32px)
     --dlg-danger-glow    dark-mode glow of the danger button
   ========================================================================== */


/* ==========================================================================
   0. UTILITIES  (all pages)
   ========================================================================== */

/* Every page toggles a "hidden" class from JS (admin gear, sign-out link,
   action badges, auth gates, the wizard's destination flag...) but none of
   them ever declared the rule, so those toggles did nothing. Declared here
   once, for all pages. */
.hidden {
  display: none !important;
}


/* ==========================================================================
   1. SHARED KEYFRAMES  (all pages)
   ========================================================================== */

@keyframes dialogFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes dialogPopIn {
  from { transform: scale(.92); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}

@keyframes overlayFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes sheetSlideUp {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

@keyframes auth-spin {
  to { transform: rotate(360deg); }
}


/* ==========================================================================
   2. CUSTOM DIALOG  (all pages - built by showAlert / showConfirm)
   ========================================================================== */

.custom-dialog-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .5);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Must outrank .modal-overlay (index.html: 9999) - a customConfirm/
     customAlert can fire while a bottom-sheet modal is already open
     (lockerModal's overwrite warning, adminCheckinUrl's replace-link
     confirm) and needs to render ON TOP of it, not behind it. Found via
     a real bug report: the sheet visually covered the dialog, so only a
     sliver of its buttons was reachable - not just a cosmetic z-fight. */
  z-index: 10000;
  padding: 20px;
  animation: dialogFadeIn .15s ease;
}

.custom-dialog-card {
  background: var(--surface, var(--card-bg));
  border-radius: 20px;
  padding: 22px 20px;
  max-width: 360px;
  width: 100%;
  box-shadow: var(--shadow, var(--shadow-float));
  text-align: center;
  animation: dialogPopIn .18s cubic-bezier(0.16, 1, 0.3, 1);
}

.custom-dialog-brand {
  font-size: .72rem;
  font-weight: 800;
  color: var(--primary, var(--sea));
  margin-bottom: 10px;
  opacity: .85;
}

.custom-dialog-message {
  font-size: .95rem;
  color: var(--text, var(--text-main));
  line-height: 1.5;
  margin: 0 0 20px;
  white-space: pre-wrap;
}

.custom-dialog-actions {
  display: flex;
  gap: 8px;
}

.custom-dialog-btn {
  flex: 1;
  min-height: 46px;
  border-radius: 13px;
  border: 0;
  font-weight: 800;
  font-size: .9rem;
  cursor: pointer;
  font-family: inherit;
}

.custom-dialog-btn.cancel {
  background: var(--surface-2, var(--btn-secondary-bg, var(--tab-bg, var(--border-color))));
  color: var(--text, var(--text-main));
}

.custom-dialog-btn.confirm {
  background: var(--primary, var(--sea));
  color: #fff;
}

.custom-dialog-btn.danger {
  background: var(--danger, #D14343);
  color: #fff;
}

[data-theme="dark"] .custom-dialog-btn.confirm {
  box-shadow: 0 0 14px color-mix(in srgb, var(--glow-color) 45%, transparent);
}

[data-theme="dark"] .custom-dialog-btn.danger {
  box-shadow: 0 0 14px var(--dlg-danger-glow, color-mix(in srgb, var(--danger) 55%, transparent));
}

@media (prefers-reduced-motion: reduce) {
  .custom-dialog-overlay,
  .custom-dialog-card {
    animation-duration: .01ms !important;
  }
}


/* ==========================================================================
   3. AUTH SPLASH / GATE
   auth-loading + spinner: index, shopping, places, packing, itinerary
   auth-gate + auth-card:  index, shopping
   ========================================================================== */

.auth-loading {
  position: fixed;
  inset: 0;
  z-index: var(--auth-loading-z, 150);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg, var(--bg-color, var(--whitewash)));
}

.auth-loading.hidden {
  display: none;
}

.auth-spinner {
  width: var(--auth-spinner-size, 32px);
  height: var(--auth-spinner-size, 32px);
  border-radius: 50%;
  border: 3px solid var(--line, var(--border-color));
  border-top-color: var(--primary, var(--sea));
  animation: auth-spin .8s linear infinite;
}

/* Safety-net shown by auth-gate.js if #authLoading isn't hidden within a
   reasonable time - see that file for why. Sits one layer above
   .auth-loading (never touches its hidden class, just paints over it). */
.auth-gate-timeout {
  position: fixed;
  inset: 0;
  z-index: calc(var(--auth-loading-z, 150) + 1);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg, var(--bg-color, var(--whitewash)));
}

.auth-gate-timeout.hidden {
  display: none;
}

.auth-gate-timeout-card {
  max-width: 300px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
}

.auth-gate-timeout-text {
  margin: 0;
  font: 600 .92rem/1.5 'Assistant', system-ui, sans-serif;
  color: var(--text, var(--text-main));
}

.auth-gate-timeout-btn {
  min-height: 46px;
  padding: 0 22px;
  border: 0;
  border-radius: 999px;
  background: var(--primary, var(--sea));
  color: #fff;
  font: 700 .92rem/1 'Assistant', system-ui, sans-serif;
  cursor: pointer;
}

.auth-gate {
  position: fixed;
  inset: 0;
  z-index: 5000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(12, 28, 40, .72);
  backdrop-filter: blur(10px);
}

.auth-gate.hidden {
  display: none;
}

.auth-card {
  width: min(420px, 100%);
  background: var(--card-bg);
  border: 1px solid var(--line, var(--border-color));
  border-radius: 26px;
  padding: 26px 20px;
  text-align: center;
  box-shadow: var(--shadow-float);
}


/* ==========================================================================
   4. CORE DESIGN TOKENS + DARK MODE   (places / packing / itinerary)
   Guarded by :where([data-tokens="core"]) - zero added specificity.
   ========================================================================== */

:root:where([data-tokens="core"]) {
  --primary: #1D7A93;
  --primary-dark: #0F3E63;
  --accent: #f2b84b;
  --bg: #E8F1F0;
  --surface: #FFFEFB;
  --surface-2: #D6EBE9;
  --text: #17324a;
  --muted: #57636E;
  --line: #C7DFDC;
  --danger: #c0533f;
  --success: #2f8b68;
  --shadow: 0 12px 34px rgba(18, 59, 99, .09);
  --radius: 22px;

  /* layout knobs - pages override these in their own :root */
  --nav-max-width: 880px;
  --sheet-max-width: 620px;
  --fab-inset: 422px;
  --nav-label-size: .62rem;
  --field-label-weight: 750;
}

:root:where([data-tokens="core"]).theme-local {
  --primary: #BD6E2E;
  --primary-dark: #7A431A;
  --accent: #4F8A8B;
  --bg: #F5E8D8;
  --surface: #FFFBF4;
  --surface-2: #F0DCC0;
  --line: #E3CBA8;
}

:root:where([data-tokens="core"]).theme-camping {
  --primary: #2C7755;
  --primary-dark: #2E4A1B;
  --accent: #d79a45;
  --bg: #E9F0DD;
  --surface: #FBFCF6;
  --surface-2: #DCE9C8;
  --line: #CFE0B8;
}

:root:where([data-tokens="core"]).theme-reserve {
  --primary: #6C57A7;
  --primary-dark: #2E3746;
  --accent: #c79b54;
  --bg: #E7EAEE;
  --surface: #FBFCFD;
  --surface-2: #D9DEE6;
  --line: #C7CDD6;
}

:where([data-tokens="core"])[data-theme="dark"] {
  --bg: #0B1015;
  --surface: #151D24;
  --surface-2: #1C262E;
  --text: #EAF2F1;
  --muted: #8CA3AC;
  --line: #223038;
  --shadow: 0 14px 36px rgba(0, 0, 0, .4);
}

:root:where([data-tokens="core"]).theme-abroad[data-theme="dark"] {
  --primary: #2DD4C8;
  --primary-dark: #17B3A8;
  --bg: #0B1015;
  --surface: #151D24;
  --surface-2: #1C262E;
  --line: #223038;
  --glow-color: #2DD4C8;
}

:root:where([data-tokens="core"]).theme-local[data-theme="dark"] {
  --primary: #FFA94D;
  --primary-dark: #E68A2E;
  --bg: #0B1015;
  --surface: #151D24;
  --surface-2: #1C262E;
  --line: #223038;
  --glow-color: #FFA94D;
}

:root:where([data-tokens="core"]).theme-camping[data-theme="dark"] {
  --primary: #52B788;
  --primary-dark: #45C97C;
  --bg: #0B1015;
  --surface: #151D24;
  --surface-2: #1C262E;
  --line: #223038;
  --glow-color: #52B788;
}

:root:where([data-tokens="core"]).theme-reserve[data-theme="dark"] {
  --primary: #A78BFA;
  --primary-dark: #5590C9;
  --bg: #0B1015;
  --surface: #151D24;
  --surface-2: #1C262E;
  --line: #223038;
  --glow-color: #A78BFA;
}

/* selective dark-mode glow */
:where([data-tokens="core"])[data-theme="dark"] .primary-btn {
  box-shadow: 0 0 14px color-mix(in srgb, var(--glow-color) 45%, transparent);
}

:where([data-tokens="core"])[data-theme="dark"] .fab {
  box-shadow: 0 0 0 1px var(--glow-color),
              0 0 18px color-mix(in srgb, var(--glow-color) 55%, transparent);
}


/* ==========================================================================
   4b. TRIP-CHARACTER ACCENT   (docs/schema-legs.md §3.4)
   --------------------------------------------------------------------------
   tripType still owns all color (--primary/--glow-color/etc. above never
   change here). tripCharacter only adds --accent-gradient, a bar under the
   page title, on the 2 characters (of 7) that carry an accent.

   Deliberately NOT gated behind [data-tokens="core"] the way --primary/
   --glow-color are above: those are gated because index/shopping/wizard
   already have their own local values that a shared reset would clobber.
   --accent-gradient is brand new - no page has a prior value to conflict
   with - so one top-level definition here covers all 6 pages with zero
   per-page duplication. See CLAUDE.md's note on this pattern: keep new
   classes like this on documentElement everywhere, don't let them split
   the way theme-X did (documentElement on some pages, body on others).
   ========================================================================== */
:root {
  --accent-gradient: none;
  --accent-bar-height: 0px;
  --accent-bar-margin: 0px;
}

.character-pride {
  --accent-gradient: linear-gradient(90deg, #E4405F, #FF8C42, #FFD23F, #4CAF6D, #4D9DE0, #9B6EF3);
  --accent-bar-height: 3px;
  --accent-bar-margin: 8px;
}

.character-ski {
  /* v4.17.0's original #4FC3F7 -> #B3E5FC was too close in hue/value to
     --glow-color on theme-abroad (#2DD4C8) - both light, both cyan-ish -
     the bar read as "more teal" instead of a distinct accent. Deep navy
     anchor gives real contrast against teal (different hue family, dark
     enough to stand out) without going so dark it disappears into --bg
     (#0B1015) the way an almost-black anchor did in testing. */
  --accent-gradient: linear-gradient(90deg, #0B3D91, #4FC3F7, #FFFFFF);
  --accent-bar-height: 3px;
  --accent-bar-margin: 8px;
}

/* One rule covers all 3 header shapes across the 6 pages (see the doc
   section above): packing/places/itinerary's shared .page-head, index's
   .hero-top-row, shopping's plain <header> inside .sticky-header-container.
   wizard has no trip and no page header, so it needs no selector here.
   height/margin default to 0 above, so an unmatched or accent-less
   character (or showAccent:false, applied in each page's JS by simply not
   adding either class) takes up zero space - not just an invisible bar.

   All 3 targets are `display:flex` header rows (icon + title + button),
   so a plain block ::after would become a flex *item* squeezed into that
   same row, not a bar underneath it. flex-basis:100% forces the pseudo-
   element onto its own wrapped line instead - the standard technique for
   "one more flex item, but on its own full-width row" - which is why
   flex-wrap:wrap is added below too (these headers don't set it
   otherwise, so this doesn't change how their real content behaves; only
   the forced-100%-basis ::after ever wraps). */
.page-head,
.hero-top-row,
.sticky-header-container > header {
  flex-wrap: wrap;
}

.page-head::after,
.hero-top-row::after,
.sticky-header-container > header::after {
  content: "";
  flex-basis: 100%;
  height: var(--accent-bar-height);
  margin-top: var(--accent-bar-margin);
  border-radius: 2px;
  background: var(--accent-gradient);
  overflow: hidden;
}


/* ==========================================================================
   5. CORE BOTTOM NAV   (places / packing / itinerary)
   ========================================================================== */

:where([data-tokens="core"]) .bottom-nav {
  position: fixed;
  z-index: 30;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(var(--nav-max-width), 100%);
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  background: rgba(255, 253, 250, .94);
  backdrop-filter: blur(18px);
  border-top: 1px solid var(--line);
  padding: 8px 5px max(8px, env(safe-area-inset-bottom));
}

:where([data-tokens="core"]) .bottom-nav a {
  min-height: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  text-decoration: none;
  color: var(--muted);
  font-size: 1.02rem;
  border-radius: 13px;
}

:where([data-tokens="core"]) .bottom-nav a svg {
  width: 22px;
  height: 22px;
  fill: currentColor;
}

:where([data-tokens="core"]) .bottom-nav a span {
  font-size: var(--nav-label-size);
}

:where([data-tokens="core"]) .bottom-nav a.active {
  color: var(--primary);
  background: var(--surface-2);
}

:where([data-tokens="core"])[data-theme="dark"] .bottom-nav {
  background: var(--surface);
}

:where([data-tokens="core"])[data-theme="dark"] .bottom-nav a.active {
  color: var(--glow-color);
  background: color-mix(in srgb, var(--glow-color) 14%, transparent);
  filter: drop-shadow(0 0 4px var(--glow-color));
}


/* ==========================================================================
   6. CORE SHEET / OVERLAY / TOAST / FAB   (places / packing / itinerary)
   ========================================================================== */

:where([data-tokens="core"]) .overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(10, 25, 35, .58);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: overlayFadeIn .2s ease;
}

:where([data-tokens="core"]) .sheet {
  width: min(var(--sheet-max-width), 100%);
  background: var(--surface);
  border-radius: 26px 26px 0 0;
  padding: 22px 18px max(22px, env(safe-area-inset-bottom));
  max-height: 88dvh;
  overflow: auto;
  animation: sheetSlideUp .28s cubic-bezier(0.16, 1, 0.3, 1);
}

:where([data-tokens="core"]) .handle {
  width: 42px;
  height: 5px;
  background: var(--line);
  border-radius: 9px;
  margin: -8px auto 14px;
}

:where([data-tokens="core"]) .sheet h2 {
  margin: 0 0 6px;
  font-size: 1.15rem;
}

:where([data-tokens="core"]) .sheet > p {
  color: var(--muted);
  margin: 0 0 16px;
}

:where([data-tokens="core"]) .field {
  display: grid;
  gap: 6px;
  margin: 12px 0;
}

:where([data-tokens="core"]) .field label {
  font-size: .82rem;
  font-weight: var(--field-label-weight);
}

:where([data-tokens="core"]) .sheet-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 16px;
}

:where([data-tokens="core"]) .secondary-btn {
  min-height: 48px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text);
  border-radius: 14px;
  font-weight: 750;
}

:where([data-tokens="core"]) .google-btn {
  width: 100%;
  min-height: 50px;
  border: 1px solid var(--line);
  border-radius: 15px;
  background: var(--surface);
  color: var(--text);
  font-weight: 800;
}

:where([data-tokens="core"]) .toast {
  position: fixed;
  z-index: 200;
  bottom: calc(88px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translate(-50%, 16px);
  background: #172a35;
  color: #fff;
  padding: 10px 16px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transition: .2s;
  max-width: 90%;
  text-align: center;
}

:where([data-tokens="core"]) .toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

:where([data-tokens="core"]) .fab {
  position: fixed;
  z-index: 40;
  bottom: calc(90px + env(safe-area-inset-bottom));
  right: max(18px, calc(50vw - var(--fab-inset)));
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 0;
  background: var(--primary);
  color: #fff;
  font-size: 1.8rem;
  font-weight: 400;
  line-height: 1;
  display: grid;
  place-items: center;
  box-shadow: 0 14px 30px rgba(18, 59, 99, .32);
  transition: transform .15s;
}

:where([data-tokens="core"]) .fab:active {
  transform: scale(.9);
}


/* ==========================================================================
   7. CORE ACTION MENU   (places / itinerary)
   A small "..." menu attached to a list card, for secondary/rare actions
   (edit, delete) that don't need to sit in the card's main action row.
   Markup: .action-menu > .action-menu-btn (trigger) + .action-menu-dropdown.
   Each page wires its own open/close JS - see toggleActionMenu() /
   closeAllActionMenus() in places.html for the pattern to copy.
   ========================================================================== */

:where([data-tokens="core"]) .action-menu {
  position: relative;
  flex: 0 0 auto;
}

:where([data-tokens="core"]) .action-menu-btn {
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 1.3rem;
  line-height: 1;
  min-width: 40px;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: 8px;
}

:where([data-tokens="core"]) .action-menu-btn:active {
  background: var(--surface-2);
}

:where([data-tokens="core"]) .action-menu-dropdown {
  position: absolute;
  top: 40px;
  left: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow);
  z-index: 10;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 112px;
}

:where([data-tokens="core"]) .action-menu-dropdown button {
  border: 0;
  background: transparent;
  color: var(--text);
  padding: 10px 14px;
  font-size: .82rem;
  font-weight: 750;
  text-align: right;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
}

:where([data-tokens="core"]) .action-menu-dropdown button:hover {
  background: var(--surface-2);
}

:where([data-tokens="core"]) .action-menu-dropdown button.danger {
  color: var(--danger);
}

@media (prefers-reduced-motion: reduce) {
  :where([data-tokens="core"]) .overlay,
  :where([data-tokens="core"]) .sheet {
    animation-duration: .01ms !important;
  }
}


/* ==========================================================================
   8. GLOBAL TOAST  (all pages - shown via window.toast(message, type), see
   toast.js)
   Not scoped to [data-tokens="core"] - unlike the section 6 .toast above
   (which stays untouched, still used by places/packing/itinerary's own
   toast()), this is meant for every page including index/shopping/wizard.
   Colours are fixed rather than theme-token-based on purpose: a solid pill
   reads fine over any of the 4 trip themes and both light/dark mode without
   needing per-theme overrides - same reasoning the untouched .toast above
   already relies on (it has no [data-theme="dark"] override either).
   Centered via left:50%/transform, so RTL vs LTR needs no separate rule.
   ========================================================================== */

.gt-toast {
  position: fixed;
  z-index: 200;
  /* Deliberately NOT the same 88-90px offset the three pre-existing toast
     implementations use (core .toast, index.html's .app-toast) - both can
     be visible at the same time as this one until they're deleted in a
     later commit, and stacking on the exact same line would overlap them.
     160px clears the core .fab too: bottom:90px + 58px height puts the
     FAB's own top edge at 148px, so 146px (tried first) left only a ~2px
     gap - not enough once a longer message widens the pill toward the
     FAB's horizontal position too. Verified on a 375x667 viewport (iPhone
     SE) with getBoundingClientRect: at 160px there's a real ~12px gap
     above the FAB and the pre-existing toast stays clearly below both. */
  bottom: calc(160px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translate(-50%, 16px);
  background: #172a35;
  color: #fff;
  padding: 10px 18px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s, transform .2s;
  max-width: min(90%, 420px);
  text-align: center;
  font: 700 .85rem/1.4 'Assistant', system-ui, sans-serif;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .28);
}

.gt-toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

.gt-toast.gt-toast-error {
  /* Same --danger token + fallback the global .custom-dialog-btn.danger
     above already uses (section 2) - a semantic color stays semantic
     across the app, unlike the base pill's colour which is fixed on
     purpose (see the section header) because that one has no equivalent
     "this is what danger looks like here" meaning to preserve. */
  background: var(--danger, #D14343);
}

@media (prefers-reduced-motion: reduce) {
  .gt-toast {
    transition-duration: .01ms;
  }
}
