/**
 * Join Team Wizard Styles
 * ============================================
 * Multi-step onboarding flow for invited teammates
 */

/* Modal Override */
.join-wizard-modal .wizard-content {
  max-width: 480px;
  width: 100%;
  padding: 0;
  /* SCROLL FIX (2026-07-10, user: "the modals have no scroll and the lower
     options aren't visible"). Was `overflow: hidden` with no height cap — a tall
     step (e.g. the Privacy step's 3 option cards + Complete Setup button) that
     exceeded the viewport was CLIPPED with no way to reach the lower options or
     the finish button. Cap the modal to the viewport and let the body scroll. */
  max-height: min(88vh, 720px);
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch; /* momentum scroll on iOS */
}

/* THE X (2026-10-02). A zero-height sticky row, so the standard .icon-button
   close floats over the top-right corner of every wizard shell and stays in
   reach while a long step scrolls. Only position is set here: the look is
   the shared .icon-button, identical to "My booking page" and "Create group". */
.join-wizard-modal .jw-close-row {
  position: sticky;
  top: 0;
  z-index: 3;
  height: 0;
  display: flex;
  justify-content: flex-end;
  overflow: visible;
  pointer-events: none;
}
.join-wizard-modal .jw-close-row .jw-close {
  margin: 12px 12px 0 0;
  pointer-events: auto;
}

/* Progress Bar */
.wizard-progress {
  height: 4px;
  background: var(--slate-200);
  border-radius: 2px 2px 0 0;
}

.progress-bar {
  height: 100%;
  background: linear-gradient(90deg, var(--primary), var(--primary-400));
  border-radius: 2px;
  transition: width 0.4s ease;
}

/* Wizard Step */
.wizard-step {
  padding: 40px 32px;
  text-align: center;
  animation: wizardFadeIn 0.3s ease;
}

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

/* Wizard Icon */
.wizard-icon {
  width: 72px;
  height: 72px;
  margin: 0 auto 24px;
  background: var(--primary-100);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.wizard-icon i {
  font-size: 32px;
  color: var(--primary);
}

/* 2026-09-04 — the amber/orange gradient was chosen to sit behind a waving
   hand, and that glyph was a Pro icon that never rendered here, so the circle
   read as an empty cream blob. It now holds the product mark, which brings its
   own colour, so the circle goes to the same soft teal the rest of the wizard
   uses and stops competing with it. */
.wizard-icon.welcome-icon {
  background: var(--primary-100);
}

.wizard-icon.welcome-icon i {
  color: var(--primary);
}

/* The mark sits INSIDE the circle rather than filling it - the same optical
   inset the glyph siblings get from their 32px font-size in a 72px circle. */
.wizard-icon-logo {
  width: 38px;
  height: 38px;
  display: block;
}

.wizard-icon.success-icon {
  background: var(--green-100);
}

.wizard-icon.success-icon i {
  color: var(--green-500);
}

.wizard-icon.error-icon {
  background: var(--red-100);
}

.wizard-icon.error-icon i {
  color: var(--red-500);
}

/* Titles */
.wizard-title {
  font-size: 20px;
  font-weight: 600;
  color: var(--slate-700);
  margin-bottom: 8px;
}

.team-name-display {
  font-size: 28px;
  font-weight: 700;
  color: var(--slate-900);
  margin-bottom: 16px;
}

/* Branded invite-landing touches (2026-07-23): member-count line under the
   group name, and a quiet "enter the code instead" fallback link. */
.wizard-member-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--slate-600);
  background: var(--slate-100, #f1f5f9);
  padding: 4px 12px;
  border-radius: 999px;
  margin: 0 0 16px;
}
.wizard-member-count i {
  font-size: 12px;
  opacity: 0.8;
}

/* Where the group already works, under the member count (2026-09-09).
   Places only, never names — see GET /invite/:code/where. */
