/* ══════════════════════════════════════════════════════════════════════
   PYQ SUBJECT QUESTION BANKS
   Rendered by scripts/features/pyq-banks.js into #pyq-banks-mount.

   TYPOGRAPHY — read this before adding a rule
   -------------------------------------------
   There is NO monospace in this section, deliberately. The rest of the PYQ
   arena leans hard on JetBrains Mono for its HUD chrome, and that is right for
   a countdown, a score chip and a rank readout — numbers that are meant to
   feel like instrumentation. It is wrong here. This is the one part of the
   page where somebody is deciding whether to spend their own money, and a
   price set in a terminal face with 0.1em tracking reads like a generated
   pricing table rather than like a person asking.

   So: Lora carries the headings, the subject names and every price (the same
   warm-numeral choice the exam countdown banner in study-stats.css makes, and
   for the same reason), DM Sans carries the body. Sentence case throughout —
   no ALL-CAPS labels, no letter-spaced eyebrows.

   The shapes are soft too. The arena's angular clip-path notches are its
   fighting-game language; a shelf of subjects is not a fight, so these are
   plain rounded cards with a hairline. What is kept from the page is its
   COLOUR — the same violet, gold, cyan and green on near-black — so the
   section still belongs to PYQ without shouting in its voice.

   Note on the cascade: PYQ_STYLE in sections/pyq-1v1.section.js is injected
   into <head> at parse time, so it lands AFTER this linked stylesheet and wins
   any same-specificity tie. Nothing here shares a selector with it — every
   class is .pqb-* — which is why these rules are not silently dead. Keep it
   that way; a .pyq-* rule added to this file would be dead config.
   ══════════════════════════════════════════════════════════════════════ */

#page-pyq .pqb {
  --pqb-serif: 'Lora', Georgia, 'Times New Roman', serif;
  --pqb-sans: 'DM Sans', system-ui, -apple-system, sans-serif;
  margin: 2rem 0 1.4rem;
  font-family: var(--pqb-sans);
}

/* The only sentence in the section, so it is allowed to be the largest thing
   in it after the pass price.

   NO max-width, deliberately. It was clamped to 20ch, which at this size is
   ~240px — barely a third of the pass card underneath it — so the headline
   broke into three stubby lines that stopped well short of everything else on
   screen and read as a different column. It runs the full width of the wrap
   now, the same width as the card and the grid, so the section has one left
   edge and one right edge.

   `text-wrap` is left alone for the same reason: `balance` would even the
   lines up by making them all SHORTER, which is the opposite of filling the
   width. Greedy wrapping is what puts a full line against the card's edge.

   The SIZE is what makes the last line fill rather than orphan, and the right
   size depends entirely on how wide the column is — this sentence is 62
   characters, so whether it lands as two full lines or one full line plus
   "once." is decided by how many characters fit. Measured in Chromium at the
   real widths, with the two line boxes given as a percentage of the card:

     container 288 (320 vp)  1.32rem → 93 / 83 / 16   1.25rem → 88 / 95  ✓
     container 328 (360 vp)  1.50rem → 92 / 83 / 16   1.40rem → 86 / 94  ✓
     container 343 (375 vp)  1.50rem → 88 / 96  ✓
     container 448 (480 vp)  1.90rem → 86 / 93  ✓
     container 608 (desktop) 1.50rem → 96 /  9   ← the orphan
                             2.10rem → 92 / 54
                             2.50rem → 83 / 90  ✓

   Every step below is one of those ✓ rows. They are steps rather than a fluid
   clamp because the wrap is pinned at 608px from a 640px viewport up, so above
   that there is nothing to interpolate — and below it the good sizes do not
   sit on a straight line anyway: whether the sentence lands as two lines or
   two-plus-an-orphan flips at a hard threshold, not gradually.

   If this sentence ever changes length, re-measure. Sizes chosen by eye will
   put "once." back on a line of its own. */
#page-pyq .pqb-title {
  font-family: var(--pqb-serif);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.28;
  color: var(--pyq-ink);
  margin: 0 0 1.1rem;
}
@media (max-width: 374px) {
  #page-pyq .pqb-title { font-size: 1.4rem; }
}
@media (max-width: 339px) {
  #page-pyq .pqb-title { font-size: 1.25rem; }
}
@media (min-width: 480px) {
  #page-pyq .pqb-title { font-size: 1.9rem; }
}
/* 640px, not 700: `.pyq-wrap` is max-width 640 with 1rem padding, so the
   column reaches its full 608px at exactly a 640px viewport and wants the
   large size from there. Measured at 700 first, which left 640 rendering the
   small size in a full-width column — 91% / 41%, the orphan back again. */
@media (min-width: 640px) {
  #page-pyq .pqb-title { font-size: 2.5rem; line-height: 1.18; }
}
#page-pyq .pqb-title em {
  font-style: italic;
  color: var(--pyq-gold);
}

