/* index.css - LibrePT Modern UI Style Guide & Design System.
   Shared design-system tokens and foundations only: buttons, cards, modals, form controls, resets.
   Each feature's own rules live in its module's same-named .css file. */

/* The five themes and the sandbox marks are NOT imported here. They are the last <link> tags in
   index.html, after every module stylesheet: a theme may restyle any component
   (docs/ARCHITECTURE.md#themes-and-styling), and at equal specificity the later rule wins.

   This file holds only the shared design-system tokens and foundations: buttons, cards, modals,
   form controls, resets. Each feature's own rules live in a same-named .css file next to its
   module. */

/* Design system shared, non-theme tokens (fonts, layout heights, blur) */
:root {
  /* Typography — geometric display headers, humanist body, mono for numbers */
  /* "LibrePT Symbols" sits after the text face and BEFORE the system names: it carries only the
     handful of symbols and emoji the app writes into its own sentences (fonts/fonts.css), so it is
     reached for exactly those characters and never for a letter. Before the system names, because
     the point of it is that the device no longer decides whether ☰ has a shape. */
  --font-sans: "DM Sans", "LibrePT Symbols", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  --font-header: "Outfit", var(--font-sans);
  --font-mono: "JetBrains Mono", "LibrePT Symbols", monospace;

  /* Layout tokens (shared across themes) */
  --app-max-width: 480px;
  /* A FALLBACK, not the truth. applicationHeader.js measures the real bar and writes the number
     here, because five surfaces position themselves below it (the notification area's height, the
     clipboard overlay's top and height, the walkthrough card's offset) and they resolve their
     calc() on the first paint, before any JavaScript has run. This value is what they use for that
     one frame — chosen to match what the bar actually measures, so the correction is invisible. */
  --hdr-height: 56px;
  --nav-height: 70px;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* The fixed bottom notification bar's collapsed height (notificationArea.css owns the actual bar
     and used to redeclare this locally on .notification-area) — needs to live at :root, not
     scoped to the bar's own element, because every other bottom-edge consumer that clears it
     (the sessions view's mirrored sticky day footer, #main-content's bottom padding, the active-
     session overlay's own bottom padding) is a SIBLING of .notification-area, not a descendant, so
     a custom property set there never reached them: they always fell back to each call site's own
     hardcoded `92px` default instead of the bar's real, current height, leaving those elements
     stopping well above where the bar actually sits (visibly above the floating "Create Session"
     button on a phone with no safe-area inset, since 92px sits higher than the button's own
     72px-based offset). */
  --notif-handle-height: calc(56px + var(--safe-bottom));
  /* Extra breathing room #main-content reserves below the notification bar's own height (its
     padding-bottom below is notif-handle-height + safe-bottom + this). sessionsView.css's mirrored
     sticky day footer needs this SAME number back, negated: `position: sticky; bottom` resolves
     against the scrollport's PADDING-BOX edge, which sits this many px above the true scrollport
     edge because of that padding — left uncompensated, the footer sticks this-much-plus-56px above
     the bar instead of flush against it. */
  --main-content-bottom-gap: 24px;
  --glass-blur: blur(16px);
  --text-dark: #09090b;
  /* Temporal session tinting (past = cool/history, future = warm/plan). Neutral fallback only;
     each theme overrides these with palette-tuned values (see src/themes/*.css). */
  --temporal-past: #c084fc;
  --session-dot-0: #f59e0b;
  --session-dot-1: #38bdf8;
  --session-dot-2: #f472b6;
  --session-dot-3: #a3e635;
  --cloud-icon-color: #0284c7;
  --cloud-arrows-color: var(--success, #10b981);
  /* Timer warning animation peak color — computed as color-mix in each theme with theme-specific
     amber blend percentage; fallback is a neutral brownish tone. Each theme defines this after
     --card-bg to ensure proper color-mix evaluation. */
  --timer-warn-peak-color: #c97a2a;
}

/* ==========================================================================
   DRY BASE & SHARED UTILITY GROUPS
   Consolidates layout, glass cards, flex containers, and button foundations.
   ========================================================================== */

/* Shared Glassmorphism Cards & Containers.
   The app header and session/view title bars are deliberately NOT in this list: they are flat
   full-width bars (sharp corners, no shadow, just a bottom divider), not floating cards. */
.glassmorphic,
.exercise-item,
.bottom-nav,
.session-menu,
.client-card,
.client-detail-card,
.chart-box,
.routine-card,
.history-card,
.exercise-deck-card,
.modal-content,
.timer-card,
.history-exercise-log,
.info-block {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: var(--border-radius-lg);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--box-shadow);
}

