/* ==========================================================================
   Page-specific styling
   ========================================================================== */

/* ---------- Dashboard ---------- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

@media (min-width: 640px) {
  .stat-row { grid-template-columns: repeat(4, 1fr); }
}

.stat-tile {
  position: relative;
  background: var(--glass-bg-lite);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  box-shadow: var(--glass-shadow);
  transition: transform var(--duration-base) var(--ease-spring),
              box-shadow var(--duration-base) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.stat-tile:hover {
  transform: translateY(-2px);
  box-shadow: var(--glass-shadow-lg), var(--glow-accent-sm);
  border-color: color-mix(in srgb, var(--color-accent) 40%, var(--glass-border));
}

/* The numeral is the whole point of a stat tile — it gets the gradient,
   the label stays plain so the pair still reads as figure + caption.
   The @supports guard matters: `color: transparent` without working
   background-clip:text renders an invisible number, so the gradient is
   only ever applied where the clip is actually honoured. */
.stat-tile .num {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  display: block;
  font-variant-numeric: tabular-nums;
}

@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .stat-tile .num {
    background: var(--gradient-accent);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}
.stat-tile .label { font-size: var(--text-xs); color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.05em; }

.plan-card {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
}

/* Tear-off calendar page: accent-tinted header strip over a lighter body,
   so the date reads as an object rather than a grey box. */
.plan-card-date {
  position: relative;
  flex-shrink: 0;
  width: 56px;
  text-align: center;
  background: var(--color-bg-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-1);
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--glass-border);
  transition: box-shadow var(--duration-base) var(--ease-standard);
}
.plan-card-date .month {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-accent-text);
  text-transform: uppercase;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
}
.plan-card-date .day {
  display: block;
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.card-interactive:hover .plan-card-date {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 45%, transparent),
              0 0 16px color-mix(in srgb, var(--color-accent) 25%, transparent);
}

.plan-card-body { flex: 1; min-width: 0; }
.plan-card-title { font-weight: var(--weight-semibold); margin-bottom: var(--space-1); }
.plan-card-meta { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); font-size: var(--text-xs); color: var(--color-text-muted); margin-bottom: var(--space-2); }
.plan-card-meta .meta-item { display: inline-flex; align-items: center; gap: 4px; }
.plan-card-meta .icon { width: 14px; height: 14px; }

.notif-list { display: flex; flex-direction: column; gap: var(--space-1); }

.notif-item {
  border-bottom: 1px solid var(--color-border);
}
.notif-item:last-child { border-bottom: none; }

.notif-item-btn {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-2);
  border: none;
  border-radius: var(--radius-md);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.12s ease, box-shadow 0.12s ease;
}
.notif-item-btn:hover { background: var(--color-bg-subtle); }
.notif-item-btn:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }

.notif-item-btn.is-unread { background: var(--color-accent-subtle); }
.notif-item-btn.is-unread:hover { background: var(--color-accent-subtle); filter: brightness(0.97); }

/* Unread rows get a lit rail on the left — the same "unread" signal the
   trailing dot carries, but visible while scanning down the column edge. */
.notif-item-btn.is-unread::before {
  content: "";
  position: absolute;
  left: 0;
  top: 20%;
  bottom: 20%;
  width: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--color-accent) 70%, transparent);
}

.notif-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--color-bg-subtle);
  color: var(--color-accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  box-shadow: inset 0 1px 0 var(--glass-highlight);
}
.notif-item-btn.is-unread .notif-icon {
  background: var(--color-accent-subtle);
  box-shadow: inset 0 1px 0 var(--glass-highlight), var(--glow-accent-sm);
}
.notif-icon .icon { width: 18px; height: 18px; }

.notif-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding-top: 2px; }
.notif-text { font-size: var(--text-sm); line-height: 1.4; }
.notif-item-btn.is-unread .notif-text { font-weight: var(--weight-semibold); }
.notif-time { font-size: var(--text-xs); color: var(--color-text-faint); }

.notif-unread-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-accent);
  flex-shrink: 0;
  margin-top: 6px;
  animation: notif-dot-pulse 2.2s ease-in-out infinite;
}

