/* Escappade Mobile-First CSS - Native App Feel */

:root {
  --primary: #00897b;
  --primary-dark: #00695c;
  --primary-light: #4db6ac;
  --accent: #ff6f61;
  --background: #f5f7fa;
  --surface: #ffffff;
  --text-primary: #1a1a1a;
  --text-secondary: #757575;
  --text-tertiary: #9e9e9e;
  --border: #e0e0e0;
  /* Fields sit on a tinted surface instead of being outlined in a hard 2px
     border. One token, so dark mode needs no second rule per control. */
  --field: #f3f5f8;
  --field-border: #e3e7ec;
  --focus-ring: rgba(0, 137, 123, 0.15);
  --success: #4caf50;
  --error: #f44336;
  --warning: #ff9800;
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  --bottom-nav-height: 60px;
  --header-height: 56px;
  --safe-area-inset-top: env(safe-area-inset-top);
  --safe-area-inset-bottom: env(safe-area-inset-bottom);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html, body {
  height: 100%;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
  background: var(--background);
  color: var(--text-primary);
  font-size: 16px;
  line-height: 1.5;
  position: fixed;
  width: 100%;
  overscroll-behavior: none;
}

/* Screens */
.screen {
  display: none;
  height: 100vh;
  height: calc(100vh - var(--safe-area-inset-bottom));
}

.screen.active {
  display: flex;
  flex-direction: column;
}

/* Auth Screen */
.auth-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 24px;
  max-width: 400px;
  margin: 0 auto;
  width: 100%;
}

.auth-logo {
  text-align: center;
  margin-bottom: 48px;
}

.auth-logo h1 {
  font-size: 2.5rem;
  font-weight: 700;
  color: var(--primary);
  margin-bottom: 8px;
  letter-spacing: -0.5px;
}

.auth-logo p {
  color: var(--text-secondary);
  font-size: 1.1rem;
}

.auth-view {
  display: none;
}

.auth-view.active {
  display: block;
  animation: fadeIn 0.3s ease;
}

.auth-footer {
  text-align: center;
  margin-top: 24px;
}

.auth-footer a {
  color: var(--primary);
  text-decoration: none;
  font-weight: 500;
}

/* Input Groups */
.input-group {
  margin-bottom: 18px;
}

.input-group label {
  display: block;
  margin-bottom: 7px;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-secondary);
}

/* One rule for every control, so a type nobody styled (select, color, date)
   cannot fall back to the raw platform widget the way select used to. */
.input-group input,
.input-group select,
.input-group textarea {
  width: 100%;
  padding: 13px 14px;
  border: 1px solid var(--field-border);
  border-radius: var(--radius-md);
  /* 16px keeps iOS from zooming the viewport on focus. */
  font-size: 16px;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--field);
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
  -webkit-appearance: none;
  appearance: none;
}

.input-group textarea {
  resize: vertical;
  min-height: 88px;
  line-height: 1.5;
}

.input-group input:focus,
.input-group select:focus,
.input-group textarea:focus {
  outline: none;
  border-color: var(--primary);
  /* background-color, not `background` — the shorthand would reset the select's
     chevron background-image the moment it gained focus. */
  background-color: var(--surface);
  box-shadow: 0 0 0 4px var(--focus-ring);
}

.input-group input::placeholder,
.input-group textarea::placeholder {
  color: var(--text-tertiary);
}

/* Select: the chevron has to be drawn by hand once appearance is stripped. */
.input-group select {
  padding-right: 40px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23888888' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  cursor: pointer;
}

/* Colour: the default control renders as a flat full-width bar, which is what
   made this form look unfinished. Sized as a swatch instead — the colour is the
   control, and it stops competing with the fields above it. */
.input-group input[type="color"] {
  width: 76px;
  height: 48px;
  padding: 5px;
  cursor: pointer;
}
.input-group input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.input-group input[type="color"]::-webkit-color-swatch {
  border: none;
  border-radius: calc(var(--radius-md) - 6px);
}
.input-group input[type="color"]::-moz-color-swatch {
  border: none;
  border-radius: calc(var(--radius-md) - 6px);
}

/* Date/time keep the native picker but lose the misaligned platform chrome. */
.input-group input[type="datetime-local"],
.input-group input[type="date"],
.input-group input[type="time"] {
  min-height: 50px;
}
.input-group input[type="number"] {
  -moz-appearance: textfield;
}
.input-group input[type="number"]::-webkit-outer-spin-button,
.input-group input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* A short hint under a field, for the ones that need explaining. */
.input-hint {
  margin-top: 6px;
  font-size: 0.8rem;
  color: var(--text-tertiary);
  line-height: 1.4;
}

/* Buttons */
.btn {
  padding: 13px 22px;
  /* 48px is the smallest comfortable touch target. */
  min-height: 48px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: 16px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--primary);
  color: white;
  box-shadow: 0 2px 8px rgba(0, 137, 123, 0.25);
}

.btn-primary:active:not(:disabled) {
  background: var(--primary-dark);
  transform: scale(0.98);
  box-shadow: none;
}

.btn-secondary {
  background: transparent;
  border-color: var(--field-border);
  color: var(--text-secondary);
}

.btn-secondary:active:not(:disabled) {
  background: var(--field);
  transform: scale(0.98);
}

.btn-block {
  width: 100%;
  display: flex;
}