/* Off-screen rendering optimization: skip layout and painting for out-of-viewport cards */
.client-card,
.routine-card,
.history-card,
.exercise-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 120px;
}

/* Shared Flex Between (Horizontal Row with Space Between) */
.view-header,
.modal-header,
.session-title-block,
.client-card-header,
.history-header,
.routine-header,
.deck-header,
.circuit-header,
.session-header,
.header-container,
.section-title,
.exercise-item-header,
.session-bar-content,
.active-exercise-navigator,
.participant-card-header,
.active-set-row,
.timer-card-head,
.form-row,
.session-title-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Shared Flex Center (Centered Row or Button Icon Layout) */
.btn,
.icon-btn,
.avatar,
.avatar-large,
.nav-item,
.nav-arrow-btn,
.step-btn,
.set-checkbox-btn,
.timer-close,
.deck-action-btn,
.collapse-btn,
.close-btn-xs,
.modal-close-btn,
.btn-remove-row,
.primary-btn,
.secondary-btn,
.danger-btn,
.success-btn,
.active-set-row .set-checkbox-btn,
.active-set-row .step-btn {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Shared Flex Column (Vertical Stack) */
.error-view,
.stack-list,
.profile-info-grid,
.chart-bar-wrapper,
.routine-card,
.exercise-item,
.history-exercise-log,
.active-session-overlay,
.clipboard-body,
.session-overlay-body,
.group-matrix-grid,
.session-actions-footer,
.modal-form,
.client-card,
.client-detail-card,
.circuit-ex-list,
.circuit-ex-row,
.deck-history-sets,
.form-group,
.backup-actions,
.stack-list {
  display: flex;
  flex-direction: column;
}

/* Shared Button & Interactive Foundation */
.btn,
.icon-btn,
.nav-arrow-btn,
.step-btn,
.set-checkbox-btn,
.timer-btn-sm,
.deck-action-btn,
.session-menu-item,
.pill-btn,
.circuit-sig,
.nav-item {
  border: none;
  cursor: pointer;
  transition: all 0.2s ease;
  border-radius: var(--btn-radius);
  font-family: var(--font-sans);
}

/* Shared Muted Text Utilities */
.client-name-meta p,
.profile-meta p,
.info-block label,
.data-table th,
.chart-label,
.routine-title-info p,
.preview-tag,
.exercise-instructions,
.history-header-meta p,
.history-date,
.history-ex-row span,
.participant-header-status,
.deck-history-reps,
.deck-history-note,
.deck-history-meta,
.set-last-meta {
  color: var(--text-muted);
}
/* Reset & Base Setup */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}
body {
  font-family: var(--font-sans);
  background-color: var(--bg-color);
  background-image: var(--bg-gradient);
  background-attachment: fixed;
  background-repeat: no-repeat;
  color: var(--text-main);
  transition: background-color 0.3s ease, color 0.3s ease;
  overflow-x: hidden;
  height: 100vh;
  display: flex;
  flex-direction: column;
  /* Constrain the entire app to phone width on desktop so the layout matches mobile exactly. */
  max-width: var(--app-max-width, 480px);
  margin: 0 auto;
} /* All headings use the geometric display face with tight tracking for a clear hierarchy. */
h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--font-header);
  letter-spacing: -0.015em;
  line-height: 1.15;
} /* Scrollbar customizations */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--border-color);
  border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
} /* Header Styling */
#main-content {
  flex: 1;
  overflow-y: auto;
  max-width: var(--app-max-width, 480px);
  width: 100%;
  margin: 0 auto;
  /* No top padding: each view opens with a flat title bar that sits flush under the app header
     (the bar carries its own padding). Bottom clears the fixed notification area handle bar. */
  padding: 0 16px
    calc(
      var(--notif-handle-height, 92px) +
      var(--safe-bottom) +
      var(--main-content-bottom-gap, 24px)
    )
    16px;
}
/* A view's primary title bar: a flat strip flush under the app header, spanning the full
   content column (breaks out of #main-content's side padding) with a bottom divider and a
   grab handle. Shared look for every view + the active-session clipboard. */
