/* modules/common/applicationHeader.css — the top app header: logo, build stamp, preview
   ribbon, sync/backup button, and the ☰ overflow menu. Extracted from index.css so this
   feature's styling lives next to applicationHeader.js, which already owns its markup and
   behavior (TODO index.html/index.css single-responsibility pass). */

.app-header {
  /* No height of its own: the bar is as tall as its content plus this padding, and
     applicationHeader.js MEASURES the result into --hdr-height for the surfaces that sit below it.
     It used to declare `min-height: var(--hdr-height)`, which made the height a promise the bar had
     to keep — and one it stopped keeping silently whenever its content changed. */
  padding: 6px 0;
  background: var(--card-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  /* Flat full-width bar: sharp corners, no shadow, only a bottom divider (not a card) */
  border-radius: 0;
  border-bottom: 1px solid var(--card-border);
  position: sticky;
  top: 0;
  /* Above the fullscreen active-session overlay (z-index 120), which shares this stacking
     context — otherwise the overlay paints over the ☰ dropdown that drops down into it. */
  z-index: 125;
}
/* The sandbox turns this bar orange. That rule lives with the sandbox's other two marks, in
   modules/common/sandboxMarker.css — the header is one of three places wearing one colour, and the
   colour has one home. */
.header-nav {
  max-width: var(--app-max-width, 480px);
  margin: 0 auto;
  padding: 4px 16px 10px 16px;
  display: flex;
  gap: 8px;
  justify-content: flex-start;
  overflow-x: auto;
  scrollbar-width: none;
}
.header-nav::-webkit-scrollbar {
  display: none;
}
body:has(#active-session-overlay:not(.hidden)) .header-nav {
  display: none;
}
.header-nav .nav-item {
  background: transparent;
  color: var(--text-muted);
  border: none;
  border-radius: 999px;
  padding: 6px 14px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
}
.header-nav .nav-item i {
  font-size: 14px;
}
.header-nav .nav-item span {
  font-size: 13px;
  font-weight: 600;
}
.header-nav .nav-item:hover {
  background: var(--card-bg);
  color: var(--text-main);
}
.header-nav .nav-item.active {
  background: color-mix(in srgb, var(--primary) 15%, transparent);
  color: var(--primary);
  font-weight: 700;
}
.header-nav .nav-item.active i {
  filter: drop-shadow(0 0 4px rgba(16, 185, 129, 0.3));
}
.header-container {
  max-width: var(--app-max-width, 480px);
  margin: 0 auto;
  padding: 0 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.logo-area {
  height: 44px;
  display: flex;
  align-items: center;
  gap: 8px;
  /* The whole cluster is the way home, and since 2026-08-18 it is a real anchor rather than a div
     with a click handler — so it inherits the app's text colour and drops the underline, while
     keeping the pointer cursor that was the only hint it navigated at all. */
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  /* Allow the cluster to shrink on narrow phones so only the wordmark truncates — the build stack
     and action buttons keep their size (see .header-build-stack / .header-actions). */
  min-width: 0;
}
/* The two build markers, stacked: PREVIEW pill over the version stamp, both centred on the same
   vertical axis so the narrower stamp reads as belonging to the pill above it. */
.header-build-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  flex-shrink: 0;
}
/* Small, muted, monospace build stamp. It is a BUTTON (tap opens the build-info dialog): the
   vertical padding buys a usable touch target on a phone without changing how small the text
   reads, since 9px of text is nothing to aim at with a gym-floor thumb. */
.app-version {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 6px 4px;
  font-family: var(--font-mono, monospace);
  font-size: 9px;
  font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 0.3px;
  line-height: 1;
}
/* The mark is an <img> (the full clipboard-and-whistle icon), not a font glyph — so it is sized in
   px rather than font-size, and `color` no longer applies. The emerald glow is kept: it is what
   ties the header mark to the --primary accent the rest of the chrome uses. The glow needs the
   icon's transparent surround to trace — see agent_tools/icon_render.py's HEADER_MARK for why this
   is the full mark and not the favicon's whistle crop. */
.logo-icon {
  width: 34px;
  height: 34px;
  display: block;
  filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.4));
}
/* Screen-reader-only text: kept in the DOM for accessibility/name computation but removed from
   layout, so it can't affect the visible size of whatever it's inside (the PREVIEW badge below). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* Pre-release PREVIEW marker (see index.html). Amber is a warning cue, so it stays
   theme-independent by design — a warning that changed colour per theme would read as decoration.
   A pill in the header's flex flow that spells the word out: an icon-only version of this was
   tried, and a lone pulsing triangle is an unexplained warning (see the comment in
   applicationHeader.js).
   The padding is sized for a thumb, not for the 9px text — the text is small, but the tap target
   must not be. It never shrinks, so the wordmark ellipsizes first rather
   than the warning being squeezed away. */