.btn-icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--primary);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: var(--transition);
}

.btn-icon:active {
  transform: scale(0.9);
}

/* App Header */
.app-header {
  height: var(--header-height);
  padding: 0 16px;
  padding-top: var(--safe-area-inset-top);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
}

.app-header h1 {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-primary);
}

/* Content Scroll */
.content-scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding: 16px;
  /* Add extra space at bottom to prevent navbar overlap */
  padding-bottom: calc(var(--bottom-nav-height) + var(--safe-area-inset-bottom) + 80px);
}

/* Tab Content */
.tab-content {
  display: none;
  flex-direction: column;
  height: 100%;
}

.tab-content.active {
  display: flex;
}

/* Tickets Container */
.tickets-container {
  display: grid;
  gap: 16px;
}

.ticket-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  box-shadow: var(--shadow-md);
  border-left: 4px solid;
  transition: var(--transition);
  position: relative;
  overflow: hidden;
}

.ticket-card.over-limit {
  background: linear-gradient(135deg, rgba(255, 152, 0, 0.05) 0%, var(--surface) 50%);
  border-left-color: #FF9800 !important;
}

/* The card is not tappable — only the button inside it is. :active bubbles from
   a descendant, so pressing "Use Ticket" scaled the whole card, which is what
   made a silently-failing button look like the card had eaten the press. */
.ticket-card:active {
  transform: none;
}

.ticket-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.ticket-name {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--text-primary);
}

.ticket-type-badge {
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  background: var(--background);
  color: var(--text-secondary);
}

.ticket-progress {
  margin: 16px 0;
}

.progress-bar {
  height: 8px;
  background: var(--border);
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 12px;
}

.progress-fill {
  height: 100%;
  transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 4px;
}

.ticket-count {
  font-size: 2rem;
  font-weight: 800;
  color: var(--text-primary);
  display: flex;
  align-items: baseline;
  gap: 4px;
}

.ticket-count .remaining {
  font-size: 2rem;
}

.ticket-count .total {
  font-size: 1.2rem;
  color: var(--text-secondary);
  font-weight: 500;
}

.ticket-actions {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  margin-top: 16px;
}

.btn-use-ticket {
  padding: 14px;
  background: var(--primary);
  color: white;
  border: none;
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: 15px;
  transition: var(--transition);
}

.btn-use-ticket.over-limit {
  background: #FF9800;
  color: white;
}

.btn-use-ticket:disabled {
  background: var(--border);
  color: var(--text-tertiary);
  cursor: not-allowed;
}

.btn-use-ticket:active:not(:disabled) {
  transform: scale(0.96);
}

/* History Timeline */
.history-stats {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: var(--shadow-sm);
}

.history-timeline {
  position: relative;
  padding-left: 24px;
}

.history-timeline::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--border);
}

.history-entry {
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 16px;
  margin-bottom: 16px;
  box-shadow: var(--shadow-sm);
  position: relative;
}

.history-entry::before {
  content: '';
  position: absolute;
  left: -20px;
  top: 20px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--primary);
  border: 3px solid var(--background);
  box-shadow: 0 0 0 2px var(--primary);
}

.history-entry-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.history-entry-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 1.1rem;
}

.history-entry-time {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.history-entry-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0;
}

.tag-badge {
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  background: var(--background);
  color: var(--text-secondary);
}

.history-entry-notes {
  background: var(--background);
  padding: 12px;
  border-radius: var(--radius-sm);
  margin-top: 12px;
  font-size: 0.95rem;
  color: var(--text-primary);
  font-style: italic;
}

/* Groups */
.groups-list {
  display: grid;
  gap: 16px;
}

.group-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  box-shadow: var(--shadow-md);
  transition: var(--transition);
  cursor: pointer;
}

.group-card:active {
  transform: scale(0.98);
}

.group-name {
  font-size: 1.3rem;
  font-weight: 700;
  margin-bottom: 8px;
  color: var(--text-primary);
}

.group-description {
  color: var(--text-secondary);
  margin-bottom: 12px;
  font-size: 0.95rem;
}

.group-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.85rem;
  color: var(--text-tertiary);
}

/* Group Detail */
.group-detail {
  animation: slideInRight 0.3s ease;
}

.group-header-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: var(--shadow-sm);
}

.group-actions-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 16px;
}

.members-list {
  margin-top: 20px;
}

.member-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px;
  background: var(--background);
  border-radius: var(--radius-md);
  margin-bottom: 8px;
}

.member-info {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* ============= AVATARS ============= */

.avatar {
  flex-shrink: 0;
  border-radius: 50%;
  object-fit: cover;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--field);
  /* A picture on a light card and a picture on a dark card both need an edge. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}

.avatar-initial {
  color: #fff;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.01em;
  user-select: none;
}

.role-badge {
  padding: 2px 8px;
  border-radius: 8px;
  font-size: 0.7rem;
  font-weight: 600;
  background: var(--primary);
  color: white;
  margin-left: 8px;
}

/* Posts Feed */
.post-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 16px;
  margin-bottom: 16px;
  box-shadow: var(--shadow-sm);
}

.post-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.post-author {
  font-weight: 600;
  font-size: 1.05rem;
}