.view-titlebar {
  position: sticky;
  top: 0;
  z-index: 10;
  background-color: var(--card-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  padding: 18px 16px 12px;
  border-bottom: 1px solid var(--card-border);
  margin-bottom: 16px;
}
#main-content .view-titlebar {
  margin-left: -16px;
  margin-right: -16px;
}
/* Grab handle: a centered pill that signals "swipe down to close" and doubles as a tap/click
   target (desktop, where the app-name logo is the other way home). Enlarged hit area via ::before. */
.view-grabber {
  position: absolute;
  top: 6px;
  left: 50%;
  transform: translateX(-50%);
  width: 40px;
  height: 5px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: var(--border-color);
  cursor: grab;
  transition: background 0.2s ease;
}
.view-grabber::before {
  content: "";
  position: absolute;
  inset: -9px -22px;
}
.view-grabber:hover {
  background: var(--text-muted);
}
.view-grabber:active {
  cursor: grabbing;
}
.app-view {
  display: none;
}
.app-view.active {
  display: block;
  animation: viewSwitch 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Each view switch (bottom-nav tab, client detail, back home) plays this: the incoming view
   rises and settles with a subtle scale + fade, so navigation reads as a deliberate transition. */
@keyframes viewSwitch {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Users who prefer reduced motion get the destination immediately, no slide. */
@media (prefers-reduced-motion: reduce) {
  .app-view.active {
    animation-duration: 0.001s;
  }
}

/* Not-found (404) view — centred in the content area, below the omnipresent header. */
.error-view {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 48px 16px;
  max-width: 480px;
  margin: 0 auto;
}
.error-view-icon {
  font-size: 44px;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.error-view-path {
  margin: 0;
  word-break: break-all;
}
.error-view-path code {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--primary);
  background: rgba(0, 0, 0, 0.3);
  padding: 4px 8px;
  border-radius: 6px;
}
.error-view .btn {
  margin-top: 8px;
}
.view-header {
  margin-bottom: 16px;
}
/* A row of secondary actions under a view's title, for the ones that do not fit beside the title on
   a phone (the client directory's review of sent details, the routines' programme import). Right-
   aligned with the buttons above, and wrapping rather than pushing the page wider. */
.view-subactions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  margin: -8px 0 12px;
}
/* Single source of truth for view-title type — every view reads as one system: the view headers,
   the client-detail back header, the home "Sessions" label, the active-session title, and the 404
   title all share this one definition. `#error-view-title` (id) beats the base heading rule.
   Change title size/weight/face here and nowhere else. */
.view-header h2,
.view-header-back h2,
.view-title-label,
.session-title-block h3,
#error-view-title {
  font-family: var(--font-header);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.5px;
}
.view-title-label {
  flex: 0 0 auto;
}
.view-header-back {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.view-header-back h2 {
  flex: 1;
}
.view-desc {
  font-size: 14px;
  color: var(--text-muted);
  margin-top: -8px;
  margin-bottom: 20px;
} /* Button UI Components */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: var(--font-sans);
  font-weight: 600;
  border-radius: var(--btn-radius);
  border: none;
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  padding: 10px 16px;
  font-size: 14px;
  user-select: none;
}
.btn-sm {
  padding: 8px 12px;
  font-size: 13px;
}
.btn-xs {
  padding: 6px 10px;
  font-size: 12px;
  border-radius: 6px;
}
.primary-btn {
  background: var(--primary-gradient);
  color: var(--on-primary);
  box-shadow: 0 6px 16px -9px rgba(0, 0, 0, 0.55);
}
.primary-btn:hover {
  filter: brightness(1.07);
}
.secondary-btn {
  background-color: var(--primary-light);
  color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 32%, transparent);
}
.secondary-btn:hover {
  background-color: color-mix(in srgb, var(--primary) 22%, transparent);
}
.danger-btn {
  background-color: var(--danger-light);
  color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 32%, transparent);
}
.danger-btn:hover {
  background-color: color-mix(in srgb, var(--danger) 24%, transparent);
}
/* The question dialog keeps the line breaks of its text: the session's name is its own line. */
#app-question-text {
  white-space: pre-line;
}
/* Slide to confirm: a native range, so arrow keys, End and screen readers work without code. The
   filled part follows --slide-fill (set by appQuestion.js). Track and thumb are both 48px tall, the
   thumb is wide enough to hold with a thumb, and touch-action stops the page scrolling under it. */