.wizard-member-places {
  display: block;
  font-size: 13px;
  color: var(--slate-500, #64748b);
  margin: -8px 0 16px;
  line-height: 1.4;
}
/* [hidden] would LOSE to the display above (author rule vs UA sheet), and the
   element ships hidden until the lookup fills it — so without this it would
   render as an empty grey line on every invite. Fifth occurrence of this trap
   here; see .menu-item[hidden],
   .ipp-actions .btn[hidden], .wizard-manual-code-link[hidden]. */
.wizard-member-places[hidden] {
  display: none !important;
}
/* [hidden] LOSES TO THE RULE BELOW (2026-09-09). `display: inline-block` here
   is an AUTHOR rule; `[hidden] { display: none }` comes from the UA sheet, so
   at equal specificity the author rule wins and the element stays visible.
   The wizard hides this link for anyone who did NOT arrive by scanning a QR,
   so without this override that fix would silently do nothing.
   Fourth occurrence of this trap in this codebase — see .menu-item[hidden],
   and .ipp-actions .btn[hidden]. */
.wizard-manual-code-link[hidden] {
  display: none !important;
}

.wizard-manual-code-link {
  display: inline-block;
  margin-top: 16px;
  background: none;
  border: none;
  padding: 4px;
  font-size: 13px;
  color: var(--slate-500);
  text-decoration: underline;
  cursor: pointer;
  font-family: inherit;
}
.wizard-manual-code-link:hover {
  color: var(--slate-700, #334155);
}
body.dark-mode .wizard-member-count {
  background: #1e293b;
  color: #cbd5e1;
}
body.dark-mode .wizard-manual-code-link {
  color: #94a3b8;
}

.joined-team-name {
  font-size: 18px;
  color: var(--slate-600);
  margin-bottom: 24px;
}

.wizard-description {
  font-size: 15px;
  color: var(--slate-500);
  line-height: 1.6;
  margin-bottom: 24px;
}

/* Wizard Actions */
.wizard-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: 32px;
}

.wizard-actions .btn-lg {
  padding: 14px 32px;
  font-size: 16px;
}

/* Detected Timezone Display
   ---------------------------------------------------------------------------
   2026-09-10 (owner, screenshot): "Asia ho chi minh box can it look nicer".
   It was a flat grey slab carrying a raw IANA id. Three changes: a soft tinted
   surface with a real border so it reads as a CARD rather than a disabled
   field; the place given its own weight with the region and offset demoted to
   one quiet line beneath; and the house badge (below) as the only glyph. */
.detected-timezone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 20px 18px;
  /* OUTLINED, NOT WASHED (2026-09-10, owner: "can it be less gray" /
     "or have an outline"). The first pass tinted the surface at 2-5% teal,
     which against white is indistinguishable from grey - it read as a
     disabled field rather than a card. A confident 2px teal edge with a clean
     white interior carries the card with no wash at all, and echoes the
     outlined "Change" button directly beneath it. */
  background: #fff;
  border: 2px solid var(--primary);
  border-radius: 16px;
  margin-bottom: 16px;
}

/* HOME, IN THE APP'S OWN VOCABULARY. The board marks your own row with
   fa-house; the card that asks you to confirm that same home now matches it.
   A tinted tile rather than a bare glyph, so it reads as deliberate at the
   card's larger scale. */
.tz-home-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 10px;
  background: var(--cmx-75540ddc00);
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  color: var(--primary);
  font-size: 15px;
}

.tz-main {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 19px;
  font-weight: 700;
  color: var(--slate-800);
  letter-spacing: -0.01em;
  /* A long city ("Ho Chi Minh City") must wrap rather than push the card. */
  min-width: 0;
  text-align: left;
}

.tz-main i {
  color: var(--primary);
}

/* The supporting line: region and offset, quieter than the place but NOT grey.
   Slate here was the other half of what read as grey - the card's own accent
   colour, dimmed, keeps the hierarchy without draining the colour out. */
.tz-region,
.tz-gmt {
  white-space: nowrap;
}