.post-time {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.post-content {
  margin: 12px 0;
  line-height: 1.6;
  color: var(--text-primary);
}

.post-actions {
  display: flex;
  gap: 20px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.post-action-btn {
  background: none;
  border: none;
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
  font-size: 0.95rem;
  cursor: pointer;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  transition: var(--transition);
}

.post-action-btn:active {
  background: var(--background);
}

.post-action-btn.liked {
  color: var(--error);
}

/* Events/Plan */
.events-calendar {
  display: grid;
  gap: 16px;
}

.event-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  box-shadow: var(--shadow-md);
  border-left: 4px solid var(--primary);
}

.event-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}

.event-title {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text-primary);
}

.event-date {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 8px 0;
}

.event-tickets {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0;
}

.event-ticket-badge {
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  font-weight: 600;
  color: white;
  display: flex;
  align-items: center;
  gap: 4px;
}

.event-status {
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
}

.status-pending {
  background: var(--warning);
  color: white;
}

.status-accepted {
  background: var(--success);
  color: white;
}

/* Bottom Navigation */
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(var(--bottom-nav-height) + var(--safe-area-inset-bottom));
  padding-bottom: var(--safe-area-inset-bottom);
  background: var(--surface);
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: space-around;
  align-items: center;
  z-index: 1000;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
}

.nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px 4px;
  border: none;
  background: none;
  color: var(--text-secondary);
  cursor: pointer;
  transition: var(--transition);
  font-size: 0.7rem;
  font-weight: 500;
}

.nav-item svg {
  width: 24px;
  height: 24px;
}

.nav-item.active {
  color: var(--primary);
}

.nav-item:active {
  transform: scale(0.95);
}

/* Filter Tabs */
.filter-tabs {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  margin-left: auto;
}

.filter-tabs::-webkit-scrollbar {
  display: none;
}

.filter-btn {
  padding: 6px 16px;
  border-radius: 16px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-weight: 500;
  white-space: nowrap;
  transition: var(--transition);
  cursor: pointer;
}

.filter-btn.active {
  background: var(--primary);
  color: white;
  border-color: var(--primary);
}

.filter-tab {
  padding: 10px 20px;
  border-radius: 20px;
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text-secondary);
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  transition: all 0.3s ease;
  cursor: pointer;
  flex: 1;
  text-align: center;
  min-width: 80px;
}

.filter-tab:hover {
  border-color: var(--primary);
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 137, 123, 0.15);
}

.filter-tab.active {
  background: linear-gradient(135deg, var(--primary) 0%, #00695c 100%);
  color: white;
  border-color: var(--primary);
  box-shadow: 0 4px 12px rgba(0, 137, 123, 0.3);
  transform: translateY(-2px);
}

/* Modal */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 2000;
  display: none;
  animation: fadeIn 0.2s ease;
  /* Don't interfere with touch events on the modal */
  touch-action: none;
}

.modal-backdrop.active {
  display: block;
}

/* The backdrop lightens as the sheet is pulled down, so the gesture reads as
   "this is on its way out" before you have committed to it. */
.modal-backdrop {
  transition: opacity 0.22s ease;
}

/* …except while the finger is down, where it has to track the drag exactly. */
.modal-backdrop.dragging {
  transition: none;
}

/* Coming back, the dim rides the same spring as the sheet — otherwise it is
   fully dark again 100ms before the sheet has finished settling. */
.modal-backdrop.settling {
  transition: opacity 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

.modal-container {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 2001;
  pointer-events: none;
  /* Hugs the sheet. It used to be a hard 90vh, which is what left a short
     modal — "Create Ticket Type" — with 400px of empty white below it. */
  height: auto;
  max-height: 90vh;
}

/* The sheet is the one scroll container.
 *
 * It used to be a 90vh flex column whose *child* was expected to scroll, via a
 * .modal-form class that 9 of the 10 forms never applied. Anything taller than
 * the sheet was simply clipped and unreachable — Settings ran to 2300px inside
 * 760px. Scrolling here instead means no modal can be built wrong. */
.modal {
  background: var(--surface);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  height: auto;
  max-height: 90vh;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
  display: none;
  pointer-events: auto;
  flex-direction: column;
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.14);
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  touch-action: pan-y;
}

.modal.active {
  display: flex;
}

/* The entry animation lives here rather than on `.modal`. A finite animation on
   the base class restarts whenever a rule that set `animation: none` stops
   matching, so removing `.settling` replayed the whole slide-up: the sheet you
   had just decided to keep dropped off-screen and came back. `.entering` is
   added once at open and removed when it has run, so nothing can retrigger it. */
