/* ==========================================================================
   Flashcards hub — DESIGN SAMPLE (/flashcards/hub-sample/)

   THE MOTION IS 2D ON PURPOSE
   No perspective, no rotateY, nothing on a Z axis. Depth here comes from
   translate, scale, clip-path, mask, shadow and colour. A card that rotates in
   space makes its own body copy harder to read at exactly the moment somebody
   is trying to read it, and this page is a dense grid of text.

   Only tokens that exist in tokens.css. --s-9, --s-13 and --s-14 are NOT in
   the scale, and an undefined custom property invalidates the whole
   declaration at computed-value time — silently.
   ========================================================================== */

.page-fhx {
  --fhx-max:  1180px;
  --fhx-ink:  var(--text-hi);
  --fhx-line: var(--surface-line);
  --fhx-pane: var(--surface-1);
  --fhx-bar-h: 4.25rem;
  background: var(--surface-0);
  overflow-x: clip;
}

.fhxEyebrow {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--accent-ink); margin: 0 0 var(--s-3);
}
.fhxMark { color: var(--accent-ink); }

.fhxBtn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  padding: .8rem 1.4rem; border-radius: var(--radius-pill);
  border: 1px solid transparent; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
  transition: transform var(--dur) var(--ease-spring), box-shadow var(--dur) var(--ease-out);
}
.fhxBtn--primary { background: var(--y-yellow); color: var(--y-black); box-shadow: var(--glow-yellow); }
.fhxBtn--primary:hover { transform: translateY(-2px); }

.fhxCrumbs ol {
  display: flex; flex-wrap: wrap; gap: var(--s-2); list-style: none;
  margin: 0 0 var(--s-6); padding: 0; font-size: var(--fs-sm); color: var(--text-lo);
}
.fhxCrumbs li + li::before { content: '/'; margin-right: var(--s-2); color: var(--fhx-line); }
.fhxCrumbs a { color: var(--text-lo); text-decoration: none; }
.fhxCrumbs a:hover { color: var(--accent-ink); }

/* ══════════════════════════════════════════════════════════════════════════
   01 · HERO
   ══════════════════════════════════════════════════════════════════════════ */
.fhxHero {
  position: relative; overflow: hidden;
  padding: clamp(var(--s-12), 8vw, var(--s-24)) 0 var(--s-12);
}
.fhxHero__inner {
  position: relative; z-index: 1;
  max-width: var(--fhx-max); margin-inline: auto; padding-inline: var(--gutter);
}

/* Two soft blobs that drift. `translate` and `scale` as independent
   properties, never a transform shorthand — that keeps them composable with
   anything else and out of the way of GSAP. */
.fhxHero__blob {
  position: absolute; border-radius: 50%; filter: blur(70px); pointer-events: none;
  animation: fhxDrift 22s var(--ease-inout) infinite alternate;
}
.fhxHero__blob--a {
  width: 34vw; height: 34vw; min-width: 18rem; min-height: 18rem;
  left: -8vw; top: -6vw;
  background: radial-gradient(circle, rgb(240 198 74 / .55), transparent 70%);
}
.fhxHero__blob--b {
  width: 26vw; height: 26vw; min-width: 14rem; min-height: 14rem;
  right: -6vw; top: 30%;
  background: radial-gradient(circle, rgb(232 84 74 / .3), transparent 70%);
  animation-delay: -11s;
}
@keyframes fhxDrift {
  from { translate: 0 0;        scale: 1; }
  to   { translate: 4vw 3vh;    scale: 1.12; }
}

.fhxHero__h1 {
  font-family: var(--font-display); font-size: var(--fs-hero);
  line-height: var(--lh-tight); letter-spacing: var(--tracking-hero);
  font-weight: 500; color: var(--fhx-ink);
  margin: 0 0 var(--s-4);
  display: flex; flex-wrap: wrap; gap: 0 .28em;
}
.fhxWord {
  display: inline-block;
  animation: fhxRise .8s var(--ease-out) backwards;
  animation-delay: calc(var(--i) * 70ms + 60ms);
}
/* A highlighter sweep behind one word, drawn with a scaled pseudo-element
   rather than a background-size trick so it can ease independently. */