.slide-confirm {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
}
.slide-confirm[hidden] {
  display: none;
}
.slide-confirm-label {
  color: var(--danger);
  font-weight: 600;
  font-size: 0.9rem;
}
.slide-confirm-range {
  --slide-fill: 0%;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 48px;
  margin: 0;
  padding: 0;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  background: linear-gradient(
    to right,
    color-mix(in srgb, var(--danger) 35%, transparent) var(--slide-fill),
    var(--danger-light) var(--slide-fill)
  );
  touch-action: none;
  cursor: grab;
}
.slide-confirm-range:focus-visible {
  outline: 2px solid var(--danger);
  outline-offset: 2px;
}
.slide-confirm-range::-webkit-slider-runnable-track {
  height: 46px;
  background: transparent;
}
.slide-confirm-range::-moz-range-track {
  height: 46px;
  background: transparent;
}
.slide-confirm-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 64px;
  height: 46px;
  border-radius: 999px;
  border: 0;
  background: var(--danger);
}
.slide-confirm-range::-moz-range-thumb {
  width: 64px;
  height: 46px;
  border-radius: 999px;
  border: 0;
  background: var(--danger);
}
/* A destructive action that shares a row with the ordinary ones — it must read as available but
   never compete for a thumb aiming at the button beside it, so it drops the filled surface and
   takes the far end of the row instead. Full button padding is kept: on a phone this is still a
   real touch target, not a hover-sized link.

   **It keeps a visible OUTLINE, though.** The border was transparent until 2026-08-18, and what
   that produced was reported directly: "Didn't happen" did not look like a button. Dropping the
   fill is what makes it recessive; dropping the edge as well makes it a red caption, and a trainer
   who does not recognise it as a control never reaches the third answer the dialog offers. Outline
   over fill is the whole distinction — it is still visibly quieter than the filled pair beside it. */
.danger-link-btn {
  background: none;
  color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
}
.danger-link-btn:hover {
  background-color: var(--danger-light);
}
.modal-actions .danger-link-btn {
  margin-right: auto;
} /* Success (e.g. "Complete Workout") stays semantically green regardless of theme primary. */
.success-btn {
  background-color: var(--success);
  color: var(--text-dark);
  box-shadow: 0 6px 16px -9px rgba(0, 0, 0, 0.5);
}
.success-btn:hover {
  filter: brightness(1.06);
}
.icon-btn {
  background: transparent;
  border: 1px solid var(--border-color);
  color: var(--text-main);
  width: 38px;
  height: 38px;
  border-radius: 50%;
}
.icon-btn:hover {
  background: var(--border-color);
}
/* A small round button drawn small, answering a thumb-sized square. The menu of a live session is
   one of these, tapped mid-set; drawn at 38px, it was only as big as it was drawn. The same for the
   32px rest timers on the clipboard (exerciseDeckOfCards.css, circuitCard.css). */
.icon-btn,
.deck-card-timer,
.circuit-break-play {
  position: relative;
}
.icon-btn::after,
.deck-card-timer::after,
.circuit-break-play::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
} /* Theme-aware Badge & Text Utility Classes */
.badge {
  display: inline-flex;
  align-items: center;
  border-radius: 9999px;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.2;
}
.badge-primary,
.badge-cyan {
  background: var(--primary-light);
  color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
}
.badge-success,
.badge-emerald {
  background: rgba(16, 185, 129, 0.15);
  color: var(--success);
  border: 1px solid rgba(16, 185, 129, 0.35);
}
.badge-danger {
  background: var(--danger-light);
  color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
}
/* A client's name plus its injury mark — rendered by getClientDisplayNameHTML (common/utils.js) and
   used across clients, session editing and the clipboard, not owned by any one module's stylesheet. */