.tz-region {
  color: var(--primary-600, #0d9488);
  opacity: 0.85;
}

.tz-gmt {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--primary-600, #0d9488);
}

.tz-dot {
  margin: 0 6px;
  color: var(--primary-600, #0d9488);
  opacity: 0.45;
}

/* THE INVITER'S SETUP, ATTRIBUTED (2026-08-27)
   ---------------------------------------------------------------------------
   When someone was added via "Add someone I know", the zone and hours above
   are the values their teammate typed, not the browser's guess. Say whose they
   are and keep detection one tap away — "inviter wins" is only acceptable if
   it is visible and reversible. Sits directly under the field it explains. */
.jw-seed-note {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin: -8px 0 16px;
  text-align: center;
}

.jw-seed-by {
  font-size: 13px;
  color: var(--slate-500);
  line-height: 1.4;
}

/* A link, not a button: the primary action is accepting what is already shown,
   and a second filled button beside "That's right" would compete with it. */
.jw-seed-revert {
  background: none;
  border: none;
  padding: 2px 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--primary);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  font-family: inherit;
}

.jw-seed-revert:hover {
  opacity: 0.8;
}

.jw-seed-revert:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: 4px;
}

.tz-offset {
  font-size: 14px;
  color: var(--slate-500);
}

/* Timezone Confirm Buttons */
.tz-confirm-buttons {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-bottom: 24px;
}

/* Timezone Change Form */
.tz-change-form {
  margin-bottom: 24px;
}

.tz-change-form .form-group {
  position: relative;
}

.tz-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--white);
  border: 1px solid var(--slate-200);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  z-index: 100;
  max-height: 200px;
  overflow-y: auto;
}

.tz-suggestion {
  display: flex;
  flex-direction: column;
  padding: 10px 12px;
  cursor: pointer;
  border-bottom: 1px solid var(--slate-100);
}

.tz-suggestion:hover {
  background: var(--primary-50);
}

/* Keyboard-active option (ArrowUp/Down via the tz-search combobox,
   ux-flows-10). Mirrors :hover plus an inset ring so the active option is
   visible to keyboard users even when the pointer rests elsewhere. */
.tz-suggestion.active,
.tz-suggestion[aria-selected="true"] {
  background: var(--primary-50);
  box-shadow: inset 0 0 0 2px var(--primary, #14b8a6);
}

.tz-suggestion:last-child {
  border-bottom: none;
}

.tz-city {
  font-weight: 500;
  color: var(--slate-800);
}

.tz-info {
  font-size: 12px;
  color: var(--slate-500);
}

/* Wizard Section */
.wizard-section {
  text-align: left;
  margin-top: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--slate-200);
}

.wizard-section h3 {
  font-size: 14px;
  font-weight: 600;
  color: var(--slate-700);
  margin-bottom: 12px;
}

/* Working Hours Row */
.working-hours-row {
  display: flex;
  align-items: flex-end;
  gap: 12px;
}

.working-hours-row .form-group {
  flex: 1;
}

.working-hours-row label {
  display: block;
  font-size: 12px;
  color: var(--slate-500);
  margin-bottom: 4px;
}

.working-hours-row .form-select {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--slate-200);
  border-radius: 8px;
  font-size: 14px;
  background: var(--white);
}

.hours-separator {
  padding-bottom: 12px;
  color: var(--slate-400);
}

/* Calendar Benefits */
.calendar-benefits {
  text-align: left;
  margin-bottom: 24px;
}

.benefit-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  font-size: 14px;
  color: var(--slate-600);
}

.benefit-item i {
  color: var(--green-500);
  font-size: 16px;
}

/* Calendar Connect Buttons */
.calendar-options {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 16px;
}

.calendar-connect-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 14px 20px;
  border: 2px solid var(--slate-200);
  border-radius: 10px;
  background: var(--white);
  font-size: 15px;
  font-weight: 500;
  color: var(--slate-700);
  cursor: pointer;
  transition: all 0.2s;
}