.fhxWord--mark { position: relative; color: var(--y-black); }
.fhxWord--mark::before {
  content: ''; position: absolute; inset: .08em -.12em .06em;
  background: var(--y-yellow); border-radius: 3px; z-index: -1;
  transform-origin: left center;
  animation: fhxSwipe .6s var(--ease-out) backwards;
  animation-delay: calc(var(--i) * 70ms + 320ms);
}
@keyframes fhxRise {
  from { opacity: 0; translate: 0 .5em; }
  to   { opacity: 1; translate: 0 0; }
}
@keyframes fhxSwipe { from { scale: 0 1; } to { scale: 1 1; } }

.fhxHero__sub {
  max-width: 54ch; color: var(--text-mid); margin: 0 0 var(--s-8);
  font-size: var(--fs-body-lg); line-height: var(--lh-body);
  animation: fhxRise .8s var(--ease-out) 420ms backwards;
}

/* --- the search field --------------------------------------------------- */
.fhxSearch {
  display: flex; gap: var(--s-3); max-width: 46rem;
  animation: fhxRise .8s var(--ease-out) 500ms backwards;
}
.fhxSearch__field {
  position: relative; flex: 1;
  display: flex; align-items: center; gap: var(--s-3);
  padding: .3rem .3rem .3rem 1.1rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--fhx-line);
  background: var(--fhx-pane);
  box-shadow: var(--shadow-card);
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.fhxSearch__field:focus-within {
  border-color: var(--y-yellow);
  box-shadow: var(--glow-yellow);
}
.fhxSearch__icon { width: 1.15rem; height: 1.15rem; flex: none; color: var(--text-lo); }
.fhxSearch__field:focus-within .fhxSearch__icon { color: var(--accent-ink); }

.fhxSearch input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font: inherit; font-size: var(--fs-body-lg); color: var(--fhx-ink);
  padding: .85rem 0;
}
.fhxSearch input::placeholder { color: var(--text-lo); }
/* The browser's own clear affordance duplicates ours and cannot be styled. */
.fhxSearch input::-webkit-search-cancel-button { display: none; }

.fhxSearch__clear {
  flex: none; width: 2rem; height: 2rem; border: 0; cursor: pointer;
  border-radius: 50%; background: var(--surface-2); color: var(--text-mid);
  font-size: 1.1rem; line-height: 1;
  transition: background-color var(--dur) var(--ease-out), rotate var(--dur) var(--ease-out);
}
.fhxSearch__clear:hover { background: var(--y-red); color: var(--y-white); rotate: 90deg; }

.fhxSearch__kbd {
  flex: none; margin-right: .55rem;
  padding: .15rem .45rem; border-radius: 5px;
  border: 1px solid var(--fhx-line); background: var(--surface-2);
  font-family: var(--font-mono); font-size: .68rem; color: var(--text-lo);
}
.fhxSearch__field:focus-within .fhxSearch__kbd { opacity: 0; }

.fhxTries {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  margin: var(--s-4) 0 0; font-size: var(--fs-sm); color: var(--text-lo);
  animation: fhxRise .8s var(--ease-out) 580ms backwards;
}
.fhxTry {
  padding: .3rem .8rem; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px dashed var(--fhx-line); background: none;
  font: inherit; font-size: var(--fs-xs); color: var(--text-mid);
  transition: border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.fhxTry:hover {
  border-style: solid; border-color: var(--y-yellow);
  background: rgb(240 198 74 / .16); color: var(--accent-ink);
}

/* ══════════════════════════════════════════════════════════════════════════
   02 · CONTROL BAR
   ══════════════════════════════════════════════════════════════════════════ */
.fhxBar {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--surface-0) 88%, transparent);
  backdrop-filter: blur(14px) saturate(1.4);
  transition: box-shadow var(--dur) var(--ease-out);
}
.fhxBar.is-stuck { box-shadow: 0 10px 30px -18px rgb(0 0 0 / .35); }

.fhxBar__inner {
  max-width: var(--fhx-max); margin-inline: auto; padding: var(--s-3) var(--gutter);
  display: flex; align-items: center; gap: var(--s-4);
  min-height: var(--fhx-bar-h);
}

/* The mini field only exists once the hero's has scrolled away — two search
   boxes on screen at once is a question about which one is authoritative. */