.preview-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  padding: 5px 9px;
  border-radius: 999px;
  background: #f59e0b;
  color: #1a1206;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.6px;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.5), 0 2px 6px rgba(245, 158, 11, 0.4);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.preview-badge-label {
  white-space: nowrap;
}
/* The same pill, naming the demo instead. A demo is a FACT, not a hazard — so it drops
   the amber and the pulse (the animation block below is scoped away from it) and takes a calm
   informational blue. Same size, same word-first treatment, same link to the data-loss notice: the
   build is still a preview, only the more urgent of the two claims has changed. */
.preview-badge.is-demo {
  background: #6366f1;
  color: #f5f3ff;
  box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.5), 0 2px 6px rgba(99, 102, 241, 0.4);
}
.preview-badge.is-demo:hover,
.preview-badge.is-demo:focus-visible {
  box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.9), 0 3px 10px rgba(99, 102, 241, 0.6);
}
/* The unbacked-data warning. Sits beside the PREVIEW badge and must not be mistaken for it:
   PREVIEW is solid amber and permanent, so `due` here is OUTLINED — present, legible, clearly a
   different kind of statement — and only `urgent` fills in, which happens solely when the browser
   reports this origin's storage as evictable. Escalation is carried by weight and colour; there is
   deliberately no animation, because a permanent pulse in a fixed header is ignored within a day and
   would devalue the data-loss badge next to it. */
.unbacked-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  /* Not the 9px the badges beside it use: this is a warning about losing every client record, and
     9px of text is unaimable with a thumb. It is also a real touch target at this padding. */
  padding: 5px 9px;
  border: 1.5px solid var(--danger, #ef4444);
  border-radius: 999px;
  background: transparent;
  color: var(--danger, #ef4444);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.4px;
  line-height: 1;
  cursor: pointer;
}
.unbacked-badge.hidden {
  display: none;
}
.unbacked-badge-label {
  white-space: nowrap;
}
.unbacked-badge-urgent {
  border-color: var(--danger, #ef4444);
  background: var(--danger, #ef4444);
  color: #fff;
}
.unbacked-badge:hover,
.unbacked-badge:focus-visible {
  transform: scale(1.08);
}
.preview-badge:hover,
.preview-badge:focus-visible {
  transform: scale(1.15);
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.9), 0 3px 10px rgba(245, 158, 11, 0.6);
  outline: none;
}
.preview-badge .fa-triangle-exclamation,
.preview-badge .fa-flask {
  font-size: 8px;
}
/* A gentle but clearly-visible pulse (opacity + glow), never a flash — and only for viewers who
   haven't asked to reduce motion, who keep a steady pill instead. */
@media (prefers-reduced-motion: no-preference) {
  .preview-badge:not(.is-demo) {
    animation: previewPulse 1.8s ease-in-out infinite;
  }
  @keyframes previewPulse {
    0%,
    100% {
      opacity: 1;
      box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.5), 0 2px 8px rgba(245, 158, 11, 0.3);
    }
    50% {
      opacity: 0.55;
      box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.85), 0 2px 16px rgba(245, 158, 11, 0.7);
    }
  }
}
/* Phones: the logo cluster and the build stack each tighten a step — icon, wordmark and the
   PREVIEW pill — so the warning keeps its full text on a narrow screen instead of the wordmark
   losing more characters to it. */
@media (max-width: 600px) {
  .header-container {
    padding: 0 10px;
  }
  .logo-area {
    gap: 6px;
  }
  .logo-icon {
    width: 29px;
    height: 29px;
  }
  .logo-area h1 {
    font-size: 19px;
  }
  .preview-badge {
    padding: 4px 7px;
    font-size: 8px;
    letter-spacing: 0.4px;
  }
}
/* While the backup warning shows, the build markers drop to a second row and the wordmark stays
   whole. At every width, not only on the narrowest phones: the bar is at most 480 px wide, and the
   warning beside the logo and the buttons left "LibrePT" 14 px of its 94 at 390 px. Measured in
   tests/medium/test_narrow_header.py. */
.header-container:has(.unbacked-badge:not(.hidden)) {
  flex-wrap: wrap;
  row-gap: 2px;
}
.header-container:has(.unbacked-badge:not(.hidden)) .logo-area {
  flex-shrink: 0;
}
.header-build-stack:has(.unbacked-badge:not(.hidden)) {
  order: 3;
  flex: 1 0 100%;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 6px;
  padding-bottom: 4px;
}
/* The narrowest phones (320 px): the bar stays ONE row, and the backup button and the menu stay
   beside the logo. Wrapping them under it was reported from a Galaxy S23 in the sandbox, where the
   SANDBOX pill and the sync counters ("↑0 ↓?") are on the bar. Every part tightens one step so the
   wordmark stays whole at 320 px in every language; on anything narrower (a phone with its display
   zoom raised) the wordmark truncates rather than the row breaking — the icon still carries the
   brand. The backup warning keeps its own second row (the rule above). Measured in
   tests/medium/test_narrow_header.py. */