.modal.entering {
  animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Drag-to-dismiss. While the finger is down the sheet follows it with no
   transition at all; only the snap-back and the final slide-out are animated,
   and the entry animation has to be off or it fights the transform. */
.modal.dragging {
  animation: none;
  transition: none;
  will-change: transform;
}

/* Letting go short of the threshold should feel like a spring, not a rewind —
   it is the confirmation that the sheet is staying. */
.modal.settling {
  animation: none;
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Leaving keeps the momentum it was thrown with; the duration is set inline
   from the release velocity, so only the curve lives here. */
.modal.closing {
  animation: none;
  transition: transform 0.26s cubic-bezier(0.32, 0, 0.67, 0);
}

/* Handle and title stay put while the body scrolls under them. The handle is a
   full-width sticky strip carrying the surface colour — a bare 44px pill would
   let the scrolling content show through on either side of it. Every modal in
   the app has both elements, so the header's offset is always right. */
.modal-handle {
  width: 100%;
  height: 22px;
  margin: 0;
  flex-shrink: 0;
  background: var(--surface);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  position: sticky;
  top: 0;
  z-index: 3;
  /* The chrome is the grab area: taking it out of the browser's pan means a
     downward pull there can never be swallowed by a scroll that has already
     begun, which is what makes the dismiss feel immediate rather than laggy. */
  touch-action: none;
  cursor: grab;
}

.modal-handle::after {
  content: '';
  display: block;
  width: 44px;
  height: 5px;
  margin: 9px auto 0;
  background: var(--field-border);
  border-radius: 999px;
}

.modal-header {
  padding: 8px 20px 14px;
  flex-shrink: 0;
  position: sticky;
  top: 22px;
  z-index: 2;
  background: var(--surface);
}

/* Every form in a sheet gets the gutter, whether or not it remembered the
   class — that missing padding is why fields ran edge to edge. */
.modal-form,
.modal > form {
  flex: 0 0 auto;
  padding: 0 20px calc(20px + var(--safe-area-inset-bottom));
}

.modal-title {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.modal-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 24px;
  padding-bottom: 4px;
}

/* Mood Selector */
.mood-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 0;
}

.mood-btn {
  aspect-ratio: 1;
  border-radius: var(--radius-md);
  border: 2px solid var(--border);
  background: var(--surface);
  font-size: 1.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: var(--transition);
  padding: 8px;
}

.mood-btn.selected {
  border-color: var(--primary);
  background: var(--primary-light);
  transform: scale(1.1);
}

.mood-btn:active {
  transform: scale(0.95);
}

/* Toast — anchored to the bottom, above the tab bar and the home indicator,
   because that is where the thumb already is. It used to sit at the top, where
   it fought with the status bar and the screen header. */
.toast-container {
  position: fixed;
  bottom: calc(var(--bottom-nav-height) + var(--safe-area-inset-bottom) + 12px);
  left: 12px;
  right: 12px;
  z-index: 3000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}

.toast {
  background: var(--surface);
  padding: 12px 12px 12px 16px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
  animation: toastIn 0.24s cubic-bezier(0.2, 0.8, 0.3, 1);
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

.toast.leaving {
  animation: toastOut 0.2s ease forwards;
}

.toast-text {
  flex: 1;
  min-width: 0;
  font-size: 0.95rem;
  line-height: 1.35;
}

.toast-undo {
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--primary);
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  min-height: 36px;
}

.toast-undo:active { background: var(--field); }
.toast-undo:disabled { opacity: 0.5; }

.toast-dismiss {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  color: var(--text-tertiary);
  font-size: 1.25rem;
  line-height: 1;
  border-radius: 50%;
}

.toast.info { border-left: 4px solid var(--primary); }

/* The install banner occupies the same band, so toasts move up over it rather
   than landing on top of it. */
body.has-install-prompt .toast-container {
  bottom: calc(var(--bottom-nav-height) + var(--safe-area-inset-bottom) + 88px);
}

.toast.success {
  border-left: 4px solid var(--success);
}

.toast.error {
  border-left: 4px solid var(--error);
}

.toast.warning {
  border-left: 4px solid var(--warning);
}

/* Loading Spinner */
.loading-spinner {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 4000;
}

.spinner {
  width: 48px;
  height: 48px;
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-top-color: white;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* PWA Install Prompt */
.install-prompt {
  position: fixed;
  bottom: calc(var(--bottom-nav-height) + var(--safe-area-inset-bottom) + 16px);
  left: 16px;
  right: 16px;
  z-index: 1500;
  animation: slideUp 0.3s ease;
}

.install-content {
  background: var(--surface);
  padding: 16px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  display: flex;
  align-items: center;
  gap: 12px;
}

.btn-install {
  padding: 8px 16px;
  background: var(--primary);
  color: white;
  border: none;
  border-radius: var(--radius-sm);
  font-weight: 600;
  white-space: nowrap;
}

.btn-dismiss {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--border);
  color: var(--text-primary);
  font-size: 1.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Animations */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* A feed entry that came from a shared ticket used to be distinguishable from a
   written post only by the colour of a 3px bar. */
.post-source {
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}

/* Two actions per member row now, so they wrap instead of squeezing. */
.member-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.member-actions .btn {
  padding: 6px 12px;
  font-size: 0.85rem;
}

/* Deleting a group is irreversible and takes everyone's posts with it, so it
   sits apart from the ordinary controls rather than beside them. */
.danger-zone {
  margin-top: 28px;
  padding: 16px;
  border: 1px solid var(--danger, #e5484d);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--danger, #e5484d) 6%, transparent);
}
.danger-zone h4 { margin-bottom: 6px; font-size: 0.95rem; }
.danger-zone p {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin-bottom: 12px;
}
.btn-danger {
  background: var(--danger, #e5484d);
  color: #fff;
}

.history-end {
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.85rem;
  padding: 16px 0 4px;
}

@keyframes slideUp {
  from {
    transform: translateY(100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes slideInRight {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

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

/* Referenced by the old showToast() and never actually defined, so toasts
   vanished instantly instead of fading. */
@keyframes toastOut {
  from {
    transform: translateY(0);
    opacity: 1;
  }
  to {
    transform: translateY(12px);
    opacity: 0;
  }
}

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

@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

/* Utility Classes */
.text-center { text-align: center; }
.text-secondary { color: var(--text-secondary); }
.mb-1 { margin-bottom: 8px; }
.mb-2 { margin-bottom: 16px; }
.mb-3 { margin-bottom: 24px; }
.mt-2 { margin-top: 16px; }

/* Dark Mode Support - Manual Toggle */
body.dark-theme {
  --primary: #4db6ac;
  --primary-dark: #00897b;
  --primary-light: #80cbc4;
  --accent: #ff8a80;
  --background: #121212;
  --surface: #1e1e1e;
  --text-primary: #ffffff;
  --text-secondary: #b3b3b3;
  --text-tertiary: #808080;
  --border: #2c2c2c;
  --field: #262626;
  --field-border: #383838;
  --focus-ring: rgba(77, 182, 172, 0.22);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
}

/* Dark mode overrides for specific elements */
/* background-color, not the `background` shorthand — the shorthand also resets
   background-image, which silently erased the select's chevron in dark mode. */
body.dark-theme input,
body.dark-theme textarea,
body.dark-theme select {
  background-color: var(--field);
  color: var(--text-primary);
  border-color: var(--field-border);
}

body.dark-theme input::placeholder,
body.dark-theme textarea::placeholder {
  color: var(--text-tertiary);
}

body.dark-theme .btn-secondary {
  background: transparent;
  color: var(--text-secondary);
  border-color: var(--field-border);
}

body.dark-theme .btn-secondary:hover {
  background: var(--border);
}

body.dark-theme .modal {
  background: var(--surface);
  color: var(--text-primary);
}

body.dark-theme strong,
body.dark-theme h1,
body.dark-theme h2,
body.dark-theme h3,
body.dark-theme h4 {
  color: var(--text-primary);
}

/* Landscape Tablets */
@media (min-width: 768px) and (orientation: landscape) {
  .content-scroll {
    padding: 24px;
    max-width: 1200px;
    margin: 0 auto;
  }

  .tickets-container {
    grid-template-columns: repeat(2, 1fr);
  }

  .groups-list {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* iPads and larger tablets */
@media (min-width: 1024px) {
  .tickets-container {
    grid-template-columns: repeat(3, 1fr);
  }

  .groups-list {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ============= NOTIFICATION PREFERENCES ============= */

.pref-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.pref-row:last-child { border-bottom: none; }

.pref-copy { flex: 1; min-width: 0; }

.pref-label {
  font-weight: 500;
  color: var(--text-primary);
  font-size: 0.95rem;
}

.pref-desc {
  font-size: 0.82rem;
  color: var(--text-secondary);
  line-height: 1.4;
  margin-top: 2px;
}

.pref-section-title {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 20px 0 4px;
}
.pref-section-title:first-child { margin-top: 4px; }

/* The switch is a real checkbox underneath, so it stays keyboard-reachable and
   announces its state to a screen reader. */
.pref-switch {
  position: relative;
  flex: 0 0 auto;
  width: 46px;
  height: 28px;
  margin-top: 2px;
}
.pref-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}
.pref-switch .pref-track {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 999px;
  transition: background 0.2s ease;
  pointer-events: none;
}
.pref-switch .pref-track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 22px;
  height: 22px;
  background: #fff;
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s ease;
}
.pref-switch input:checked + .pref-track { background: var(--primary); }
.pref-switch input:checked + .pref-track::after { transform: translateX(18px); }
.pref-switch input:focus-visible + .pref-track {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
.pref-switch input:disabled { cursor: not-allowed; }
.pref-switch input:disabled + .pref-track { opacity: 0.45; }

.pref-note {
  font-size: 0.85rem;
  color: var(--text-secondary);
  line-height: 1.5;
  background: var(--background);
  border-radius: var(--radius-md);
  padding: 12px;
}


/* ============= PROFILE, AVATAR SURFACES, ADMIN ============= */

/* The identity row is the shortest path to the profile picture, so it is a
   button rather than a label — nobody was going to find the upload three
   sections down a scrolling Settings sheet. */
.menu-identity {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  border: none;
  cursor: pointer;
  background: var(--background);
  padding: 16px;
  border-radius: var(--radius-md);
  margin-bottom: 16px;
}

.menu-identity:active {
  background: var(--field);
}

.menu-identity-text { flex: 1; min-width: 0; }

.menu-identity-chevron {
  color: var(--text-secondary);
  font-size: 1.5rem;
  line-height: 1;
}

/* A camera badge is the only thing that says "this picture is yours to set". */
.avatar-badge,
.avatar-edit {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.avatar-badge::after,
.avatar-edit::after {
  content: '📷';
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.avatar-edit {
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.avatar-edit::after {
  width: 26px;
  height: 26px;
  font-size: 0.85rem;
}

.profile-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 14px;
  background: var(--background);
  border-radius: var(--radius-md);
}

.profile-meta { flex: 1; min-width: 0; }

.profile-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.btn-sm {
  padding: 8px 14px;
  font-size: 0.875rem;
  min-height: 36px;
}

.btn-secondary.danger {
  color: var(--error);
  border-color: var(--error);
}

.post-byline,
.participant-byline {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.participant-byline {
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.comment-row {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  background: var(--background);
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  margin-bottom: 8px;
}

/* ---- history ---- */

.history-entry-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.entry-remove {
  border: none;
  background: none;
  color: var(--text-tertiary);
  font-size: 0.95rem;
  width: 32px;
  height: 32px;
  border-radius: 50%;
}

.entry-remove:active {
  background: var(--field);
  color: var(--error);
}

.stat-tile {
  text-align: center;
  padding: 12px 8px;
  background: var(--background);
  border-radius: var(--radius-md);
}

.stat-value {
  font-size: 1.75rem;
  font-weight: 700;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
}

.stat-label {
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.stat-breakdown {
  margin-top: 16px;
  display: grid;
  gap: 8px;
}

.stat-bar-row {
  display: grid;
  grid-template-columns: minmax(0, 6.5rem) 1fr auto;
  align-items: center;
  gap: 10px;
  font-size: 0.875rem;
}

.stat-bar-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-secondary);
}

.stat-bar-track {
  height: 8px;
  background: var(--field);
  border-radius: 4px;
  overflow: hidden;
}

.stat-bar-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
}

.stat-bar-count {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text-primary);
}

/* ---- admin ---- */

.admin-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.admin-row:last-child { border-bottom: none; }

.admin-who {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  flex: 1;
}

.admin-name { font-weight: 600; }

.admin-sub {
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.admin-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--background);
  border-radius: var(--radius-sm);
}

.checkbox-row input[type="checkbox"] {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

.checkbox-row label { margin: 0; font-weight: 500; }

body.dark-theme .avatar {
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}

/* ============= DESKTOP =============
 *
 * Everything above is written for a phone, and on a 1440px window it showed:
 * a bottom sheet 1440px wide with 300px mood squares, a header whose title and
 * + button sat at opposite ends of the screen, cards stretched to the height of
 * the tallest one in their row, and a tab bar spread across the full width.
 *
 * This block does not restructure anything — same markup, same components. It
 * gives the app a content column, turns the sheet into a centred dialog, and
 * floats the tab bar. Below 900px nothing here applies.
 */
@media (min-width: 900px) {
  :root {
    --rail-width: 232px;
  }

  /* One column, and the header lines up with it instead of with the window.
     Padding rather than max-width so the sticky bar still spans full bleed and
     the scrollbar stays at the window edge. */
  .app-header,
  .content-scroll {
    padding-left: max(24px, calc((100% - 1180px) / 2));
    padding-right: max(24px, calc((100% - 1180px) / 2));
    max-width: none;
    margin: 0;
  }

  .app-header {
    height: 68px;
  }

  .content-scroll {
    padding-top: 28px;
    /* The phone rule reserves 60px of tab bar plus 80px of clearance. With the
       rail there is nothing overlapping the bottom of the list. */
    padding-bottom: 48px;
  }

  /* Cards size to their own content. Without align-items the row was as tall
     as its tallest card, so a counted ticket next to an active timed one grew
     200px of empty space. */
  .tickets-container,
  .groups-list,
  .events-calendar {
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    align-items: start;
  }

  .history-timeline,
  .history-stats {
    max-width: 760px;
  }

  /* A pointer can hover; a finger cannot. The whole app only ever had :active. */
  .ticket-card,
  .group-card,
  .event-card {
    transition: box-shadow 0.15s ease, transform 0.15s ease;
  }

  .group-card:hover,
  .event-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.12));
  }

  .btn:hover:not(:disabled) {
    filter: brightness(1.06);
  }

  .nav-item:hover,
  .menu-identity:hover,
  .pref-row:hover {
    background: var(--background);
  }

  /* The tab bar becomes a left rail. A bar pinned to the bottom of a 900px-tall
     window is a long way from the content and floats over the last card; a rail
     is where a pointer already expects navigation, and it frees the full height
     for the list. */
  #app-screen {
    padding-left: var(--rail-width);
  }

  .bottom-nav {
    top: 0;
    bottom: 0;
    left: 0;
    right: auto;
    width: var(--rail-width);
    height: 100%;
    padding: 76px 14px 20px;
    flex-direction: column;
    justify-content: flex-start;
    align-items: stretch;
    gap: 4px;
    border-top: none;
    border-right: 1px solid var(--border);
    box-shadow: none;
  }

  /* The markup has no slot for a wordmark, and the rail looks unfinished
     without one. */
  .bottom-nav::before {
    content: 'escappade';
    position: absolute;
    top: 26px;
    left: 24px;
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--primary);
  }

  .nav-item {
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    gap: 14px;
    padding: 12px 14px;
    border-radius: var(--radius-md);
    font-size: 0.95rem;
    font-weight: 600;
  }

  .nav-item.active {
    background: var(--primary-light);
    color: #fff;
  }

  .nav-item:active {
    transform: none;
  }

  .nav-item-menu {
    margin-top: auto;
  }

  /* The bottom sheet becomes a centred dialog. The handle goes with it: there
     is no drag gesture with a mouse, so the grab cue would be a lie. */
  .modal-container {
    inset: 0;
    height: auto;
    max-height: none;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 24px;
  }

  .modal {
    width: min(560px, 100%);
    max-height: min(84vh, 780px);
    border-radius: var(--radius-xl);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
  }

  .modal.entering {
    animation: dialogIn 0.18s cubic-bezier(0.2, 0, 0.2, 1);
  }

  .modal-handle {
    display: none;
  }

  .modal-header {
    top: 0;
    padding: 22px 60px 14px 24px;
  }

  /* Two classes deep on purpose: the base `display: none` for phones sits at
     the end of this file, so an equal-specificity rule here would lose. */
  .modal .modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 16px;
    right: 16px;
    width: 34px;
    height: 34px;
    border: none;
    border-radius: 50%;
    background: var(--field);
    color: var(--text-secondary);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
  }

  .modal .modal-close:hover {
    background: var(--border);
    color: var(--text-primary);
  }

  .modal-form,
  .modal > form {
    padding: 0 24px 24px;
  }

  /* 4 × 1fr of a 1440px sheet made every mood button a 300px square. */
  .mood-grid {
    gap: 10px;
  }

  .mood-btn {
    aspect-ratio: auto;
    min-height: 64px;
  }

  /* Nothing is anchored to the bottom of the screen any more, so toasts go to
     the corner a pointer expects rather than floating mid-canvas. */
  .toast-container {
    left: auto;
    right: 28px;
    bottom: 28px;
    width: min(420px, calc(100% - 56px));
  }

  body.has-install-prompt .toast-container {
    bottom: 108px;
  }

  .install-prompt {
    left: auto;
    right: 28px;
    bottom: 28px;
    width: min(420px, calc(100% - 56px));
  }
}

@keyframes dialogIn {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

/* The dialog close button belongs to the desktop layout only — on a phone the
   handle, the drag and the backdrop are the way out. */
.modal-close {
  display: none;
}

/* The theme buttons carried their whole appearance in a style attribute, which
   no class rule can beat — so `.active`, which setTheme() has always toggled,
   never showed. Which theme you are on is now visible. */
.theme-btn {
  padding: 9px 16px;
  border: 2px solid var(--field-border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.theme-btn.active {
  border-color: var(--primary);
  background: var(--primary);
  color: #fff;
}

/* ============================================================
   Pull to refresh
   ============================================================ */

/* The browser's own pull-to-refresh reloads the whole PWA; the app's version
   is below. `contain` is what stops the two fighting over the same gesture. */
.content-scroll {
  overscroll-behavior-y: contain;
}

.ptr {
  position: fixed;
  top: calc(var(--safe-area-inset-top) + var(--header-height) - 34px);
  left: 50%;
  z-index: 90;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-md);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 0);
}

/* Only while returning to rest — during the drag it has to track the finger
   with no easing at all. */
.ptr.settling {
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.2s ease;
}

.ptr-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
}

.ptr.armed .ptr-spinner {
  border-color: var(--primary-light);
  border-top-color: var(--primary);
}

.ptr.spinning .ptr-spinner {
  animation: spin 0.7s linear infinite;
}

/* ============================================================
   Quick-log (hold a ticket card)
   ============================================================ */

.quick-log-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  opacity: 0.14;
  pointer-events: none;
  transition: width 0.2s ease;
}

/* The bar fills over exactly the hold duration, so letting go early is
   visibly letting go early rather than a silent nothing. */
.ticket-card.holding .quick-log-fill {
  width: 100%;
  transition: width 0.5s linear;
}

.ticket-card.holding {
  transform: scale(0.985);
  transition: transform 0.5s ease;
}

.ticket-card.quick-fired {
  animation: quick-pop 0.26s ease;
}

@keyframes quick-pop {
  0%   { transform: scale(0.985); }
  45%  { transform: scale(1.015); }
  100% { transform: scale(1); }
}

.quick-log-hint {
  margin-top: 10px;
  text-align: center;
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

/* ============================================================
   Borrowed switch
   ============================================================ */

/* `.input-group label` is (0,1,1) and sets display:block, so a bare .switch-row
   loses to it however late it appears — the track then stayed an inline span
   with no width and the whole switch measured 0x0. */
.switch-row,
.input-group label.switch-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  cursor: pointer;
  margin-bottom: 0;
}

.switch-copy {
  flex: 1;
  display: block;
}

.switch-label {
  display: block;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text-primary);
}

.switch-hint {
  display: block;
  margin-top: 2px;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--text-secondary);
}

/* The checkbox stays in the accessibility tree and keeps working with a
   keyboard; the track beside it is what you actually see. */
.switch-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.switch-track {
  display: block;
  flex: none;
  width: 46px;
  height: 28px;
  border-radius: 14px;
  background: var(--field-border);
  position: relative;
  transition: background 0.2s ease;
  margin-top: 2px;
}

.switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}

.switch-input:checked ~ .switch-track {
  background: var(--primary);
}

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

.switch-input:focus-visible ~ .switch-track {
  box-shadow: 0 0 0 4px var(--focus-ring);
}

.borrowed-from {
  margin-top: 12px;
}

.badge-borrowed {
  background: var(--primary);
  color: #fff;
}

.modal-note {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-secondary);
  margin-bottom: 16px;
}

/* Three choices do not divide into two columns. */
.modal-actions-stacked {
  grid-template-columns: 1fr;
}

.btn-plain {
  background: none;
  border: none;
  color: var(--text-secondary);
}

.entry-edit {
  border: none;
  background: none;
  color: var(--text-tertiary);
  font-size: 0.9rem;
  width: 32px;
  height: 32px;
  border-radius: 50%;
}

.entry-edit:active {
  background: var(--field);
}

.toast-extra {
  border: none;
  background: none;
  color: var(--primary);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 6px 8px;
  border-radius: 8px;
  white-space: nowrap;
  cursor: pointer;
}

.toast-extra:active { background: var(--field); }

/* ============================================================
   Month view
   ============================================================ */

/* Title, view switch and tag filters do not fit one 56px line on a phone. */
#history-tab .app-header {
  height: auto;
  min-height: var(--header-height);
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: 8px;
  padding-top: calc(var(--safe-area-inset-top) + 10px);
  padding-bottom: 10px;
}