/* ── the pass: the one large object in the section ── */
#page-pyq .pqb-bundle {
  position: relative;
  padding: 1.15rem 1.1rem 1.2rem;
  margin-bottom: 1.5rem;
  border-radius: 18px;
  overflow: hidden;
  background:
    radial-gradient(130% 100% at 8% 0%, rgba(124, 58, 255, 0.32), transparent 62%),
    radial-gradient(110% 90% at 100% 100%, rgba(255, 210, 63, 0.1), transparent 60%),
    linear-gradient(180deg, rgba(24, 17, 46, 0.97), rgba(13, 10, 27, 0.97));
  box-shadow:
    inset 0 0 0 1px rgba(150, 110, 255, 0.34),
    0 18px 40px -26px rgba(0, 0, 0, 0.9);
}
#page-pyq .pqb-bundle.is-owned {
  background:
    radial-gradient(130% 100% at 8% 0%, rgba(47, 224, 138, 0.22), transparent 62%),
    linear-gradient(180deg, rgba(14, 31, 26, 0.97), rgba(10, 22, 19, 0.97));
  box-shadow:
    inset 0 0 0 1px rgba(47, 224, 138, 0.3),
    0 18px 40px -26px rgba(0, 0, 0, 0.9);
}
#page-pyq .pqb-bundle-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.7rem;
  flex-wrap: wrap;
  margin-bottom: 0.15rem;
}
#page-pyq .pqb-bundle-name {
  font-family: var(--pqb-serif);
  font-size: 1.22rem;
  font-weight: 700;
  color: var(--pyq-ink);
}

/* Anchor and price on one baseline. The struck-through figure is deliberately
   quiet: it is context, not the offer, and a loud one reads as a hard sell. */
#page-pyq .pqb-bundle-price {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  flex-wrap: wrap;
  margin-bottom: 0.4rem;
}
#page-pyq .pqb-bundle-price .pqb-was {
  font-family: var(--pqb-serif);
  font-size: 1rem;
  color: var(--pyq-ink-3);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
#page-pyq .pqb-bundle-price .pqb-now {
  font-family: var(--pqb-serif);
  font-size: 2.15rem;
  font-weight: 700;
  line-height: 1;
  color: var(--pyq-gold);
}

/* Access is yearly, so every price says so. Quiet and tucked against the
   number rather than beside it — it qualifies the price, it is not a second
   piece of information competing with it. */
#page-pyq .pqb-per {
  font-family: var(--pqb-sans);
  font-size: 0.72rem;
  color: var(--pyq-ink-3);
  margin-left: -0.2rem;
}
#page-pyq .pqb-bundle-price .pqb-per { font-size: 0.85rem; }

/* ── badges: text-carrying, never colour alone ── */
#page-pyq .pqb-off,
#page-pyq .pqb-owned {
  font-family: var(--pqb-sans);
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0.16rem 0.5rem;
  border-radius: 99px;
  white-space: nowrap;
}
/* Soft on a card, solid on the pass — the pass is the hero and gets to shout
   slightly louder than nineteen equals. */
#page-pyq .pqb-off {
  color: var(--pyq-gold);
  background: rgba(255, 210, 63, 0.13);
  box-shadow: inset 0 0 0 1px rgba(255, 210, 63, 0.3);
}
#page-pyq .pqb-bundle .pqb-off {
  color: #1b0713;
  background: var(--pyq-gold);
  box-shadow: none;
  font-weight: 700;
}
#page-pyq .pqb-owned {
  color: var(--pyq-green);
  background: rgba(47, 224, 138, 0.14);
  box-shadow: inset 0 0 0 1px rgba(47, 224, 138, 0.32);
}

/* ── year filters ── */
#page-pyq .pqb-filters {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 0.4rem;
  margin-bottom: 0.85rem;
  -webkit-overflow-scrolling: touch;
}
#page-pyq .pqb-filters::-webkit-scrollbar { display: none; }
#page-pyq .pqb-filter {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 38px;
  padding: 0.4rem 0.8rem;
  font-family: var(--pqb-sans);
  font-size: 0.78rem;
  color: var(--pyq-ink-2);
  background: var(--pyq-surface);
  border: 1px solid var(--pyq-line);
  border-radius: 99px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
#page-pyq .pqb-filter:hover { color: var(--pyq-ink); border-color: rgba(150, 110, 255, 0.45); }
#page-pyq .pqb-filter.is-on {
  color: #0a0616;
  background: var(--pyq-cyan);
  border-color: var(--pyq-cyan);
  font-weight: 600;
}
#page-pyq .pqb-filter-n {
  font-size: 0.68rem;
  opacity: 0.62;
}