@media (max-width: 360px) {
  .header-container {
    padding: 0 8px;
  }
  .logo-area {
    gap: 4px;
  }
  .logo-icon {
    width: 26px;
    height: 26px;
  }
  .logo-area h1 {
    font-size: 14px;
  }
  .preview-badge {
    padding: 4px 5px;
  }
  .header-actions {
    gap: 4px;
  }
}
.logo-area h1 {
  font-family: var(--font-header);
  font-size: 26px;
  font-weight: 800;
  line-height: 1;
  margin: 0;
  letter-spacing: -0.5px;
  /* Truncates instead of forcing header overflow when space is tight (icon still carries the brand). */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.header-actions,
.normal-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Never shrink the controls; the wordmark yields first. */
  flex-shrink: 0;
}
.header-actions .icon-btn {
  width: 44px;
  height: 44px;
  font-size: 18px;
} /* Language + theme selectors live inside the ☰ menu (identical layout on desktop and mobile), so
   the header bar stays compact — just the logo, the sync pill and the menu button. They appear as
   labeled rows at the top of the dropdown; the selects follow the active theme like every input. */
.menu-control-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 14px;
}
.menu-control-label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-main);
  white-space: nowrap;
}
.menu-control-label i {
  width: 16px;
  text-align: center;
  color: var(--text-muted);
}
/* select.menu-select (element+class) outspecifies the later .form-control rule, whose
   10px vertical padding would otherwise combine with the fixed height and clip the text. */
select.menu-select {
  background: var(--input-bg);
  color: var(--text-main);
  border: 1px solid var(--border-color);
  border-radius: var(--btn-radius);
  height: 34px;
  line-height: 32px;
  padding: 0 10px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  flex: 0 0 auto;
  min-width: 104px;
}
.menu-divider {
  height: 1px;
  background: var(--border-color);
  margin: 6px 4px;
}
/* Narrow phones: only the logo, sync pill and menu button remain in the bar; keep the icon
   buttons a comfortable 40px. */