.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.segmented {
  display: inline-flex;
  background: var(--field);
  border: 1px solid var(--field-border);
  border-radius: 10px;
  padding: 2px;
}

.segment {
  border: none;
  background: none;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.segment.active {
  background: var(--surface);
  color: var(--primary);
  box-shadow: var(--shadow-sm);
}

#history-tab .filter-tabs {
  margin-left: 0;
}

.month-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.month-label {
  font-size: 1.05rem;
  font-weight: 700;
  text-align: center;
  flex: 1;
}

.month-arrow {
  background: var(--field);
  color: var(--text-primary);
  font-size: 1.4rem;
  line-height: 1;
}

.month-arrow:disabled {
  opacity: 0.35;
  cursor: default;
}

.calendar {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 14px;
  box-shadow: var(--shadow-sm);
  margin-bottom: 16px;
}

.cal-weekdays,
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}

.cal-weekdays {
  margin-bottom: 8px;
}

.cal-weekdays span {
  text-align: center;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
}

.cal-cell {
  position: relative;
  aspect-ratio: 1;
  border: 1px solid var(--field-border);
  border-radius: 10px;
  background: var(--field);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 2px;
  cursor: pointer;
  transition: transform 0.12s ease;
}

.cal-cell.empty {
  border: none;
  background: none;
  cursor: default;
}