@keyframes notif-dot-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 55%, transparent); }
  50% { box-shadow: 0 0 6px 3px color-mix(in srgb, var(--color-accent) 35%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .notif-unread-dot { animation: none; }
}

.group-chip-list { display: flex; flex-direction: column; gap: var(--space-2); }
.group-chip {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: inherit;
  border: 1px solid var(--color-border);
  background: var(--glass-bg-lite);
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-spring);
}
.group-chip:hover {
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border-strong));
  background: var(--color-bg-subtle);
  box-shadow: var(--glow-accent-sm);
  transform: translateX(3px);
}
.group-chip:hover .group-icon {
  transform: rotate(-4deg) scale(1.06);
  box-shadow: inset 0 1px 0 var(--glass-highlight), var(--glow-accent-sm);
}
.group-chip-meta { font-size: var(--text-xs); color: var(--color-text-muted); }

/* ---------- Group page ---------- */
.group-banner {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
}

.group-banner-info { flex: 1; min-width: 200px; }
.group-banner-meta { display: flex; gap: var(--space-3); flex-wrap: wrap; font-size: var(--text-sm); color: var(--color-text-muted); margin-top: var(--space-1); }

.member-list { display: flex; flex-direction: column; gap: var(--space-1); }
.member-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  transition: background var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.member-row:hover {
  background: var(--color-bg-subtle);
  box-shadow: inset 3px 0 0 var(--color-accent);
}
.member-row:hover .avatar { box-shadow: 0 0 0 1px var(--color-border), var(--glow-accent-sm); }
.member-row-main { flex: 1; min-width: 0; }
.member-row-name { font-weight: var(--weight-medium); display: flex; align-items: center; gap: var(--space-2); }
.member-row-role { font-size: var(--text-xs); color: var(--color-text-muted); }

.subgroup-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.subgroup-card-icon {
  width: 40px; height: 40px; border-radius: var(--radius-md);
  background: var(--color-bg-subtle); display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.subgroup-card-body { flex: 1; min-width: 0; }
.subgroup-card-body strong { display: block; }
.subgroup-card-body span { font-size: var(--text-xs); color: var(--color-text-muted); }

/* ---------- Create plan wizard ---------- */
.wizard-panel { max-width: 720px; }
.wizard-panel[hidden] { display: none; }

.invitee-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.picker-search {
  position: sticky;
  top: calc(var(--header-height) + var(--space-2));
  background: var(--color-bg);
  z-index: 2;
  padding-bottom: var(--space-2);
}

.picker-group-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-faint);
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-2);
}

.chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.invitee-summary {
  background: var(--glass-bg-lite);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  position: sticky;
  bottom: var(--bottom-nav-clearance);
}

@media (min-width: 1024px) {
  .invitee-summary { position: static; }
}

.invitee-summary-count { font-weight: var(--weight-semibold); margin-bottom: var(--space-2); }

.final-list { display: flex; flex-direction: column; gap: var(--space-1); }
.final-list .select-row { cursor: default; }

.review-section {
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--glass-bg-lite);
  box-shadow: var(--glass-shadow);
}
.review-section h3 { margin-bottom: var(--space-3); font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-text-muted); }
.review-row { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-2) 0; border-bottom: 1px dashed var(--color-border); font-size: var(--text-sm); }
.review-row:last-child { border-bottom: none; }
.review-row dt { color: var(--color-text-muted); }
.review-row dd { margin: 0; font-weight: var(--weight-medium); text-align: right; }

/* ---------- Availability response page ---------- */
.avail-legend {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
  font-size: var(--text-xs);
}
.avail-legend-item { display: flex; align-items: center; gap: var(--space-2); }
.avail-legend-swatch { width: 14px; height: 14px; border-radius: 4px; }
.avail-legend-swatch.available { background: var(--status-available); }
.avail-legend-swatch.maybe { background: var(--status-maybe); }
.avail-legend-swatch.unavailable { background: var(--status-unavailable); }
.avail-legend-swatch.unanswered { background: var(--status-unanswered); }