@media (max-width: 480px) {
  .header-actions .icon-btn {
    width: 40px;
    height: 40px;
    font-size: 16px;
  }
}
/* Main View Controller styling */
.cloud-sync-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.cloud-sync-icon .fa-cloud {
  font-size: 18px;
  color: var(--cloud-icon-color, #0284c7);
}
/* One rule for whichever overlay glyph the state calls for — arrows, warning triangle
   or slash all sit in the same place over the cloud, so only the colour changes between states. */
.cloud-sync-icon .fa-arrows-rotate,
.cloud-sync-icon .fa-triangle-exclamation,
.cloud-sync-icon .fa-slash {
  position: absolute;
  left: 50%;
  top: 54%;
  transform: translate(-50%, -50%);
  font-size: 10px;
  color: var(--cloud-arrows-color, var(--success, #10b981));
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.45));
}
/* A failed sync is a genuine fault and earns warning colour — the treatment deliberately withheld
   from "merely not connected" below. */
.cloud-sync-icon.is-failed .fa-triangle-exclamation {
  color: var(--danger, #ef4444);
}
/* Not connected reads as UNHEALTHY, which reverses the earlier treatment on the
   maintainer's ruling. It used to be muted grey, reasoning that PRIVACY.md makes local-first a
   supported choice and painting it as a fault would spend the warning vocabulary. What that
   produced was a line nobody read as a problem — reported in exactly those words. Declining cloud
   sync is still no moral failing, but it does mean every client record lives in one evictable
   place, and that is a state worth seeing across a gym.

   Warning amber, not the danger red a genuine sync FAILURE gets above: the ladder still has two
   rungs, and "you have not connected" is not "the thing you asked for did not happen". The cloud
   keeps its own colour so the slash is what carries the state; the slash is drawn larger than the
   other overlays because it has to read as crossing the whole cloud rather than sitting on it. */
.cloud-sync-icon.is-disconnected .fa-slash {
  color: var(--warning, #f59e0b);
  font-size: 16px;
  top: 50%;
}
/* Everything AROUND the slash goes quiet (wanted 2026-08-18): the cloud body, and the ahead/behind
   counters beside it. The counters are the reason — "3 up, 0 down" in green and red states a
   relationship with a cloud that does not exist, and while the numbers are true the colour promises
   a sync that cannot happen. Grey says "facts about something you have not set up", which is what
   they are.

   The slash above keeps its warning colour deliberately: it is the one part reporting
   a state worth acting on. Greying the cloud around it also restores what an earlier version
   always had and a later change dropped by accident — a bright cloud under a grey slash reads
   as connected. */
.cloud-sync-icon.is-disconnected .fa-cloud,
.sync-badge.is-disconnected .sync-ahead,
.sync-badge.is-disconnected .sync-behind {
  color: var(--text-muted);
} /* When there are pending changes the cloud button grows into a pill to seat the ahead/behind
   counters beside the icon; fully synced, the badge hides and it reads as a round icon again. */
.sync-backup-btn {
  width: auto;
  min-width: 44px;
  height: 44px;
  border-radius: 999px;
  padding: 0 10px;
  gap: 2px;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
/* `.header-actions .icon-btn` pins a fixed 44/40px width that beats the width:auto above, so the
   button stayed a cramped circle and clipped the ahead/behind counts. This higher-specificity
   override lets it grow into a pill that seats the cloud + both counters. */
.header-actions .icon-btn.sync-backup-btn {
  width: auto;
}
/* Fully synced (badge hidden): drop the side padding and restore single icon centering. */
.sync-backup-btn:has(.sync-badge.hidden) {
  padding-left: 0;
  padding-right: 0;
}
/* 12px, not the 10px this shipped with. the canonical example of what not to do is 9px of
   text, which is nothing to aim at with a sweaty thumb, and the counters were 10px with 9px arrows — the
   anti-pattern, in the surface a trainer checks to know whether their work is safe. Raised for EVERY
   viewport rather than only desktop: a phone held at arm's length mid-session is the harder read,
   not the easier one. */
.sync-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  margin-top: -2px;
}
.sync-badge.hidden {
  display: none;
}
.sync-badge .sync-ahead,
.sync-badge .sync-behind,
.sync-badge .sync-zero {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.sync-badge .sync-ahead {
  color: var(--success, #10b981);
} /* local edits to push (ahead, green) */
.sync-badge .sync-behind {
  color: #ef4444;
} /* remote changes to pull (behind, red) */
.sync-badge .sync-zero {
  color: var(--text-muted);
} /* neutral state when counter is 0 */
.sync-badge .sync-offline {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: #f59e0b;
} /* offline cached code (amber warning) */
.sync-badge i {
  font-size: 11px;
}

/* Desktop keeps the LARGER COUNTERS and drops the taller bar (2026-09-12).

   It used to take 76px here, on the reasoning that the app body is a 480px centred column at every
   width so the vertical axis was the only one left to spend. That was backwards: a desktop screen is
   short and wide, so height is the scarce axis there too, and 33px of the 77 measured were padding
   around a 44px logo row. The bar is now one height everywhere — what desktop still gets is the
   bigger sync counters, which cost no height.

   `pointer: fine` rather than width alone: a phone in landscape clears 700px easily, so keying on
   the pointer type asks the real question ("is this a device driven by a mouse?") instead of a proxy
   for it. */
@media (min-width: 700px) and (pointer: fine) {
  .cloud-sync-icon .fa-cloud {
    font-size: 20px;
  }
  .sync-badge {
    font-size: 14px;
  }
  .sync-badge i {
    font-size: 13px;
  }
}

/* Attribution block in the About dialog. Collapsed by default so the dialog still opens on the
   product description, but reachable in ONE tap — the notices must be present in the installed app,
   not only in the repository (run_build ships src/ only, so THIRD_PARTY_NOTICES.md never reaches a
   phone). The summary is a real touch target, not 9px of text: licence text is exactly the kind of
   thing a support request needs and a sweaty thumb has to hit. */
.about-attribution {
  margin-top: 0.75rem;
  border-top: 1px solid var(--border-color, rgba(128, 128, 128, 0.25));
  padding-top: 0.5rem;
}

.about-attribution > summary {
  cursor: pointer;
  padding: 0.6rem 0.25rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted, inherit);
  list-style: none;
}

.about-attribution > summary::-webkit-details-marker {
  display: none;
}

.about-attribution > summary::after {
  content: " ▾";
  opacity: 0.6;
}

.about-attribution[open] > summary::after {
  content: " ▴";
}

.about-attribution .dialog-desc {
  font-size: 0.78rem;
  line-height: 1.5;
  opacity: 0.85;
}

/* Settings (#dialog-settings): the menu's former rows, as a list inside a dialog. The rows keep the
   menu's own item style, so a theme that restyles one restyles both. */
.settings-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.settings-heading {
  margin: 16px 0 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

/* One service in Settings' Connected accounts: its name, its state on this device, and its two acts. */
.connected-account {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