.cal-cell.quiet {
  background: transparent;
}

.cal-cell.future {
  opacity: 0.4;
}

.cal-cell:disabled {
  cursor: default;
}

.cal-cell.today {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}

.cal-cell:not(:disabled):active {
  transform: scale(0.94);
}

.cal-day {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-secondary);
  line-height: 1;
}

.cal-units {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.cal-dots {
  display: flex;
  gap: 2px;
  height: 4px;
  align-items: center;
}

.cal-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
}

.month-busiest {
  margin-top: 12px;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.month-note {
  margin-top: 12px;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-tertiary);
}

/* Inside a sheet the timeline has no rail to hang its dots off. */
.day-timeline .history-entry::before {
  display: none;
}

.day-timeline .history-entry:last-child {
  margin-bottom: 0;
}

/* ============================================================
   Wide windows
   ============================================================ */

@media (min-width: 900px) {
  /* There is no touch pull with a mouse, so the indicator would only ever be
     a thing that never appears. */
  .ptr {
    display: none;
  }

  /* Holding is a touch idiom; on desktop the hint would be advice nobody can
     act on comfortably. The gesture itself still works with a mouse. */
  .quick-log-hint {
    display: none;
  }

  #history-tab .app-header {
    flex-direction: row;
    align-items: center;
    height: var(--header-height);
    padding-top: var(--safe-area-inset-top);
    padding-bottom: 0;
    gap: 16px;
  }

  .header-row {
    flex: none;
    gap: 16px;
  }

  /* A 7-column grid stretched across 1180px gives 160px squares; the calendar
     reads better at a size a month actually fits into. */
  .calendar {
    max-width: 520px;
  }
}