/* The owner heatmap's "maybe"/"no responses yet" swatches used to need a
   .heat-legend override to match the calendar cells' fill colors, back when
   those cells were hardcoded to different hex values than the tokens above.
   Now that the calendar cells use the same --status-* tokens (see
   components.css .calendar-cell[data-status]), the base swatch rules above
   already match exactly — no override needed. */

.sticky-save-bar {
  position: sticky;
  bottom: var(--bottom-nav-clearance);
  background: var(--glass-bg-strong);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--glass-shadow-lg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

@media (min-width: 1024px) {
  .sticky-save-bar { position: static; }
}

.responder-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-2);
}
.responder-tile {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  background: var(--glass-bg-lite);
  border: 1px solid var(--glass-border);
  font-size: var(--text-xs);
  transition: box-shadow var(--duration-fast) var(--ease-standard), transform var(--duration-base) var(--ease-spring);
}
.responder-tile:hover { box-shadow: var(--glow-accent-sm); transform: translateY(-1px); }

/* ---------- Plan results ---------- */
.result-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-3);
  background: var(--glass-bg-lite);
  transition: box-shadow var(--duration-base) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.result-row:hover { border-color: var(--color-border-strong); box-shadow: var(--glass-shadow); }

.result-row.is-best {
  border-color: var(--color-accent);
  background: var(--color-accent-subtle);
  box-shadow: var(--glow-accent-sm);
}

/* The winning date is the answer the whole page is computing — it gets a
   slow breathing glow so it's unmistakable in a list of near-misses. */
.result-row.is-best::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  pointer-events: none;
  animation: best-row-glow 3.4s ease-in-out infinite;
}

@keyframes best-row-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 40%, transparent); }
  50% { box-shadow: 0 0 22px 2px color-mix(in srgb, var(--color-accent) 35%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .result-row.is-best::before { animation: none; }
}

.result-rank {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--color-bg-inset);
  display: flex; align-items: center; justify-content: center;
  font-weight: var(--weight-bold); font-size: var(--text-sm); flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  box-shadow: inset 0 1px 0 var(--glass-highlight);
}
.is-best .result-rank {
  background: var(--gradient-accent-solid);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), var(--glow-accent-sm);
}
.result-main { flex: 1; min-width: 0; }
.result-date { font-weight: var(--weight-semibold); margin-bottom: var(--space-2); }
.result-counts { display: flex; gap: var(--space-3); font-size: var(--text-xs); color: var(--color-text-muted); margin-top: var(--space-2); flex-wrap: wrap; }
.result-counts strong { color: var(--color-text); }

.participant-detail { margin-top: var(--space-3); border-top: 1px solid var(--color-border); padding-top: var(--space-3); }
.participant-detail[hidden] { display: none; }
.participant-row { display: flex; align-items: center; justify-content: space-between; padding: var(--space-2) 0; font-size: var(--text-sm); }

/* ---------- Confirmed plan ---------- */
.confirmed-hero {
  position: relative;
  z-index: 1;
  background: var(--glass-bg-strong);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-xl);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
  box-shadow: var(--glass-shadow-lg), var(--glow-accent-sm);
}

/* A slow rotating ring of light traces the border — a small celebratory
   flourish reserved for the "plan confirmed" moment, not used everywhere. */
.confirmed-hero::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(from 0deg,
    transparent 0%,
    color-mix(in srgb, var(--color-accent) 85%, transparent) 12%,
    transparent 28%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: ring-rotate 8s linear infinite;
  pointer-events: none;
}

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

@media (prefers-reduced-motion: reduce) {
  .confirmed-hero::before { animation: none; }
}

.confirmed-detail-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-top: var(--space-5);
}

@media (min-width: 640px) {
  .confirmed-detail-grid { grid-template-columns: repeat(2, 1fr); }
}

.detail-item { display: flex; gap: var(--space-3); align-items: flex-start; }
.detail-item .icon { width: 20px; height: 20px; color: var(--color-accent); flex-shrink: 0; margin-top: 2px; }
.detail-item strong { display: block; }
.detail-item span { color: var(--color-text-muted); font-size: var(--text-sm); }