.fhxBar__mini {
  flex: 0 0 auto; width: 0; overflow: hidden;
  transition: width var(--dur-slow) var(--ease-out), opacity var(--dur) var(--ease-out);
  opacity: 0;
}
.fhxBar.is-stuck .fhxBar__mini { width: min(16rem, 34vw); opacity: 1; }
.fhxBar__mini input {
  width: 100%; padding: .5rem .9rem; font: inherit; font-size: var(--fs-sm);
  border-radius: var(--radius-pill); border: 1px solid var(--fhx-line);
  background: var(--fhx-pane); color: var(--fhx-ink); outline: none;
}
.fhxBar__mini input:focus { border-color: var(--y-yellow); }

.fhxFilters {
  display: flex; align-items: center; gap: var(--s-2);
  flex: 1; min-width: 0;
  overflow-x: auto; scrollbar-width: none;
  padding-block: var(--s-1);
  /* Fades at both ends so a chip that is half off the edge reads as "there is
     more" rather than as a clipped element. */
  mask-image: linear-gradient(to right, transparent, #000 12px, #000 calc(100% - 12px), transparent);
}
.fhxFilters::-webkit-scrollbar { display: none; }

.fhxChip {
  position: relative; flex: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .45rem .9rem; border-radius: var(--radius-pill);
  border: 1px solid var(--fhx-line); background: var(--fhx-pane);
  font: inherit; font-size: var(--fs-sm); color: var(--text-mid);
  transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
  isolation: isolate;
}
/* The fill wipes in from the left rather than the background switching. */
.fhxChip::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; background: var(--y-yellow);
  transform-origin: left center; scale: 0 1;
  transition: scale var(--dur) var(--ease-out);
}
.fhxChip:hover { border-color: var(--y-yellow); color: var(--accent-ink); }
.fhxChip.is-on { color: var(--y-black); border-color: transparent; font-weight: 600; }
.fhxChip.is-on::before { scale: 1 1; }
.fhxChip i {
  font-style: normal; font-family: var(--font-mono); font-size: .62rem;
  padding: .05rem .35rem; border-radius: 999px;
  background: var(--surface-2); color: var(--text-lo);
}
.fhxChip.is-on i { background: rgb(28 28 28 / .18); color: var(--y-black); }

.fhxBar__right { display: flex; align-items: center; gap: var(--s-3); flex: none; }
.fhxSort select {
  font: inherit; font-size: var(--fs-sm); cursor: pointer;
  padding: .45rem 1.8rem .45rem .8rem; border-radius: var(--radius-pill);
  border: 1px solid var(--fhx-line); background: var(--fhx-pane); color: var(--text-mid);
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 1rem center, right .72rem center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.fhxSort select:focus-visible { outline: 2px solid var(--y-yellow); outline-offset: 2px; }

.fhxCount { margin: 0; font-size: var(--fs-sm); color: var(--text-lo); white-space: nowrap; }
.fhxCount b { color: var(--fhx-ink); font-variant-numeric: tabular-nums; }

.fhxBar__rule { display: block; height: 1px; background: var(--fhx-line); }

/* ══════════════════════════════════════════════════════════════════════════
   03 · THE GRID
   ══════════════════════════════════════════════════════════════════════════ */
.fhxDecks { padding-block: var(--s-10) var(--s-20); }
.fhxDecks__inner { max-width: var(--fhx-max); margin-inline: auto; padding-inline: var(--gutter); }

.fhxGrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-5);
  align-items: start;
}

/* A group label spans the row it introduces. It is a SIBLING of the cards, not
   a wrapper — sorting has to move a card past a heading, and a card nested in
   a per-year section cannot move without being re-parented. */
.fhxGroup {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: var(--s-3);
  margin: var(--s-6) 0 0; padding-bottom: var(--s-2);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--text-lo); font-weight: 500;
  border-bottom: 1px solid var(--fhx-line);
}
.fhxGrid > .fhxGroup:first-child { margin-top: 0; }
.fhxGroup i {
  font-style: normal; padding: .1rem .45rem; border-radius: 999px;
  background: var(--surface-2); color: var(--text-mid); font-size: .62rem;
}
/* Hidden when a search is running: groups describe the full list, and a
   heading above zero cards is a lie about what is underneath it. */