/* Borrowing is the only thing that makes this month's number and next month's
   disagree; the card says so rather than leaving it to be discovered on the 1st. */
.ticket-borrow-note {
  margin-top: 8px;
  font-size: 0.78rem;
  color: var(--text-secondary);
}

/* ============================================================
   Ticket emoji, edit form, invite
   ============================================================ */

.ticket-emoji {
  font-size: 1.15em;
  line-height: 1;
}

.label-hint {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-tertiary);
}

.field-note {
  margin-top: 6px;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--text-tertiary);
}

.emoji-current {
  display: flex;
  gap: 8px;
  align-items: center;
}

/* The field itself takes anything; the grid below is only a shortcut. */
.emoji-current input {
  flex: none;
  width: 84px;
  text-align: center;
  font-size: 1.4rem;
}

.emoji-clear {
  padding: 10px 14px;
  font-size: 0.85rem;
}

.emoji-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 6px;
  margin-top: 12px;
  max-height: 168px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 8px;
  background: var(--field);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-md);
}

.emoji-option {
  aspect-ratio: 1;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s ease, transform 0.12s ease;
}

.emoji-option:active { transform: scale(0.88); }

.emoji-option.selected {
  background: var(--surface);
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}

.invite-box {
  background: var(--background);
  padding: 16px;
  border-radius: var(--radius-md);
  margin-top: 16px;
}

.invite-banner {
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--field);
  border: 1px solid var(--field-border);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-secondary);
  text-align: center;
}

@media (min-width: 900px) {
  .emoji-option:hover { background: var(--surface); }
  .emoji-grid { grid-template-columns: repeat(10, 1fr); max-height: 200px; }
}