.rsvp-group {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-5);
  flex-wrap: wrap;
}
.rsvp-group .btn { flex: 1; min-width: 120px; }
.rsvp-group .btn[data-active="true"] { box-shadow: inset 0 0 0 2px currentColor; }
.rsvp-group input[name="status"][value="going"] ~ .btn[data-active="true"] {
  background: var(--status-available-bg);
  border-color: var(--status-available);
  color: var(--status-available);
  box-shadow: none;
}
.rsvp-group input[name="status"][value="not_going"] ~ .btn[data-active="true"] {
  background: var(--status-unavailable-bg);
  border-color: var(--status-unavailable);
  color: var(--status-unavailable);
  box-shadow: none;
}

.bring-list { display: flex; flex-direction: column; gap: var(--space-2); }
.bring-item { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) 0; }
.bring-item input { width: 18px; height: 18px; }
.bring-item .assignee { margin-left: auto; font-size: var(--text-xs); color: var(--color-text-muted); }

/* ---------- Notifications page ---------- */
.notif-filters { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); flex-wrap: wrap; }

/* ---------- Settings ---------- */
.settings-nav { display: flex; flex-direction: column; gap: var(--space-1); }
.settings-section { margin-bottom: var(--space-8); }
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.settings-row:last-child { border-bottom: none; }
.settings-row-label strong { display: block; }
.settings-row-label span { font-size: var(--text-xs); color: var(--color-text-muted); }

