/* ════════════════════════════════════════════════════════════════════════
   SwimBase enhance.css — shared, theme-aware UX toolkit.

   Loaded on every page via partials/_head_scripts.html. All classes are
   namespaced `fx-` to avoid colliding with page-local styles. Colors use the
   universal palette vars (--blue, --surface*, --b1/2, --t1/2/3, --r*) that
   every page defines, with fallback chains for the semantic colors that vary
   page-to-page (some pages name green `--green`, others `--a`, etc.).

   Pair with static/js/enhance.js (window.SwimFX). Everything degrades to a
   static, fully-usable state without JS and respects prefers-reduced-motion.
   ════════════════════════════════════════════════════════════════════════ */

:root {
  /* Resolve semantic colors regardless of which naming a page uses. */
  --fx-blue:  var(--blue, #3b82f6);
  --fx-cyan:  var(--cyan, var(--blue, #38bdf8));
  --fx-green: var(--green, var(--b, var(--a, #22c55e)));
  --fx-amber: var(--amber, var(--c, #f59e0b));
  --fx-red:   var(--red, var(--d, #ef4444));
  --fx-ease:  cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* ── Count-up numbers ──────────────────────────────────────────────────────
   tabular-nums keeps the glyph width fixed so a counting number doesn't jitter
   the layout as digits change. */
.fx-count, .fx-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }

/* ── Pop / stamp ────────────────────────────────────────────────────────────
   The generalized version of analyze.html's grade "stamp" — draws the eye to a
   value that just resolved. */
@keyframes fx-pop {
  0%   { transform: scale(0.84); opacity: 0; }
  60%  { transform: scale(1.04); opacity: 1; }
  100% { transform: scale(1); }
}
.fx-pop { animation: fx-pop 0.42s var(--fx-ease) both; }

/* ── Scroll-reveal + stagger ────────────────────────────────────────────────
   Add .fx-reveal to anything; enhance.js adds .fx-in when it scrolls into view.
   Stagger siblings with style="--fx-delay:60ms" (or data-fx-delay). */
.fx-reveal {
  opacity: 0;
  transform: translateY(10px);
  will-change: opacity, transform;
}
.fx-reveal.fx-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s var(--fx-ease), transform 0.55s var(--fx-ease);
  transition-delay: var(--fx-delay, 0ms);
}

/* ── Hover lift ─────────────────────────────────────────────────────────────
   Signals "this whole card is clickable" without copy. */
.fx-lift { transition: transform 0.16s var(--fx-ease), box-shadow 0.16s var(--fx-ease); }
.fx-lift:hover { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28); }
.fx-lift:active { transform: translateY(-1px); }
/* Lighter, cooler lift shadow on white — the heavy black reads as dirty smudge. */
:root[data-theme="light"] .fx-lift:hover { box-shadow: 0 10px 28px rgba(15, 27, 45, 0.12); }

/* ── Smooth expand / collapse ───────────────────────────────────────────────
   The grid-rows 0fr→1fr trick animates to natural content height (no JS height
   measurement, no magic max-height numbers). Markup:
     <div class="fx-collapse"><div class="fx-collapse-inner"> …content… </div></div>
   Toggle .open (enhance.js does this for [data-fx-toggle]). */
.fx-collapse {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s var(--fx-ease);
}
.fx-collapse.open { grid-template-rows: 1fr; }
.fx-collapse > .fx-collapse-inner { overflow: hidden; min-height: 0; }

/* Chevron that rotates when its collapse opens. */
.fx-chevron { transition: transform 0.24s var(--fx-ease); }
.fx-chevron.open { transform: rotate(180deg); }

/* ── Inline form validation ─────────────────────────────────────────────────
   enhance.js toggles .fx-valid / .fx-invalid on the field as the user types. */
input.fx-valid, textarea.fx-valid, select.fx-valid {
  border-color: color-mix(in srgb, var(--fx-green) 55%, transparent) !important;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-green) 14%, transparent) !important;
}
input.fx-invalid, textarea.fx-invalid, select.fx-invalid {
  border-color: color-mix(in srgb, var(--fx-red) 60%, transparent) !important;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-red) 14%, transparent) !important;
}
/* Validation message line rendered under a field. */
.fx-msg {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.3;
  margin-top: 5px;
  min-height: 0;
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity 0.16s ease, transform 0.16s ease;
}
.fx-msg.show { opacity: 1; transform: none; }
.fx-msg.err { color: var(--fx-red); }
.fx-msg.ok  { color: var(--fx-green); }
.fx-msg svg { width: 13px; height: 13px; flex-shrink: 0; }

/* ── Progress bar ───────────────────────────────────────────────────────────
   <div class="fx-progress"><i class="fx-progress-bar"></i></div>
   enhance.js will create the <i> if it's missing. */
.fx-progress {
  position: relative;
  height: 6px;
  width: 100%;
  background: var(--surface-3, rgba(255, 255, 255, 0.06));
  border-radius: 100px;
  overflow: hidden;
}
.fx-progress > .fx-progress-bar {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 100px;
  background: linear-gradient(90deg, var(--fx-blue), var(--fx-cyan));
  transition: width 0.25s var(--fx-ease), background 0.25s ease;
}
.fx-progress.done > .fx-progress-bar { background: linear-gradient(90deg, var(--fx-green), var(--fx-green)); width: 100%; }
.fx-progress.error > .fx-progress-bar { background: var(--fx-red); }
/* Indeterminate: a band sweeps while we wait for a determinate signal. */
.fx-progress.indeterminate > .fx-progress-bar {
  width: 38%;
  animation: fx-indeterminate 1.15s ease-in-out infinite;
}
@keyframes fx-indeterminate {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(330%); }
}

/* ── Skeletons ──────────────────────────────────────────────────────────────
   Compose layout-matching placeholders from these building blocks so the
   skeleton mirrors the real content (no generic gray bars). */
@keyframes fx-shimmer { 0% { background-position: -160% 0; } 100% { background-position: 160% 0; } }
.fx-sk {
  background: linear-gradient(90deg,
    var(--surface-2, rgba(255, 255, 255, 0.04)) 25%,
    var(--hover, rgba(255, 255, 255, 0.09)) 37%,
    var(--surface-2, rgba(255, 255, 255, 0.04)) 63%);
  background-size: 280% 100%;
  animation: fx-shimmer 1.25s ease-in-out infinite;
  border-radius: var(--r-sm, 8px);
}
.fx-sk-line   { height: 12px; border-radius: 6px; }
.fx-sk-line.lg { height: 16px; }
.fx-sk-line.sm { height: 9px; }
.fx-sk-pill   { height: 22px; width: 56px; border-radius: 100px; }
.fx-sk-circle { border-radius: 50%; aspect-ratio: 1; }
.fx-sk-block  { height: 64px; border-radius: var(--r, 12px); }

/* ── Insight banner — the "bite" layer of bite/snack/meal ────────────────────
   A single plain-language takeaway above the dense data. */
.fx-insight {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 15px;
  border-radius: var(--r, 12px);
  background: var(--surface-2, rgba(255, 255, 255, 0.04));
  border: 1px solid var(--b1, rgba(255, 255, 255, 0.06));
  border-left: 3px solid var(--fx-blue);
}
.fx-insight.good { border-left-color: var(--fx-green); }
.fx-insight.warn { border-left-color: var(--fx-amber); }
.fx-insight.bad  { border-left-color: var(--fx-red); }
.fx-insight-ic {
  flex-shrink: 0;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px;
  background: var(--blue-dim, rgba(59, 130, 246, 0.12));
  color: var(--fx-blue);
}
.fx-insight.good .fx-insight-ic { background: color-mix(in srgb, var(--fx-green) 14%, transparent); color: var(--fx-green); }
.fx-insight.warn .fx-insight-ic { background: color-mix(in srgb, var(--fx-amber) 14%, transparent); color: var(--fx-amber); }
.fx-insight.bad  .fx-insight-ic { background: color-mix(in srgb, var(--fx-red) 14%, transparent); color: var(--fx-red); }
.fx-insight-ic svg { width: 17px; height: 17px; }
.fx-insight-body { min-width: 0; }
.fx-insight-head { font-size: 13.5px; font-weight: 700; color: var(--t1, #f0f4f8); letter-spacing: -0.01em; }
.fx-insight-sub  { font-size: 12px; color: var(--t2, #8ba0b8); margin-top: 2px; line-height: 1.45; }
.fx-insight-head .fx-up   { color: var(--fx-green); }
.fx-insight-head .fx-down { color: var(--fx-red); }

/* ── Sparkline — the "snack" trend glyph ─────────────────────────────────────
   enhance.js renders the SVG; this just sizes it inline with text. */
.fx-spark { display: inline-block; vertical-align: middle; line-height: 0; }
.fx-spark svg { display: block; overflow: visible; }

/* ── content-visibility — skip layout/paint for off-screen blocks ────────────
   A cheap, large perceived-perf win on long race lists/tables. Set the
   estimated height with --fx-cv-h to keep the scrollbar stable. */
.fx-cv { content-visibility: auto; contain-intrinsic-size: auto var(--fx-cv-h, 600px); }

/* ── Reduced motion — honor the OS setting everywhere ────────────────────────*/
@media (prefers-reduced-motion: reduce) {
  .fx-reveal { opacity: 1; transform: none; }
  .fx-collapse { transition: none; }
  .fx-chevron { transition: none; }
  .fx-pop { animation: none; }
  .fx-lift { transition: none; }
  .fx-lift:hover { transform: none; }
  .fx-sk { animation: none; }
  .fx-progress.indeterminate > .fx-progress-bar { animation: none; }
  .fx-progress > .fx-progress-bar { transition: none; }
}