.client-name-with-injury {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.client-name-injury-mark {
  font-size: 11px;
  color: var(--danger);
}
.badge-warning {
  background: rgba(245, 158, 11, 0.15);
  color: #f59e0b;
  border: 1px solid rgba(245, 158, 11, 0.35);
}
/* Double-booking readout under the session setup form's time fields. The two states are
   deliberately different colours rather than one styled list: a clash is a warning about something
   impossible, while the merged-clipboard line is confirmation that the app understood two
   simultaneous sessions as one. Rendering them alike would teach the trainer to skim past both. */
.setup-conflict-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
/* Kept light: this readout sits between the time fields and the participant picker, and a stack of
   full-width tinted blocks pushed the picker off a phone screen. A coloured bar down the left says
   which of the two states a line is in without painting the whole row. */
.setup-conflict-list li {
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 12px;
  line-height: 1.3;
  border-left: 3px solid transparent;
}
.schedule-clash {
  background: var(--danger-light);
  color: var(--danger);
  border-left-color: var(--danger);
}
.schedule-note {
  color: var(--text-muted);
  border-left-color: color-mix(in srgb, var(--primary) 60%, transparent);
}

.text-primary,
.text-cyan {
  color: var(--primary) !important;
} /* Cloud + recycle composite for the merged Sync & Backup button: a cloud with small sync
   arrows overlaid on its body, so one control reads as both "cloud backup" and "sync". */
.back-btn {
  background: transparent;
  border: none;
  color: var(--primary);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
}
.w-full {
  width: 100%;
}
.flex-1 {
  flex: 1;
}
.ml-1 {
  margin-left: 4px;
}
.text-emerald {
  color: var(--primary) !important;
}
.text-indigo {
  color: var(--secondary) !important;
}
.text-sm {
  font-size: 14px !important;
} /* Cards & Glassmorphism styling */
.card {
  background-color: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: var(--border-radius-lg);
  padding: 16px;
  box-shadow: var(--box-shadow);
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.glassmorphic {
  -webkit-backdrop-filter: var(--glass-blur);
} /* Search Bars & Inputs */
.search-bar-container {
  position: relative;
  margin-bottom: 16px;
}
.search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: 14px;
}
.search-input {
  width: 100%;
  padding: 12px 12px 12px 38px;
  background-color: var(--input-bg);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  color: var(--text-main);
  font-family: var(--font-sans);
  font-size: 14px;
  outline: none;
  transition: border-color 0.2s ease;
}
.search-input:focus {
  border-color: var(--primary);
} /* Chips Filter Container */
.filter-chips {
  display: flex;
  /* Wrap the category filters to multiple rows so every one stays visible on a narrow phone. A single
     overflow-x row hid the trailing chips off-screen with no scrollbar affordance (they "ran out of
     view"); the reusable .picker-chips already wraps, so this keeps the two consistent. */
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: 8px;
  margin-bottom: 16px;
}
.chip {
  background: var(--input-bg);
  border: 1px solid var(--border-color);
  color: var(--text-muted);
  padding: 6px 14px;
  border-radius: 20px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.2s ease;
}
.chip.active {
  background: var(--primary-light);
  color: var(--primary);
  border-color: rgba(16, 185, 129, 0.4);
} /* Lists styling (Grid vs Stack) */
.grid-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

@media (min-width: 480px) {
  .grid-list {
    grid-template-columns: 1fr 1fr;
  }
}
.stack-list {
  gap: 12px;
} /* Client Card Components */
.section-title {
  margin-top: 24px;
  margin-bottom: 12px;
}
.section-title h3 {
  font-family: var(--font-header);
  font-size: 16px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
} /* Table Design */
.dialog-modal {
  border: 1px solid var(--card-border);
  background-color: var(--card-bg);
  border-radius: var(--border-radius-lg);
  --dialog-padding: 20px;
  padding: var(--dialog-padding);
  margin: auto;
  max-width: 440px;
  width: 90%;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
  color: var(--text-main);
}
.dialog-modal::backdrop {
  background: rgba(9, 9, 11, 0.7);
  backdrop-filter: blur(8px);
}
.wide-modal {
  max-width: var(--app-max-width, 480px);
}
.modal-header {
  margin-bottom: 16px;
}
/* The glyph stays small in the corner; the target around it is a thumb. It measured 12 × 16, the
   smallest target in every dialog and the one a trainer leaves it by. The negative margin keeps the
   header as tall as it was. */
.modal-close-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 16px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: -12px -14px -12px 0;
}
.modal-form {
  gap: 14px;
}
.form-group {
  gap: 6px;
}
.form-row {
  gap: 12px;
}
.form-group.col {
  flex: 1;
}
.form-group label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}
/* What is wrong with one field, said under that field. Colour is not the message — the sentence is
   — so this is only the emphasis on text that already says what to do. */