.theme-picker { display: flex; gap: var(--space-3); }
.theme-option {
  flex: 1;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  text-align: center;
  cursor: pointer;
  background: var(--color-bg-elevated);
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-spring);
}
.theme-option:hover { border-color: var(--color-border-strong); transform: translateY(-2px); }
.theme-option.is-selected {
  border-color: var(--color-accent);
  box-shadow: var(--glow-accent-sm);
}
.theme-option .swatch {
  /* Was a bare <span>, so `display: inline` — which ignores height and
     collapses an empty element to zero width. The 40px preview below has
     therefore never been visible; the theme buttons have always been
     label-only. Making it a block is the whole fix. */
  display: block;
  height: 40px;
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-2);
  box-shadow: inset 0 0 0 1px var(--glass-border), inset 0 1px 0 var(--glass-highlight);
}
.theme-option[data-theme-option="light"] .swatch { background: linear-gradient(135deg, #fff 50%, #eef0f6 50%); }
.theme-option[data-theme-option="dark"] .swatch { background: linear-gradient(135deg, #1c1e2b 50%, #14151f 50%); }
.theme-option[data-theme-option="system"] .swatch { background: linear-gradient(135deg, #fff 50%, #14151f 50%); }

/* ---------- Language picker ----------
   Two shapes, one look: the Settings switcher is a form of submit buttons
   (same as .theme-picker above), while the signup form needs plain radios
   because it posts as part of the larger registration form. Both render as
   .lang-option so the choice looks identical in either place. */
.lang-picker {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.lang-option {
  flex: 1 1 140px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  min-height: 52px;
  text-align: center;
  cursor: pointer;
  background: var(--color-bg-elevated);
  color: var(--color-text);
  font: inherit;
  font-weight: var(--weight-medium);
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-spring);
}
.lang-option:hover { border-color: var(--color-border-strong); transform: translateY(-2px); }

.lang-option.is-selected,
.lang-option:has(input:checked) {
  border-color: var(--color-accent);
  box-shadow: var(--glow-accent-sm);
}

/* The radio is the accessible control; the label around it is the visual.
   Kept in the layout (not display:none) so it stays focusable and reachable
   by keyboard and assistive tech. */
.lang-option input[type="radio"] {
  position: absolute;
  opacity: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.lang-option:has(input:focus-visible) { box-shadow: var(--shadow-focus), var(--glow-accent-sm); }

/* Language code badge. Deliberately a code and not a flag emoji — see the
   note in includes/language-picker.php. */
.lang-code {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  line-height: 1;
  flex-shrink: 0;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  background: var(--color-bg-inset);
  color: var(--color-text-muted);
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}

.lang-option.is-selected .lang-code,
.lang-option:has(input:checked) .lang-code {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
  box-shadow: var(--glow-accent-sm);
}

/* ---------- Group settings / invite links ---------- */
.invite-link-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.invite-link-code {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  background: var(--color-bg-inset);
  color: var(--color-accent-text);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  flex: 1;
  min-width: 160px;
  overflow-wrap: anywhere;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 22%, transparent);
  letter-spacing: 0.02em;
}
.invite-link-meta { font-size: var(--text-xs); color: var(--color-text-muted); width: 100%; }

/* ---------- Admin ---------- */
.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}
@media (min-width: 768px) {
  .admin-stat-grid { grid-template-columns: repeat(4, 1fr); }
}

.filter-bar {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
  align-items: center;
}
.filter-bar .input, .filter-bar select { width: auto; min-width: 160px; }
.filter-bar .search-field { flex: 1; min-width: 200px; }

.audit-log-item {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.audit-log-item time { color: var(--color-text-faint); font-size: var(--text-xs); white-space: nowrap; }

.login-page-wrapper .invite-note {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  background: var(--color-accent-subtle);
  color: var(--color-accent-text);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  margin-bottom: var(--space-5);
}

.invite-context {
  background: var(--color-bg-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-6);
}
.invite-context .group-icon { margin-bottom: var(--space-3); }

/* ---- AI Assistant chat panel (ai-assistant.php) ---- */

.ai-chat-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
  height: min(60vh, 640px);
  min-height: 360px;
}

/* Below the side-nav breakpoint, the assistant stops being "a page you
   scroll, containing a card you also scroll" and becomes one full-height
   chat screen — same mental model as any native messaging app. Desktop has
   room to spare so it keeps the card-in-a-page treatment above 1024px. */
@media (max-width: 1023px) {
  .page-main.chat-page {
    /* Pinned independently to the header's bottom edge and the true
       viewport bottom, rather than a computed `calc(100dvh - ...)` height —
       subtracting two assumed constants from a viewport-height unit is
       exactly the kind of arithmetic that quietly drifts on a real phone
       (address-bar show/hide, in-app browser chrome, dvh support gaps),
       which is what left the compose row riding a bit under the nav dock.
       Anchoring both edges directly lets the browser resolve the actual
       available height itself — nothing here to get subtly wrong. */
    position: fixed;
    top: var(--header-height);
    left: 0;
    right: 0;
    /* Same --bottom-nav-clearance the FAB/toast/sticky-save-bar already
       anchor to elsewhere (all fixed-position, all fine) — a plain px
       offset, not a viewport-relative unit, so it doesn't inherit vh's
       dynamic-toolbar ambiguity. */
    bottom: var(--bottom-nav-clearance);
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
  }

  .chat-page .container {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    padding: 0;
  }

  .chat-page .page-head {
    flex-shrink: 0;
    margin-bottom: 0;
    padding: var(--space-3) var(--space-4) var(--space-2);
    animation: none;
  }
  .chat-page .page-head h1 { font-size: var(--text-lg); }
  .chat-page .page-head p { display: none; }
  .chat-page .page-head-actions .btn {
    padding: var(--space-2) var(--space-3);
    min-height: 36px;
    font-size: var(--text-xs);
  }

  .chat-page .section {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    margin-bottom: 0;
  }

  .chat-page .ai-chat-card {
    flex: 1;
    min-height: 0;
    height: auto;
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .chat-page .ai-chat-messages { padding: var(--space-4); }

  .chat-page .ai-chat-form {
    padding: var(--space-3) var(--space-4);
    padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0));
  }
}

.ai-chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ai-msg { display: flex; gap: var(--space-2); }
.ai-msg-user { flex-direction: row-reverse; }

.ai-msg-avatar { flex-shrink: 0; }
.ai-msg-avatar:empty { display: none; }
.avatar-assistant { background: var(--color-accent-subtle); color: var(--color-accent-text); }
.avatar-assistant .icon { width: 16px; height: 16px; }

.ai-msg-content { display: flex; flex-direction: column; gap: 2px; max-width: 78%; min-width: 0; }
.ai-msg-user .ai-msg-content { align-items: flex-end; }
.ai-msg-assistant .ai-msg-content, .ai-msg-error .ai-msg-content { align-items: flex-start; }

.ai-msg-bubble {
  max-width: 100%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  line-height: 1.5;
  word-wrap: break-word;
}

.ai-msg-bubble {
  animation: bubble-in var(--duration-base) var(--ease-spring) both;
}

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

@media (prefers-reduced-motion: reduce) {
  .ai-msg-bubble { animation: none; }
}

.ai-msg-user .ai-msg-bubble {
  background: var(--gradient-accent-solid);
  color: var(--color-text-on-accent);
  border-bottom-right-radius: var(--radius-sm);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--color-accent) 35%, transparent);
}
.ai-msg-assistant .ai-msg-bubble {
  background: var(--glass-bg-lite);
  border: 1px solid var(--glass-border);
  color: var(--color-text);
  border-bottom-left-radius: var(--radius-sm);
  box-shadow: var(--glass-shadow);
}
.ai-msg-error .ai-msg-bubble {
  background: var(--color-danger-subtle);
  color: var(--color-danger);
  border-bottom-left-radius: var(--radius-sm);
  box-shadow: inset 3px 0 0 var(--color-danger);
}

.ai-msg-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  padding: 0 var(--space-1);
}
.ai-msg-time { white-space: nowrap; }
.ai-msg-copy-btn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--color-text-faint);
  text-decoration: underline;
  cursor: pointer;
}
.ai-msg-copy-btn:hover { color: var(--color-text-muted); }