.fhxGrid.is-searching .fhxGroup { display: none; }
.fhxGroup[hidden] { display: none; }

/* --- the card ------------------------------------------------------------ */
.fhxCard {
  position: relative; isolation: isolate;
  display: grid; align-content: start; gap: var(--s-3);
  padding: var(--s-6);
  border-radius: var(--radius-lg);
  border: 1px solid var(--fhx-line);
  background: var(--fhx-pane);
  animation: fhxIn .5s var(--ease-out) backwards;
  animation-delay: calc(var(--i) * 32ms);
  transition: border-color var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out),
              translate var(--dur) var(--ease-out);
}
@keyframes fhxIn {
  from { opacity: 0; translate: 0 12px; }
  to   { opacity: 1; translate: 0 0; }
}
.fhxCard:hover {
  border-color: rgb(240 198 74 / .6);
  box-shadow: var(--shadow-card), var(--glow-yellow);
  translate: 0 -4px;
}

/* The whole card is the link. A stretched ::after rather than wrapping the
   card in an <a> keeps the heading a heading and the list a list. */
.fhxCard__hit { position: absolute; inset: 0; z-index: 2; border-radius: inherit; }
.fhxCard__hit::after { content: ''; position: absolute; inset: 0; }
.fhxCard:focus-within { border-color: var(--y-yellow); }
.fhxCard__hit:focus-visible { outline: 2px solid var(--y-yellow); outline-offset: 3px; }

/* A wash that wipes up from the bottom on hover — clip-path, so it costs one
   composited property and never touches layout. */
.fhxCard__wash {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  background: linear-gradient(to top, rgb(240 198 74 / .16), transparent 62%);
  clip-path: inset(100% 0 0 0);
  transition: clip-path var(--dur-slow) var(--ease-out);
}
.fhxCard:hover .fhxCard__wash { clip-path: inset(0 0 0 0); }

.fhxCard__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
.fhxCard__code {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  padding: .15rem .5rem; border-radius: 5px;
  background: var(--surface-2); color: var(--accent-ink);
}
.fhxCard__dia {
  font-family: var(--font-mono); font-size: .62rem; color: var(--text-lo);
  white-space: nowrap;
}

.fhxCard__title {
  font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  line-height: var(--lh-snug); color: var(--fhx-ink); margin: 0;
}
.fhxCard__desc {
  margin: 0; font-size: var(--fs-sm); color: var(--text-mid); line-height: var(--lh-body);
}

.fhxCard__meter { height: 3px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
.fhxCard__meter span {
  display: block; height: 100%; width: var(--w, 0%);
  background: linear-gradient(to right, var(--y-yellow-700), var(--y-yellow));
  transform-origin: left center;
  animation: fhxBar .7s var(--ease-out) backwards;
  animation-delay: calc(var(--i) * 32ms + 180ms);
}
@keyframes fhxBar { from { scale: 0 1; } to { scale: 1 1; } }

.fhxCard__facts {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  margin: 0; padding: 0; list-style: none;
  font-size: var(--fs-xs); color: var(--text-lo);
}
.fhxCard__facts b { color: var(--text-mid); font-variant-numeric: tabular-nums; }

.fhxCard__go {
  font-size: var(--fs-sm); font-weight: 600; color: var(--accent-ink);
  display: inline-flex; align-items: center; gap: .4ch;
}
.fhxCard__go i { font-style: normal; transition: translate var(--dur) var(--ease-out); }
.fhxCard:hover .fhxCard__go i { translate: 5px 0; }

/* Search hit marks. <mark> is injected by JS around the matched run. */
.fhxCard mark {
  background: rgb(240 198 74 / .5); color: inherit;
  border-radius: 3px; padding: 0 .1em;
}

/* The keyboard-highlighted result. Distinct from :hover so both can be true. */
.fhxCard.is-cursor {
  border-color: var(--y-yellow);
  box-shadow: var(--glow-yellow);
  translate: 0 -4px;
}

.fhxCard[hidden] { display: none; }

/* --- empty state --------------------------------------------------------- */
.fhxEmpty {
  text-align: center; padding: var(--s-20) var(--gutter);
  display: grid; justify-items: center; gap: var(--s-3);
}
.fhxEmpty[hidden] { display: none; }
.fhxEmpty__mark { font-size: 3rem; margin: 0; color: var(--fhx-line); line-height: 1; }
.fhxEmpty h2 { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500; margin: 0; color: var(--fhx-ink); }
.fhxEmpty h2 b { color: var(--accent-ink); }
.fhxEmpty p { margin: 0; color: var(--text-mid); }

/* ══════════════════════════════════════════════════════════════════════════
   04 · HOW IT WORKS
   ══════════════════════════════════════════════════════════════════════════ */
.fhxHow { padding-block: var(--section-y); background: var(--surface-1); border-top: 1px solid var(--fhx-line); }
.fhxHow__inner { max-width: var(--fhx-max); margin-inline: auto; padding-inline: var(--gutter); }
.fhxHow__title {
  font-family: var(--font-display); font-size: var(--fs-h2); font-weight: 500;
  letter-spacing: var(--tracking-hero); color: var(--fhx-ink); margin: 0 0 var(--s-10);
}
.fhxSteps {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-5); list-style: none; margin: 0; padding: 0;
  counter-reset: fhx;
}
.fhxStep {
  position: relative; padding-top: var(--s-5);
  border-top: 2px solid var(--fhx-line);
}
/* The rule fills in on scroll — a scaled pseudo-element rather than a width
   change, so it never triggers layout. */