/* ── the shelf ── */
#page-pyq .pqb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
  gap: 0.65rem;
}
#page-pyq .pqb-card {
  display: flex;
  flex-direction: column;
  padding: 0.85rem 0.8rem 0.8rem;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(20, 15, 38, 0.9), rgba(13, 10, 26, 0.9));
  box-shadow: inset 0 0 0 1px var(--pyq-line);
  transition: box-shadow 0.18s, transform 0.18s;
}
#page-pyq .pqb-card:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 1px rgba(150, 110, 255, 0.42);
}
#page-pyq .pqb-card.is-owned {
  background: linear-gradient(180deg, rgba(14, 31, 26, 0.9), rgba(10, 21, 18, 0.9));
  box-shadow: inset 0 0 0 1px rgba(47, 224, 138, 0.3);
}
#page-pyq .pqb-card.is-owned:hover { box-shadow: inset 0 0 0 1px rgba(47, 224, 138, 0.5); }
#page-pyq .pqb-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.35rem;
  margin-bottom: 0.45rem;
}
#page-pyq .pqb-ico { font-size: 1.3rem; line-height: 1; }
#page-pyq .pqb-name {
  font-family: var(--pqb-serif);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.25;
  color: var(--pyq-ink);
  margin: 0 0 0.5rem;
}
/* An owned card has no price block, so the CTA takes the leftover height
   instead — otherwise owned and locked cards in the same row would have their
   buttons at different heights. */
#page-pyq .pqb-name + .pqb-cta { margin-top: auto; }
/* The price block takes the leftover height so every CTA in a row lines up,
   whatever the subject name wrapped to. */
#page-pyq .pqb-price {
  margin-top: auto;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
}
#page-pyq .pqb-card .pqb-was {
  font-family: var(--pqb-serif);
  font-size: 0.82rem;
  color: var(--pyq-ink-3);
}
#page-pyq .pqb-card .pqb-now {
  font-family: var(--pqb-serif);
  font-size: 1.32rem;
  font-weight: 700;
  color: var(--pyq-gold);
}

/* ── the one CTA per card ── */
#page-pyq .pqb-cta {
  width: 100%;
  min-height: 44px;
  padding: 0.62rem 0.5rem;
  font-family: var(--pqb-sans);
  font-size: 0.82rem;
  font-weight: 600;
  color: #0a0616;
  background: linear-gradient(100deg, var(--pyq-violet), #a06bff);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  transition: transform 0.12s, filter 0.15s;
}
#page-pyq .pqb-cta:hover { filter: brightness(1.1); }
#page-pyq .pqb-cta:active { transform: scale(0.97); }
#page-pyq .pqb-cta:disabled { opacity: 0.55; cursor: progress; }
#page-pyq .pqb-cta-owned {
  color: #05170f;
  background: linear-gradient(100deg, var(--pyq-green), #7ef0b8);
}
#page-pyq .pqb-cta-bundle {
  font-size: 0.95rem;
  font-weight: 700;
  min-height: 52px;
  border-radius: 12px;
  color: #14060f;
  background: linear-gradient(100deg, var(--pyq-gold), #ffe98a);
  box-shadow: 0 10px 28px -14px rgba(255, 210, 63, 0.75);
}
#page-pyq .pqb-filter:focus-visible,
#page-pyq .pqb-cta:focus-visible,
#page-pyq .pqb-retry:focus-visible {
  outline: 2px solid var(--pyq-cyan);
  outline-offset: 2px;
}

/* ── status / loading / error ── */
/* Visually hidden. Filtering swaps the whole grid out from under a screen
   reader with nothing else to announce it; on screen it would be one more line
   of text this section is deliberately without. */
#page-pyq .pqb-status {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
#page-pyq .pqb-skeleton,
#page-pyq .pqb-error {
  padding: 1.15rem 1rem;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(20, 15, 38, 0.9), rgba(13, 10, 26, 0.9));
  box-shadow: inset 0 0 0 1px var(--pyq-line);
}
#page-pyq .pqb-skeleton {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-size: 0.85rem;
  color: var(--pyq-ink-2);
}
#page-pyq .pqb-skeleton-ring {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid rgba(150, 110, 255, 0.25);
  border-top-color: var(--pyq-violet);
  animation: pqbSpin 0.85s linear infinite;
}
@keyframes pqbSpin { to { transform: rotate(360deg); } }
#page-pyq .pqb-error-msg {
  font-family: var(--pqb-serif);
  font-size: 1rem;
  font-weight: 700;
  color: var(--pyq-ink);
  margin: 0 0 0.35rem;
}
#page-pyq .pqb-retry { width: auto; padding: 0.55rem 1.2rem; }

/* The one disclosure that stays. A real purchase needs its terms reachable,
   and this is one line. */
#page-pyq .pqb-fine {
  font-size: 0.7rem;
  color: var(--pyq-ink-3);
  margin: 1rem 0 0;
}
#page-pyq .pqb-fine a { color: var(--pyq-cyan); }

/* Narrow phones: one column beats two 140px cards whose names all wrap. */
@media (max-width: 359px) {
  #page-pyq .pqb-grid { grid-template-columns: 1fr; }
  #page-pyq .pqb-bundle-price .pqb-now { font-size: 1.85rem; }
}

@media (prefers-reduced-motion: reduce) {
  #page-pyq .pqb-skeleton-ring { animation: none; }
  #page-pyq .pqb-cta, #page-pyq .pqb-card { transition: none; }
  #page-pyq .pqb-cta:active { transform: none; }
  #page-pyq .pqb-card:hover { transform: none; }
}