.calendar-connect-btn:hover {
  border-color: var(--primary);
  background: var(--primary-50);
}

/* Provider marks are the canonical .integration-icon tiles (settings-modals.css)
   so Google/Outlook look identical to the Settings integrations cards — the old
   bare flat-colour glyphs here (#4285f4 / #0078d4 on a naked <i>) were the only
   place in the app drawing the brands differently (user 2026-08-03). The tile
   is 42px in the settings cards; scale it to 30px inside this compact connect
   button. This file loads AFTER settings-modals.css, so these context overrides
   win the equal-specificity race by order. */
.calendar-connect-btn .integration-icon {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md, 8px);
}

.calendar-connect-btn .integration-icon i {
  font-size: 1.1rem;
}

/* Calendar step: transient "finishing…" state while the integrations modal is
   open, and the confirmed "✓ Connected" state after a real connected event. */
.calendar-connecting {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 20px;
  margin-bottom: 16px;
  border-radius: 10px;
  background: var(--slate-50, #f8fafc);
  border: 1px dashed var(--slate-300, #cbd5e1);
  font-size: 14px;
  color: var(--slate-600, #475569);
}

.calendar-connecting i { color: var(--primary, #14b8a6); }

.calendar-connected-state {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 20px;
  margin-bottom: 16px;
  border-radius: 10px;
  background: var(--green-50, #f0fdf4);
  border: 1px solid var(--green-300, #86efac);
  font-size: 15px;
  font-weight: 600;
  color: var(--green-700, #15803d);
}

.calendar-connected-state i {
  font-size: 20px;
  color: var(--green-500, #22c55e);
}

body.dark-mode .calendar-connecting {
  background: rgba(148, 163, 184, 0.12);
  border-color: var(--slate-600, #475569);
  color: var(--slate-300, #cbd5e1);
}

body.dark-mode .calendar-connected-state {
  background: rgba(34, 197, 94, 0.14);
  border-color: var(--green-600, #16a34a);
  color: var(--green-300, #86efac);
}

/* Privacy Note */
.privacy-note {
  font-size: 13px;
  color: var(--slate-500);
  margin-top: 16px;
}

.privacy-note i {
  margin-right: 6px;
}

/* 2026-08-25: the card matching the GROUP'S OWN default (unlocked groups) —
   quiet ring, same visual family as the canonical picker's selected state. */
.privacy-option-card.is-group-default {
  border-color: var(--primary-400, #2dd4bf);
}

/* 2026-08-26: brand the NATIVE radios. Unstyled, Android's Material You
   palette tints them from the user's wallpaper — reported live as a BROWN
   selected dot beside the app's teal, unlike the inviter-side picker
   (privacy-cards.css) whose custom radio is explicitly teal. accent-color is
   the whole fix: the UA keeps rendering, only the tint is pinned. */
.privacy-option-card input[type="radio"],
.privacy-fallback input[type="radio"] {
  accent-color: var(--primary-500, #14b8a6);
}

/* Hours-ownership line (2026-08-25): the joiner owns their hours; one quiet
   sentence under the section heading, same voice as .privacy-note. */
.jw-hours-ownership {
  font-size: 13px;
  color: var(--slate-500, #64748b);
  margin: 2px 0 10px;
}

/* ============================================================================
   JOIN-WIZARD PRIVACY STEP — preview blocks (2026-07-10 fix)
   ----------------------------------------------------------------------------
   The wizard's "What should the group see?" step reuses the class names
   .privacy-preview / .preview-block for a simple STACKED LIST of 3 sample rows
   (Busy / Free / a meeting title). But css/privacy-cards.css defines
   .preview-block as `position:absolute; top:4px; bottom:4px` — designed for a
   DIFFERENT component's absolute-positioned TIMELINE (blocks placed by inline
   left/width inside a sized .preview-track). With no track + no inline coords,
   all three absolute blocks collapsed onto each other AND escaped their card,
   overlapping the option labels — the garbled "Team Standup / Busy" text
   mashed over "Busy/Free Only" the user reported. Scope the blocks back into
   normal flow HERE (higher specificity via the wizard-modal ancestor beats the
   generic .preview-block), so each renders as its own stacked chip. */
#join-wizard-modal .privacy-options-with-preview {
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}
/* COMPACT preview (2026-07-10, user: "why scroll when we have space"). The
   3 sample rows were a stacked list ~110px tall PER card → 3 cards overflowed a
   short viewport. Lay them in ONE short row of small bars (~28px tall total) so
   each card is compact and the whole step fits without scrolling on normal
   screens; scroll stays as the small-screen fallback. */
#join-wizard-modal .privacy-option-card .privacy-preview {
  display: flex;
  flex-direction: row;
  gap: 4px;
  background: var(--slate-100, #f1f5f9);
  border-radius: 6px;
  padding: 5px;
  margin: 6px 0;
}
#join-wizard-modal .privacy-option-card .privacy-preview .preview-block {
  /* Undo the absolute timeline positioning from privacy-cards.css. */
  position: static;
  top: auto;
  bottom: auto;
  left: auto;
  flex: 1;
  min-width: 0;
  min-height: 20px;
  padding: 2px 6px;
  border-radius: 4px;
  justify-content: center;
  font-size: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #fff;
}
/* ⚠️THE ELLIPSIS HAS TO BE ON THE ELEMENT THAT HOLDS THE TEXT (2026-09-17).
   .preview-block already sets overflow:hidden + text-overflow:ellipsis, but
   the text lives in a child <span>, so the SPAN overflowed the block and was
   cut mid-word with no ellipsis ("eam Standu", "1 with Sarah" — measured 68px
   and 64px of text in a 60px chip at 360px wide). The block clipped it, which
   is why nothing overflowed the card and no layout assertion could see it.
   Pre-existing; visible on the merged screen because the cards now sit at
   phone width under a summary line. */
#join-wizard-modal .privacy-option-card .privacy-preview .preview-block > span {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#join-wizard-modal .privacy-option-card .privacy-preview .preview-block.busy {
  background: var(--slate-400, #94a3b8);
}
#join-wizard-modal .privacy-option-card .privacy-preview .preview-block.free {
  background: var(--slate-200, #e2e8f0);
  color: var(--slate-600, #475569);
}
#join-wizard-modal .privacy-option-card .privacy-preview .preview-block.full {
  background: var(--primary-500, #14b8a6);
}
#join-wizard-modal .privacy-option-card .privacy-preview .preview-block.private {
  background: var(--slate-500, #64748b);
}

/* COMPACT PRIVACY STEP CHROME (2026-07-10, "why scroll when we have space").
   The privacy step is the tallest (3 option cards); trim the surrounding chrome
   so it fits a normal viewport without scrolling — smaller step padding, a
   smaller icon with less margin, tighter card padding + description. */
/* ⚠️WAS `padding: 24px 28px` — A SCREEN'S PADDING ON A SECTION (2026-09-17).
   Correct while #wizard-step-4 WAS a screen; once the 09-17 merge nested it
   inside #wizard-step-1 (which already pads 32px each side) the two stacked to
   120px of horizontal padding on a 360px phone, leaving 210px of content and
   squeezing the summary row. Measured in the live app. The section keeps its
   vertical rhythm and inherits the screen's gutter. */
#join-wizard-modal #wizard-step-4 {
  padding: 24px 0 0;
}
#join-wizard-modal #wizard-step-4 .wizard-icon {
  width: 52px;
  height: 52px;
  margin-bottom: 14px;
}
#join-wizard-modal #wizard-step-4 .wizard-icon i {
  font-size: 24px;
}
#join-wizard-modal #wizard-step-4 .wizard-title {
  margin-bottom: 4px;
}
#join-wizard-modal #wizard-step-4 .wizard-description {
  margin-bottom: 16px;
}
#join-wizard-modal #wizard-step-4 .privacy-option-card {
  padding: 12px;
}
#join-wizard-modal #wizard-step-4 .privacy-desc {
  margin: 4px 0 0;
  font-size: 12px;
}

/* ⭐2026-08-20 GROUP PRIVACY LOCK (owner: "inviting a user to a preexisting
   group with fixed privacy asks the invited user which privacy level? I assume
   they have no control?"). They have none: a locked preset resolves every
   choice to the same ceiling. Show which one the group enforces and dim the
   rest rather than offering three options that all do the same thing. Mirrors
   the event modal's .capped treatment so the vocabulary is consistent. */
#join-wizard-modal #wizard-step-4 .privacy-option-card.is-capped {
  opacity: 0.45;
  cursor: not-allowed;
  filter: grayscale(0.6);
}
#join-wizard-modal #wizard-step-4 .privacy-option-card.is-capped:hover {
  border-color: var(--slate-200);
  background: none;
}
#join-wizard-modal #wizard-step-4 .privacy-option-card.is-group-locked {
  border-color: var(--primary);
  background: var(--primary-50);
}
/* The lock note replaces the "change this anytime" line, which is false here. */
#join-wizard-modal #wizard-step-4[data-privacy-locked="true"] .privacy-note {
  color: var(--slate-600);
}
body.dark-mode #join-wizard-modal #wizard-step-4 .privacy-option-card.is-group-locked {
  background: var(--slate-700);
}
body.dark-mode #join-wizard-modal #wizard-step-4[data-privacy-locked="true"] .privacy-note {
  color: var(--slate-300);
}

/* Privacy Fallback (if cards not loaded) */
.privacy-fallback {
  text-align: left;
}

.privacy-fallback label {
  display: block;
  padding: 12px;
  border: 1px solid var(--slate-200);
  border-radius: 8px;
  margin-bottom: 8px;
  cursor: pointer;
  transition: all 0.2s;
}

.privacy-fallback label:hover {
  border-color: var(--primary);
  background: var(--primary-50);
}

.privacy-fallback input[type="radio"] {
  margin-right: 10px;
}

/* What Now List */
.what-now {
  text-align: left;
  background: var(--slate-100);
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 20px;
}

.what-now p {
  font-size: 14px;
  font-weight: 600;
  color: var(--slate-700);
  margin-bottom: 12px;
}

.what-now ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.what-now li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  font-size: 14px;
  color: var(--slate-600);
}

.what-now li i {
  width: 20px;
  color: var(--primary);
}

.notification-note {
  font-size: 13px;
  color: var(--slate-500);
}

.notification-note i {
  margin-right: 6px;
  color: var(--primary);
}

/* Responsive */
@media (max-width: 480px) {
  .wizard-step {
    padding: 32px 20px;
  }

  .team-name-display {
    font-size: 22px;
  }

  .wizard-icon {
    width: 60px;
    height: 60px;
  }

  .wizard-icon i {
    font-size: 26px;
  }

  .tz-confirm-buttons {
    flex-direction: column;
  }

  .working-hours-row {
    flex-direction: column;
    align-items: stretch;
  }

  .hours-separator {
    text-align: center;
    padding: 8px 0;
  }
}

/* Dark Mode
   ----------------------------------------------------------------------------
   Gate on the app's explicit `body.dark-mode` toggle, NOT on the OS
   `@media (prefers-color-scheme: dark)`. The rest of the app (style.css) is
   light-only unless the user turns dark mode on via the in-app toggle. The old
   media-query version made the join wizard — most visibly the "Invalid or
   Expired Invite" card — render DARK whenever the visitor's OS was in dark mode,
   while every other surface stayed light. Following body.dark-mode keeps the
   wizard consistent with the rest of the app. */
body.dark-mode .wizard-progress {
  background: var(--slate-700);
}

body.dark-mode .wizard-step {
  background: var(--slate-800);
}

body.dark-mode .wizard-icon {
  background: var(--slate-700);
}

body.dark-mode .wizard-title {
  color: var(--slate-200);
}

body.dark-mode .team-name-display {
  color: var(--white);
}

body.dark-mode .detected-timezone {
  background: var(--slate-700);
}

body.dark-mode .tz-main {
  color: var(--slate-200);
}

/* The outline holds in dark mode; only the interior changes, since a white
   fill would punch a hole in a dark page. The teal edge stays at full strength
   - it is the whole device. */
body.dark-mode .detected-timezone {
  background: transparent;
  border-color: var(--primary);
  box-shadow: none;
}

body.dark-mode .tz-home-badge {
  background: var(--cmx-24d515bad6);
  background: color-mix(in srgb, var(--primary) 22%, transparent);
}

/* On a dark ground the 600 teal is too dense to read; step up to the brighter
   base tone rather than falling back to grey. */
body.dark-mode .tz-region,
body.dark-mode .tz-gmt,
body.dark-mode .tz-dot {
  color: var(--primary);
}

body.dark-mode .jw-seed-by {
  color: var(--slate-400);
}

body.dark-mode .tz-suggestions {
  background: var(--slate-700);
  border-color: var(--slate-600);
}

body.dark-mode .tz-suggestion:hover {
  background: var(--slate-600);
}

body.dark-mode .tz-suggestion.active,
body.dark-mode .tz-suggestion[aria-selected="true"] {
  background: var(--slate-600);
}

body.dark-mode .calendar-connect-btn {
  background: var(--slate-700);
  border-color: var(--slate-600);
  color: var(--slate-200);
}

body.dark-mode .calendar-connect-btn:hover {
  border-color: var(--primary);
  background: var(--slate-600);
}

body.dark-mode .what-now {
  background: var(--slate-700);
}

/* "Joining as …" (2026-09-15): an account we already know skips About you; the
   privacy step states what the join carries and offers one way back. */
.jw-joining-as {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0.6rem 0.9rem;
  border-radius: 10px;
  background: var(--slate-50, #f8fafc);
  color: var(--slate-700, #334155);
  font-size: 0.95rem;
}
.jw-joining-as .jw-joining-as-change {
  padding: 0 0.25rem;
  font-weight: 600;
}
body.dark-mode .jw-joining-as {
  background: var(--slate-700);
  color: var(--slate-100, #f1f5f9);
}

/* ─── THE JOIN IS ONE SCREEN (2026-09-17) ─────────────────────────────────────
   Owner: "there are 4 sceens for a simple choice … no need for so many screen
   changes." The invite header, "Joining means", About you and the visibility
   choice now share one scrolling screen with one submit.

   ⚠️THE SCREEN SCROLLS AS ONE DOCUMENT. It is taller than a phone viewport, so
   the submit must be reachable by scrolling — deliberately NOT a fixed footer,
   which eats height on the short landscape viewports (an iPad toolbar clipping
   was traced to exactly that class of assumption, 2026-09-16). */
#join-wizard-modal #wizard-step-1 {
  max-height: min(86vh, 900px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* A section, not a step: separated by a rule rather than a screen change, so
   the eye reads one form instead of four pages. */
#join-wizard-modal .jw-section {
  text-align: left;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--border-color, #e5e7eb);
}
#join-wizard-modal .jw-section-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 4px;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-primary, #0f172a);
}
#join-wizard-modal .jw-section-title i {
  color: var(--primary-600, #0d9488);
}
#join-wizard-modal .jw-section-desc {
  margin: 0 0 14px;
  font-size: 0.9rem;
  color: var(--slate-500, #64748b);
}

/* WHAT AGREEING MEANS. Places, never names — this block states what the join
   does, never who is behind it (an invite link is forwardable). */
#join-wizard-modal .jw-means {
  text-align: left;
  margin: 18px 0 0;
  padding: 14px 16px;
  background: var(--primary-50, #f0fdfa);
  border-left: 3px solid var(--primary-500, #14b8a6);
  border-radius: 8px;
}
#join-wizard-modal .jw-means-title {
  margin: 0 0 8px;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--slate-500, #64748b);
}
#join-wizard-modal .jw-means-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
#join-wizard-modal .jw-means-list li {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--text-primary, #0f172a);
}
#join-wizard-modal .jw-means-list i {
  margin-top: 3px;
  flex: 0 0 auto;
  color: var(--primary-600, #0d9488);
}

/* The resolved visibility answer, stated on one line. [Change] unfolds the
   cards in place — 44px so it is a real touch target, not a text link. */
/* ⚠️NOWRAP, MEASURED AT 360px (2026-09-17). This was `flex-wrap: wrap` and the
   row rendered 119px tall on a phone: "The group sees:" broke from
   "Busy/Free Only", which pushed [Change] onto a third line. A summary that
   occupies three lines is not a summary — it is worse than the three cards it
   replaced. The text ellipsises instead (min-width:0 lets a flex item shrink
   below its content width; `auto` floors it at that width, which is what made
   it wrap). Caught by running the app, not by any assertion. */
/* ⚠️MEASURED AT 360px, TWICE (2026-09-17).
   First cut was `flex-wrap: wrap`, which broke "They see:" from its level and
   pushed [Change] to a third line — 119px tall, worse than the three cards it
   replaces. Second cut was `nowrap` + ellipsis, which truncated the LEVEL:
   "They see: Busy/Free Onl…". The level is the one word the line exists to
   show, so clipping it defeats the control.
   Answer: the TEXT never wraps or clips, and the BUTTON drops beneath it when
   the row is too narrow (a 25px shortfall for the longest label in the
   narrowest case). Wide enough and it is one 44px line; too narrow and the
   answer stays whole. */
#join-wizard-modal .jw-vis-summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  min-height: 44px;
  padding: 8px 12px;
  background: var(--slate-50, #f8fafc);
  border: 1px solid var(--border-color, #e5e7eb);
  border-radius: 8px;
}
#join-wizard-modal .jw-vis-summary-text {
  /* No `flex: 1 1 auto` shrink here: the level must stay readable, so the row
     grows a line rather than the text losing characters. */
  white-space: nowrap;
  /* ⚠️DO NOT ADD flex-grow OR margin-left:auto (both tried, both measured).
     In a WRAPPING row either one makes the text claim the whole line, so
     [Change] wrapped beneath even at 393px where 264px would have fitted.
     `flex: 0 0 auto` + `max-content` keeps the LEVEL whole and lets the button
     sit beside it whenever there is room; only a genuinely too-narrow row
     (the longest label on a 360px phone) drops the button to a second line. */
  flex: 0 0 auto;
  min-width: max-content;
  font-weight: 600;
  color: var(--text-primary, #0f172a);
}
#join-wizard-modal .jw-vis-change {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 8px;
  font-weight: 600;
  color: var(--primary-700, #0f766e);
}
#join-wizard-modal .jw-vis-change[hidden] { display: none !important; }
#join-wizard-modal .jw-vis-options { margin-top: 12px; }

/* The one commitment on the one screen. */
#join-wizard-modal .jw-submit-row {
  margin-top: 22px;
  display: flex;
  justify-content: center;
}
#join-wizard-modal .jw-submit-row .btn {
  min-height: 48px;
  min-width: 190px;
}

body.dark-mode #join-wizard-modal .jw-means {
  background: var(--slate-800, #1e293b);
}
body.dark-mode #join-wizard-modal .jw-vis-summary {
  background: var(--slate-800, #1e293b);
  border-color: var(--slate-700, #334155);
}
body.dark-mode #join-wizard-modal .jw-section {
  border-top-color: var(--slate-700, #334155);
}