.fhxStep::before {
  content: ''; position: absolute; inset: -2px 0 auto 0; height: 2px;
  background: var(--y-yellow); transform-origin: left center;
  scale: 0 1;
  transition: scale .7s var(--ease-out);
  transition-delay: calc(var(--i) * 90ms);
}
.fhxSteps.is-in .fhxStep::before { scale: 1 1; }
.fhxStep b {
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent-ink);
  display: block; margin-bottom: var(--s-2);
}
.fhxStep h3 { font-size: var(--fs-body-lg); color: var(--fhx-ink); margin: 0 0 var(--s-2); font-weight: 600; }
.fhxStep p { margin: 0; font-size: var(--fs-sm); color: var(--text-mid); line-height: var(--lh-body); }

.fhxFlag {
  text-align: center; margin: 0; padding: var(--s-4);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: var(--text-lo); background: var(--surface-2);
}
.fhxFlag a { color: var(--accent-ink); }

/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE
   Each breakpoint restates the grid columns it changes. An element left
   holding a template that no longer exists is auto-placed into an implicit
   row, which looks like a random gap and reports no error.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 64rem) {
  .fhxGrid  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fhxSteps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); }
}

@media (max-width: 48rem) {
  .fhxBar__inner { flex-wrap: wrap; gap: var(--s-2); padding-block: var(--s-2); }
  /* The mini field would take a whole row of a sticky bar on a phone, and the
     hero field is one scroll away. The chips are the navigation here. */
  .fhxBar.is-stuck .fhxBar__mini { width: 0; opacity: 0; }
  .fhxFilters { order: 2; flex-basis: 100%; }
  .fhxBar__right { order: 1; margin-left: auto; }
}

@media (max-width: 46rem) {
  .fhxGrid  { grid-template-columns: minmax(0, 1fr); }
  .fhxSteps { grid-template-columns: minmax(0, 1fr); }
  .fhxHero { padding-top: var(--s-10); }
  .fhxSearch input { font-size: 1rem; }   /* 16px — under it iOS zooms on focus */
  .fhxSearch__kbd { display: none; }      /* no keyboard to press it with */
}

@media (max-width: 24rem) {
  .fhxCard { padding: var(--s-5); }
  .fhxBtn  { width: 100%; }
}

/* ══════════════════════════════════════════════════════════════════════════
   REDUCED MOTION
   tokens.css already zeroes durations globally. What it cannot know about are
   this page's infinite loops and its entrance animations, which have to be
   switched off at the source or they simply play instantly and flash.
   ══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .fhxHero__blob, .fhxWord, .fhxWord--mark::before,
  .fhxHero__sub, .fhxSearch, .fhxTries,
  .fhxCard, .fhxCard__meter span { animation: none; }
  .fhxWord--mark::before { scale: 1 1; }
  .fhxCard__meter span, .fhxStep::before, .fhxChip::before { scale: 1 1; transition: none; }
  .fhxCard:hover { translate: none; }
  .fhxCard__wash { display: none; }
}