.ai-msg-trace {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  max-width: 100%;
}
.ai-msg-trace summary { cursor: pointer; }
.ai-msg-trace ul { margin: var(--space-1) 0 0; padding-left: var(--space-5); }

.ai-msg-confirm-actions { display: flex; gap: var(--space-2); }

.ai-quick-replies { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: center; margin-top: var(--space-3); }
.ai-quick-reply-btn {
  border: 1px solid var(--glass-border);
  background: var(--glass-bg-lite);
  color: var(--color-text);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
  cursor: pointer;
  box-shadow: var(--glass-shadow);
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-spring);
}
.ai-quick-reply-btn:hover {
  background: var(--color-bg-subtle);
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border-strong));
  box-shadow: var(--glow-accent-sm);
  transform: translateY(-2px);
}
.ai-quick-reply-btn:active { transform: scale(0.97); }

/* Markdown produced by App\AI\AgentRunner's replies, rendered client-side
   by assets/js/markdown.js — scoped to the bubble so it never leaks into
   the rest of the page's typography. */
.ai-msg-bubble p { margin: 0 0 var(--space-2); }
.ai-msg-bubble p:last-child { margin-bottom: 0; }
.ai-msg-bubble p.ai-md-heading { font-weight: var(--weight-semibold); font-size: var(--text-base); }
.ai-msg-bubble strong { font-weight: var(--weight-semibold); }
.ai-msg-bubble code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--color-bg-inset);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
}
.ai-msg-bubble pre {
  background: var(--color-bg-inset);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  overflow-x: auto;
  margin: 0 0 var(--space-2);
}
.ai-msg-bubble pre:last-child { margin-bottom: 0; }
.ai-msg-bubble pre code { background: none; padding: 0; font-size: var(--text-xs); }
.ai-msg-bubble ul, .ai-msg-bubble ol { margin: 0 0 var(--space-2); padding-left: var(--space-6); }
.ai-msg-bubble ul:last-child, .ai-msg-bubble ol:last-child { margin-bottom: 0; }
.ai-msg-bubble li { margin-bottom: var(--space-1); }
.ai-msg-bubble hr { border: none; border-top: 1px solid var(--color-border-strong); margin: var(--space-3) 0; }
.ai-msg-bubble a { color: var(--color-accent-text); text-decoration: underline; }
.ai-msg-user .ai-msg-bubble a { color: inherit; }

.ai-chat-status {
  padding: var(--space-2) var(--space-5);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-subtle);
}