.form-error {
  color: var(--danger);
  font-weight: 600;
}
.form-control {
  background-color: var(--input-bg);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-sm);
  color: var(--text-main);
  padding: 10px 12px;
  font-family: var(--font-sans);
  font-size: 14px;
  outline: none;
  transition: border-color 0.2s ease;
}
.form-control:focus {
  border-color: var(--primary);
}
/* A field the app refused to save, and the word it refused it for. The border says WHICH
   field without reading, the word says WHICH part of what was typed — the sentence under the field
   still carries the reason, because a red edge on its own explains nothing. */
.form-control.is-invalid,
.form-control.is-invalid:focus {
  border-color: var(--danger);
}
.form-error-word {
  font-weight: 800;
  text-decoration: underline;
}
textarea.form-control {
  resize: vertical;
} /* Compact Workout Setup Modal Styling */
/* RULE for every dialog: with two or more buttons in this row, the primary (or destructive) action is
   LAST, so it sits on the RIGHT, and Cancel/Close/Back is first, on the left. Write it in that order in
   the markup and do not reorder it here (no row-reverse, no order): DOM, tab and visual order must be
   one. A red danger-link-btn may sit apart at the far left. tests/unit/test_dialog_action_order.py
   fails the build on a row that breaks this. */
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 10px;
  border-top: 1px solid var(--border-color);
  padding-top: 14px;
}
/* A dialog longer than the screen scrolls, and its action row stays at the bottom of it: the client
   form opened with Save 133 pixels below a 390×844 screen, 387 below a 320×680 one, and the one way
   to keep an edit was a scroll away. The row carries the dialog's own background and covers the
   dialog's bottom padding, so what scrolls under it does not show through. */
.dialog-modal .modal-actions {
  position: sticky;
  bottom: calc(-1 * var(--dialog-padding));
  margin-bottom: calc(-1 * var(--dialog-padding));
  padding-bottom: var(--dialog-padding);
  background-color: var(--card-bg);
  z-index: 1;
} /* The app versions a trainer can choose (appVersionDialog.js): one full-width choice per line */
.app-version-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.app-version-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  width: 100%;
  text-align: left;
} /* Routine builder items */
.hidden {
  display: none !important;
}
/* The `hidden` attribute hides, whatever else sets `display`. Without this, `.btn`'s inline-flex beat
   it: every one-answer message showed an empty second button, and a Save meant to be withdrawn
   stayed on the screen. Ten components had each grown their own `[hidden]` rule for the same
   reason. */
[hidden] {
  display: none !important;
} /* History Feedback Icon and Tooltip CSS */
@media (max-width: 600px) {
  /* Scale buttons for thumb-friendly interaction */
  .btn,
  .btn-sm {
    padding: 12px 18px !important;
    font-size: 14px !important;
    min-height: 44px !important;
  } /* Increase margins and size of feedback/note icons in global history to prevent misclicks */
  .history-feedback-icon {
    font-size: 20px !important;
    padding: 10px !important;
    /* Extremely easy to click/tap */
    margin-left: 14px !important;
  } /* Form input controls spacing */
  .form-control,
  select {
    min-height: 44px !important;
    font-size: 14px !important;
  }
}

/* --- VOICE MEMO WAVEFORM ANIMATION --- */
.integrity-error-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(3px);
}
.integrity-error-card {
  max-width: 440px;
  width: 100%;
  text-align: center;
  padding: 32px 28px;
  border-radius: var(--border-radius-lg, 16px);
  background: var(--card-bg);
  border: 1px solid var(--card-border, var(--border-color, #e2e2e2));
  border-top: 4px solid var(--danger, #dc2626);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.4);
  color: var(--text-main);
}
.integrity-error-icon {
  font-size: 40px;
  color: var(--danger, #dc2626);
  margin-bottom: 12px;
}
.integrity-error-card h2 {
  margin: 0 0 10px;
  font-family: var(--font-header, sans-serif);
}
.integrity-error-card p {
  margin: 0 0 16px;
  line-height: 1.5;
  color: var(--text-muted, #555);
}
.integrity-error-detail {
  font-family: var(--font-mono, monospace);
  font-size: 0.8rem;
  word-break: break-word;
  color: var(--text-muted, #777) !important;
}
.integrity-error-detail:empty {
  display: none;
}