.ai-chat-form {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--color-border);
}
.ai-chat-form .field { flex: 1; }
.ai-chat-form textarea { resize: vertical; min-height: 44px; }
.ai-chat-form .form-actions { margin: 0; display: flex; gap: var(--space-2); }

/* ---------- Onboarding: /welcome.php ----------
   The one screen a brand new account is guaranteed to see. Two cards, one
   question, and deliberately nothing else competing for attention — the
   whole block is width-capped and centred rather than filling the page the
   way every other view does. */
.onboarding-choice {
  max-width: 860px;
  margin: 0 auto;
  padding: var(--space-4) 0 var(--space-8);
}

.onboarding-choice-head { text-align: center; margin-bottom: var(--space-8); }
.onboarding-choice-head h1 { margin-bottom: var(--space-3); }

.onboarding-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-5);
  background: var(--glass-bg-strong);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow), var(--glow-accent-sm);
}
.onboarding-mark img { width: 44px; height: 44px; border-radius: var(--radius-sm); }

.onboarding-lead {
  max-width: 58ch;
  margin: 0 auto;
  color: var(--color-text-muted);
  line-height: var(--leading-relaxed);
}

.onboarding-options {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}

@media (min-width: 720px) {
  /* Side by side once there is room, because the whole point of this screen
     is that the two options get weighed against each other. */
  .onboarding-options { grid-template-columns: repeat(2, 1fr); }
}

.onboarding-option {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-elevated);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-spring);
}
.onboarding-option:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.onboarding-option.is-primary {
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border));
  box-shadow: var(--shadow-sm), var(--glow-accent-sm);
}
.onboarding-option h2 { font-size: var(--text-lg); margin: 0; }
.onboarding-option p { color: var(--color-text-muted); margin: 0; line-height: var(--leading-relaxed); }
/* The CTA sits on the bottom edge of both cards however much text is above
   it, so the two buttons line up across the pair. */
.onboarding-option .btn { margin-top: auto; }
.onboarding-option-meta {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-faint);
  margin-bottom: var(--space-3);
}

.onboarding-option-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  background: var(--color-accent-subtle);
  color: var(--color-accent-text);
}
.onboarding-option-icon svg { width: 24px; height: 24px; }

.onboarding-footnote {
  margin: var(--space-6) 0 0;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .onboarding-option:hover { transform: none; }
}

/* ---------- Onboarding: /tutorial.php ----------
   One page of the tour per screen. Same width cap as the welcome screen, so
   moving from one to the other does not shift the layout under the reader. */
.tutorial {
  max-width: 780px;
  margin: 0 auto;
  padding-bottom: var(--space-8);
}

.tutorial-progress { margin-bottom: var(--space-6); }
.tutorial-progress-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.tutorial-step-count {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}

/* One segment per page of the tour, each a flex child so the row divides
   itself however many pages there are — no width has to be computed, which
   is what lets this work at all under a CSP that drops inline style
   attributes (see the comment in tutorial.php). */
.tutorial-segments {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.tutorial-segment {
  flex: 1;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-inset);
  box-shadow: inset 0 0 0 1px var(--color-border);
}
.tutorial-segment.is-done {
  background: color-mix(in srgb, var(--color-accent) 45%, transparent);
  box-shadow: none;
}
.tutorial-segment.is-current {
  background: var(--color-accent);
  box-shadow: var(--glow-accent-sm);
}

.tutorial-card { padding: var(--space-6); }

.tutorial-card-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.tutorial-card-head h1 { font-size: var(--text-xl); margin: 0; }

.tutorial-eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-accent-text);
  margin: 0 0 var(--space-1);
}

.tutorial-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: var(--color-accent-subtle);
  color: var(--color-accent-text);
}
.tutorial-icon svg { width: 28px; height: 28px; }

.tutorial-lead {
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  margin-bottom: var(--space-6);
}

/* A description list rather than bullets: every point on every page is a
   named thing plus what it does, which is what a <dl> is for. */
.tutorial-points { margin: 0 0 var(--space-6); }
.tutorial-point {
  padding: var(--space-4) 0;
  border-top: 1px solid var(--color-border);
}
.tutorial-point dt {
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-1);
}
.tutorial-point dd {
  margin: 0;
  color: var(--color-text-muted);
  line-height: var(--leading-relaxed);
}

.tutorial-tip {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  background: var(--color-accent-subtle);
  border: 1px solid color-mix(in srgb, var(--color-accent) 25%, transparent);
}
.tutorial-tip p { margin: 0; line-height: var(--leading-relaxed); }
.tutorial-tip-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-accent-text);
}

.tutorial-outro {
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border);
  text-align: center;
}
.tutorial-outro h2 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.tutorial-outro p {
  color: var(--color-text-muted);
  margin: 0 auto;
  max-width: 52ch;
  line-height: var(--leading-relaxed);
}

/* space-between, with an empty <span> holding the left slot on page one, so
   "Next" stays where it is instead of jumping left when "Back" is absent. */
.tutorial-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

/* ---------- "Invite everyone" shortcut ----------
   Sits above the subgroup chips and the member list on the create-plan
   wizard and the plan's add-invitees panel, and is deliberately built like
   a card rather than a checkbox row: it replaces both lists in one tick, so
   it should not look like a third item in a list of ticks. */
.invite-all {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-bg-elevated);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard);
}
.invite-all:hover {
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border-strong));
}

/* Checked, the whole card commits — same accent-subtle fill the chips and
   invitee rows use, so "chosen" looks identical everywhere on the page. */
.invite-all:has(input:checked) {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent);
  box-shadow: var(--glow-accent-sm);
}

.invite-all-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  color: var(--color-text-muted);
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.invite-all-icon .icon { width: 22px; height: 22px; }
.invite-all:has(input:checked) .invite-all-icon {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
}

.invite-all-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.invite-all-body strong { font-weight: var(--weight-semibold); }
.invite-all-body span { font-size: var(--text-xs); color: var(--color-text-muted); line-height: var(--leading-normal); }
.invite-all:has(input:checked) .invite-all-body strong { color: var(--color-accent-text); }

/* Order matters on a phone: the box is the control, so it stays first in
   the DOM, but the icon reads better leading the line. Swapping visually
   rather than in markup keeps the tab/label association intact. */
.invite-all input[type="checkbox"] { order: -1; }

/* A labelled rule between the shortcut and the manual pickers. It is the
   one piece of this screen that explains the relationship between them —
   these are alternatives, not a sequence — so it earns the space. */
.invitee-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-5);
  color: var(--color-text-faint);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.invitee-or::before,
.invitee-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}

/* Everything the shortcut makes redundant fades back, without being
   disabled — the values still post and the resolver unions them, so a
   half-made manual selection is not destroyed by trying the shortcut, and
   unticking restores the screen exactly as it was. Exclusions are pointedly
   NOT dimmed: they are the one picker that still changes the outcome. */
.invitee-picker:has(.invite-all input:checked) .field:not(:has(input[name="excludes[]"])) {
  opacity: 0.55;
  transition: opacity var(--duration-base) var(--ease-standard);
}

/* `display: flex` on a <summary> drops the disclosure triangle in Chrome, so
   the marker is suppressed deliberately and replaced with a chevron that
   rotates on open — the same open/closed language the dropdown triggers in
   components.css already use. */
.invitee-exclude-summary {
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  list-style: none;
}
.invitee-exclude-summary::-webkit-details-marker { display: none; }
.invitee-exclude-summary:hover { color: var(--color-accent-text); }
.invitee-exclude-summary .icon {
  width: 16px;
  height: 16px;
  color: var(--color-text-faint);
  transition: transform var(--duration-fast) var(--ease-standard);
}
details[open] > .invitee-exclude-summary .icon { transform: rotate(180deg); }
.invitee-exclude-summary:hover .icon { color: var(--color-accent-text); }

@media (prefers-reduced-motion: reduce) {
  .invitee-exclude-summary .icon { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .invite-all,
  .invite-all-icon,
  .invitee-picker:has(.invite-all input:checked) .field:not(:has(input[name="excludes[]"])) {
    transition: none;
  }
}
