/* ============================================================================================
   Plan cards - ONE design for both audiences of pricing.html
   ============================================================================================

   pricing.html serves two pages: the public /pricing page (logged out) and the signed-in Plans
   page. public-mode.js swaps the shell before first paint - app-dark for a signed-in visitor,
   bd-dark + html.public-mode for an anonymous one.

   THE BUG THIS FIXES. Every dark rule for the plan cards lived in app-dark.css, and that is the
   stylesheet public-mode.js strips. The html.public-mode block in styles.css only ever themed the
   page head (eyebrow, h1, sub-head) - roughly ten rules, none of them touching a card. So a
   logged-out visitor got the LIGHT styles.css card rules painted on a black background: #0f172a
   prices that were invisible, and .pricing-card-note / .pricing-current-access rendering as pale
   bordered boxes that read as empty text inputs.

   So this file is deliberately NOT scoped to one shell. Every rule names both contexts, and it
   loads after both blog-dark.css and app-dark.css so it wins on source order at equal specificity
   (body.app-dark .x and html.public-mode .x are both 0,2,1 - a bare .x would lose to app-dark).

   Keep the two selectors on every rule. Dropping the public one is how the original bug happened.
   ============================================================================================ */

/* --- 1. Grid ------------------------------------------------------------------------------- */
body.app-dark .pricing-grid,
html.public-mode .pricing-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  align-items: stretch;
  margin: 0;
  max-width: none;
}

/* --- 2. The card -------------------------------------------------------------------------- */
body.app-dark .pricing-card,
html.public-mode .pricing-card {
  /* A hair of light from the top-left, so the surface reads as lit rather than as a flat fill. */
  background:
    linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.015) 42%, rgba(255,255,255,0)),
    #0e141d;
  border: 1px solid rgba(255,255,255,.09);
  border-radius: 16px;
  padding: 22px 20px 20px;
  min-height: 0;
  display: flex;
  flex-direction: column;
  position: relative;
  /* Clip decoration to the radius. The old 3px ::before accent bar sat at left/right/top: -1px
     with an 8px radius while the card had 16px, so its square ends poked out past every card's
     rounded top corners - the notches visible at all four card tops. */
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(0,0,0,.28);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}

/* Retired outright rather than re-radiused: it was colour-coding plan identity (violet, emerald,
   teal, and an indigo-to-cyan gradient), which is the thing the accent pass removed everywhere
   else. Which plan you are on is said by the ring and the chip, once. */
body.app-dark .pricing-card::before,
html.public-mode .pricing-card::before { content: none; display: none; }

/* Lift only where there is something to click. A card whose action is an inert state chip should
   not animate under the cursor as though it were pressable. */
body.app-dark .pricing-card:hover:has(button),
html.public-mode .pricing-card:hover:has(button) {
  border-color: rgba(255,255,255,.16);
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
  transform: translateY(-2px);
}

/* The recommended plan gets the cyan, and a wash of it. Card-level cyan used to mean "the access
   you hold" while the recommendation got a brighter white edge - two ways of saying "this card is
   special" in the same visual language, on the same row, one of them pointing at the plan you
   cannot buy again. Cyan now means "this is the one to pick"; "this is the one you have" is said
   by the .pricing-current-tag, in cyan, in words, which is where it belongs.
   A wash and an edge. No outer glow: the point is to be the clearest card, not the brightest
   object on the screen. `.popular` is only ever set on pro_annual when it is NOT the current plan
   (see isBestValue in pricing.js), so this cannot fight the outline below. */
body.app-dark .pricing-card.popular,
html.public-mode .pricing-card.popular {
  background:
    linear-gradient(180deg, rgba(34,211,238,.055), rgba(34,211,238,.012) 46%, rgba(255,255,255,0)),
    #0e141d;
  border-color: rgba(34,211,238,.34);
  box-shadow: 0 1px 2px rgba(0,0,0,.28), inset 0 0 0 1px rgba(34,211,238,.07);
}

/* The plan you hold: outlined, and on a slightly richer surface. A bright neutral edge says "this
   one is different" without competing with the recommendation for the same colour - but an edge
   alone, on a card dimmed along with everything around it, read as a frame around something
   switched off. The lifted fill is what makes it read as selected. */
body.app-dark .pricing-card.current,
html.public-mode .pricing-card.current {
  background:
    linear-gradient(180deg, rgba(255,255,255,.075), rgba(255,255,255,.025) 42%, rgba(255,255,255,.008)),
    #111823;
  border-color: rgba(255,255,255,.34);
  box-shadow: 0 1px 2px rgba(0,0,0,.28), inset 0 0 0 1px rgba(255,255,255,.07);
}

/* Free is the quiet one - a dimmer edge than the three paid cards, so the row has a floor as well
   as a ceiling. Skipped when it is the current plan, which the outline above owns. */
body.app-dark .pricing-card-free:not(.current),
html.public-mode .pricing-card-free:not(.current) {
  border-color: rgba(255,255,255,.06);
}

/* The floating ribbon duplicated the inline "Best value" chip on the same card and, being
   absolutely positioned above the top border, is clipped by the overflow rule above anyway. */
body.app-dark .pricing-popular-tag,
html.public-mode .pricing-popular-tag { display: none; }

/* --- 3. Header row: one chip, left-aligned ------------------------------------------------ */
body.app-dark .pricing-card-top,
html.public-mode .pricing-card-top {
  display: flex; align-items: center; justify-content: flex-start; gap: 8px;
  margin: 0 0 14px; min-height: 22px;
}
body.app-dark .pricing-plan-kicker,
body.app-dark .pricing-plan-fit,
body.app-dark .pricing-current-tag,
html.public-mode .pricing-plan-kicker,
html.public-mode .pricing-plan-fit,
html.public-mode .pricing-current-tag {
  position: static; transform: none; left: auto; top: auto;
  display: inline-flex; align-items: center;
  padding: 3px 9px; border-radius: 6px;
  font-size: 10.5px; font-weight: 600; line-height: 1.4;
  letter-spacing: .01em; text-transform: none; white-space: nowrap;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.1);
  color: rgba(255,255,255,.62);
}
body.app-dark .pricing-current-tag,
html.public-mode .pricing-current-tag {
  background: rgba(34,211,238,.1);
  border-color: rgba(34,211,238,.38);
  color: #67e8f9;
}
/* "Best value" is the one chip allowed to assert itself, and it now matches the card it sits on.
   It is targeted as .pricing-plan-fit, NOT .pricing-plan-kicker: Pro Annual used to carry both a
   "Best value" kicker and a "Recommended" fit side by side, and collapsing that to one chip moved
   the surviving text into the fit slot. The kicker selector this replaces would have matched
   nothing - a silent regression rather than a visible one. */
body.app-dark .pricing-card.popular .pricing-plan-fit,
html.public-mode .pricing-card.popular .pricing-plan-fit {
  background: rgba(34,211,238,.1);
  border-color: rgba(34,211,238,.38);
  color: #67e8f9;
}

/* --- 4. Name and description -------------------------------------------------------------- */
body.app-dark .pricing-card h3,
html.public-mode .pricing-card h3 {
  margin: 0 0 6px;
  font-size: 17px; font-weight: 600; line-height: 1.25; letter-spacing: -.01em;
  color: #fff;
}
/* Fixed height so the price line starts at the same y across all four cards - the descriptions
   run one to three lines and were knocking the whole card out of step with its neighbours. */
body.app-dark .pricing-desc,
html.public-mode .pricing-desc {
  margin: 0 0 18px;
  font-size: 12.5px; line-height: 1.5;
  /* .58, was .5. Secondary, not unavailable - the whole card was reading as disabled because every
     line below the name sat under 50% on a near-black ground. */
  color: rgba(255,255,255,.58);
  /* Three lines. At a four-up width these descriptions run two or three lines, and reserving only
     two left the price of one card sitting a line below its neighbours'. */
  min-height: 56px;
}

/* --- 5. Price ----------------------------------------------------------------------------- */
body.app-dark .pricing-price-row,
html.public-mode .pricing-price-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  margin: 0 0 4px;
}
body.app-dark .pricing-price,
html.public-mode .pricing-price {
  display: flex; align-items: baseline; gap: 7px;
  font-size: 31px; font-weight: 650; line-height: 1.05; letter-spacing: -.02em;
  color: #fff;
  font-variant-numeric: tabular-nums;
}
body.app-dark .price-period,
html.public-mode .price-period {
  margin: 0;
  font-size: 12.5px; font-weight: 400; letter-spacing: 0;
  color: rgba(255,255,255,.52);
}
body.app-dark .pricing-old-price,
html.public-mode .pricing-old-price {
  display: block; margin: 0 0 1px;
  font-size: 14px; font-weight: 400;
  color: rgba(255,255,255,.34);
  text-decoration: line-through;
}
body.app-dark .pricing-new-price,
html.public-mode .pricing-new-price { color: #fff; }
body.app-dark .pricing-rate-note,
html.public-mode .pricing-rate-note {
  font-size: 11.5px; font-weight: 400; line-height: 1.4;
  color: rgba(255,255,255,.52);
}

/* Numbers first, colour last: a saving is a figure to read, not a status to flag. */
body.app-dark .pricing-savings,
html.public-mode .pricing-savings {
  margin: 10px 0 0;
  font-size: 12px; font-weight: 600;
  color: rgba(255,255,255,.78);
}
body.app-dark .pricing-discount-note,
html.public-mode .pricing-discount-note {
  display: inline-block; margin: 8px 0 0; padding: 3px 9px;
  border: 1px solid rgba(34,211,238,.32); border-radius: 999px;
  background: rgba(34,211,238,.08);
  font-size: 11px; font-weight: 600; color: #67e8f9;
}

/* --- 6. "Best for ..." ------------------------------------------------------------------- */
/* Unboxed. In light mode this is a bordered panel; on a dark ground that same panel read as an
   empty text input sitting in the middle of the card, which is what it looked like on /pricing. */
body.app-dark .pricing-card-note,
html.public-mode .pricing-card-note {
  margin: 14px 0 0; padding: 0;
  background: none; border: 0; border-radius: 0;
  font-size: 12px; line-height: 1.5; font-style: normal;
  color: rgba(255,255,255,.52);
}

/* --- 7. Features -------------------------------------------------------------------------- */
/* flex: 1 puts the slack here, so the fine print and the action stay locked to the card floor and
   line up across all four cards even though the lists differ in length. */
body.app-dark .pricing-features,
html.public-mode .pricing-features {
  list-style: none; margin: 18px 0 0; padding: 0 0 18px; flex: 1 1 auto;
}
body.app-dark .pricing-features li,
html.public-mode .pricing-features li {
  position: relative;
  padding: 5px 0 5px 21px;
  font-size: 12.5px; line-height: 1.5;
  color: rgba(255,255,255,.8);
}
body.app-dark .pricing-features li::before,
html.public-mode .pricing-features li::before {
  content: '';
  position: absolute; left: 1px; top: 11px;
  width: 8px; height: 4.5px;
  border-left: 1.5px solid rgba(34,211,238,.75);
  border-bottom: 1.5px solid rgba(34,211,238,.75);
  transform: rotate(-45deg);
}

/* --- 8. Terms and action ------------------------------------------------------------------ */
body.app-dark .pricing-card-fine,
html.public-mode .pricing-card-fine {
  margin: 0 0 14px; padding: 13px 0 0;
  border-top: 1px solid rgba(255,255,255,.07);
  font-size: 11px; line-height: 1.45;
  /* Payment terms. Quietest line on the card by design, but .36 crossed from quiet into unreadable
     once the card around it was also dimmed to .78. */
  color: rgba(255,255,255,.46);
}
/* The Free card carries no payment terms (planFineLine returns '' for it, and its test asserts
   that), so it gets the same rule as a plain divider to keep the four action rows aligned. */
body.app-dark .pricing-card-free .pricing-action,
html.public-mode .pricing-card-free .pricing-action {
  padding-top: 13px; border-top: 1px solid rgba(255,255,255,.07);
}
body.app-dark .pricing-action,
html.public-mode .pricing-action { margin: 0; text-align: center; }
body.app-dark .pricing-action .btn,
html.public-mode .pricing-action .btn {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-width: 0; height: 40px; padding: 0 14px;
  border-radius: 10px;
  font-size: 13px; font-weight: 600; letter-spacing: 0;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
body.app-dark .pricing-action .btn-primary,
html.public-mode .pricing-action .btn-primary {
  background: #22d3ee; border: 1px solid #22d3ee; color: #06202a;
  box-shadow: none;
}
body.app-dark .pricing-action .btn-primary:hover,
html.public-mode .pricing-action .btn-primary:hover {
  background: #67e8f9; border-color: #67e8f9;
}
/* An inert state reads as a label, not as a button someone should keep trying to press. */
body.app-dark .pricing-action .btn-state,
html.public-mode .pricing-action .btn-state {
  background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.14);
  color: rgba(255,255,255,.58); font-weight: 500; cursor: default;
}

/* --- 9. Grid heading and caption ---------------------------------------------------------- */
body.app-dark .plans-grid-heading {
  margin: 30px 0 14px;
  font-size: 15px; font-weight: 600; letter-spacing: -.005em;
  color: rgba(255,255,255,.92);
}
/* The public page leads with its own h1 and sub-head, so a second heading over the grid there
   would just be a third title in a row. pricing.js only injects it when signed in. */
body.app-dark .pricing-trust-line,
html.public-mode .pricing-trust-line {
  margin: 16px 0 0;
  font-size: 12px; line-height: 1.55; text-align: left;
  color: rgba(255,255,255,.55);
}

/* --- 10. Public page: no entitlement talk for someone with no account --------------------- */
/* subData is {} for an anonymous request, so these fall back to free-plan wording and told a
   visitor with no account what "your plan" is. The cards carry the offer; these do not. */
html.public-mode .pricing-account-line,
html.public-mode #pricing-current-context { display: none; }
html.public-mode .pricing-assurance-row {
  margin: 16px 0 0; padding: 0; border: 0; background: none;
  font-size: 12.5px; color: rgba(255,255,255,.55);
}
/* Signed in, the same line is checkout trivia the FAQ already answers, under a page whose job is
   to state what you hold. */
body.app-dark .pricing-assurance-row { display: none; }

/* --- 11. Narrow screens ------------------------------------------------------------------- */
@media (max-width: 1180px) {
  body.app-dark .pricing-grid,
  html.public-mode .pricing-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  body.app-dark .pricing-desc,
  html.public-mode .pricing-desc { min-height: 0; }
}
@media (max-width: 640px) {
  body.app-dark .pricing-grid,
  html.public-mode .pricing-grid { grid-template-columns: minmax(0, 1fr); }
  body.app-dark .pricing-card,
  html.public-mode .pricing-card { padding: 20px 18px 18px; }
  body.app-dark .pricing-price,
  html.public-mode .pricing-price { font-size: 28px; }
}

/* ============================================================================================
   12. Public page layout
   ============================================================================================
   Only the card grid and the page head had any public-mode styling, so the page below the grid
   was a stack of mismatched widths and alignments: a head hard against the left edge under a
   centred pill nav, a centred assurance line over a full-width grid, then 820px centred prose,
   then a 720px centred voucher. This gives the public page one column and one alignment. */

html.public-mode .page-container.pricing-page {
  max-width: 1200px;
  margin: 0 auto;
  padding-left: 24px;
  padding-right: 24px;
}

/* Centred head, like the landing page's pricing section - this is a marketing page, and the head
   was the one part already themed, just not aligned to anything. */
html.public-mode .pricing-page-head { text-align: center; margin: 0 0 26px; }
/* balance stops the last word dropping to a line of its own - it was breaking as
   "... your exam / plan". max-width keeps the measure sane on a wide monitor. */
html.public-mode .pricing-page-head h1 {
  margin: 14px auto 0; max-width: 24ch; text-wrap: balance;
}
html.public-mode .pricing-page-head > div > p {
  max-width: 62ch; margin: 12px auto 0;
  font-size: 15px; line-height: 1.6;
}
/* The eyebrow becomes a chip rather than loose violet lettering. #7c8cff was the last of the
   indigo palette on a public page and it is set with !important in styles.css, so overriding the
   colour needs the same weight. */
html.public-mode .pricing-eyebrow {
  display: inline-flex; align-items: center;
  margin: 0; padding: 5px 12px;
  border: 1px solid rgba(34,211,238,.3); border-radius: 999px;
  background: rgba(34,211,238,.07);
  color: #67e8f9 !important;
  font-size: 10.5px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
}

html.public-mode .pricing-assurance-row { text-align: center; margin: 0 0 26px; }
html.public-mode .pricing-trust-line { text-align: center; margin: 18px 0 0; }

/* One shared width for everything under the grid. */
html.public-mode .voucher-redeem-section,
html.public-mode .pricing-about,
html.public-mode .pricing-compare,
html.public-mode .pricing-faq { max-width: 960px; margin-left: auto; margin-right: auto; }
html.public-mode .voucher-redeem-section { margin-top: 34px; }
html.public-mode .pricing-about { margin-top: 44px; text-align: left; }
html.public-mode .pricing-about h2,
html.public-mode .pricing-faq h2 {
  text-align: center; font-size: 22px; font-weight: 600; color: #fff; margin: 0 0 16px;
}
/* This is the page's actual pitch, so it is read, not skimmed past - it was at the same opacity
   as the fine print underneath it. */
html.public-mode .pricing-about-lead {
  font-size: 14px; line-height: 1.7; color: rgba(255,255,255,.72);
}
html.public-mode .pricing-about-fine {
  font-size: 12.5px; line-height: 1.6; color: rgba(255,255,255,.55);
}
html.public-mode .pricing-faq { margin-top: 52px; padding-bottom: 8px; }
/* A link inside FAQ prose was distinguished from the body text by its cyan alone, which is
   1.91:1 against the surrounding grey and fails WCAG 1.4.1. Underline it, as the article
   prose links in blog-dark.css already are. */
html.public-mode .pricing-faq-list p > a,
body.app-dark .pricing-faq-list p > a { text-decoration: underline; }
/* Two columns, matching the signed-in page. Six questions in one 960px column was a long scroll
   of prose for what people scan. */
html.public-mode .pricing-faq-list { display: grid; gap: 22px 40px; text-align: left; }
@media (min-width: 820px) {
  html.public-mode .pricing-faq-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
html.public-mode .pricing-faq-list h3 { font-size: 14px; font-weight: 600; color: #fff; margin: 0 0 5px; }
html.public-mode .pricing-faq-list p {
  font-size: 13px; line-height: 1.65; color: rgba(255,255,255,.6); margin: 0;
}
/* The signed-in page hides this pitch once you have paid; publicly it is the whole point, so it
   only needs to stop being centred prose at a third width. */
html.public-mode .pricing-included { display: none; }

/* ── Signed-in Plans page: everything below the cards ─────────────────────────────────────────
   Scoped to body.app-dark, so the public /pricing page (html.public-mode, above) keeps its
   markup, its schema.org offers and its expanded FAQ. This file loads after app-dark.css, so
   these win without !important - anything written for these classes in app-dark.css would not.

   The page was doing six jobs under the cards: two scope lines, a bordered voucher panel, a
   "what you are paying for" pitch, links inviting a paying customer to compare CertGrid against
   rival tools, and six billing answers expanded by default. The landing pricing section reads as
   premium because it has focus and air; this gives the signed-in page the same shape. */

/* Current access, as one line rather than a four-fact panel above the cards. */
body.app-dark .plans-access-line {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 9px;
  margin: 14px 0 0; font-size: 13px; line-height: 1.55;
}
body.app-dark .plans-access-lab {
  font-size: 11px; font-weight: 600; letter-spacing: .02em; color: var(--ad-text-4);
}
body.app-dark .plans-access-val { color: var(--ad-text-2); }

/* The selling prose and the pitch are gone from the signed-in page. .pricing-trust-line pitched the
   product to somebody who has already bought it, and .pricing-about / .pricing-compare ask that same
   customer to go and compare tools. Hidden for every signed-in state, not only the paid ones: a free
   signed-in user still has a decision, and .plans-rec below helps them make it in one line. */
body.app-dark .pricing-trust-line,
body.app-dark .pricing-about,
body.app-dark .pricing-compare { display: none; }

/* One recommendation line, where three cells used to be.

   The cells restated what the cards already say - "Single Exam Pass is best for focused prep" sat
   directly under a card headed Single Exam Pass whose own note says the same thing - and the third
   pointed at a voucher fold that is the very next element on the page. Three bordered panels of
   restatement is furniture, not help. This line says the one thing no single card can, because it is
   a comparison between two of them. */
body.app-dark .plans-rec {
  display: flex; align-items: baseline; gap: 8px;
  margin: 22px 0 0; padding: 11px 14px;
  border: 1px solid var(--ad-line-soft); border-left: 2px solid rgba(34,211,238,.5);
  border-radius: 10px; background: rgba(255,255,255,.02);
  font-size: 12.5px; line-height: 1.55; color: rgba(255,255,255,.66);
}
body.app-dark .plans-rec strong { color: rgba(255,255,255,.9); font-weight: 600; }

/* Disclosures. Native <details>: the CSP refuses inline handlers, and a fold that works before any
   script runs is one less thing that can break. */
body.app-dark .plans-fold { margin: 20px 0 0; }
body.app-dark .plans-fold > summary {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer; list-style: none;
  padding: 8px 14px; border: 1px solid var(--ad-line-soft); border-radius: 999px;
  background: rgba(255,255,255,.02);
  font-size: 12.5px; font-weight: 600; color: var(--ad-text-2);
}
body.app-dark .plans-fold > summary::-webkit-details-marker { display: none; }
body.app-dark .plans-fold > summary::after { content: '+'; color: var(--ad-text-4); }
body.app-dark .plans-fold[open] > summary::after { content: '\2013'; }
body.app-dark .plans-fold > summary:hover { border-color: var(--ad-line); color: #fff; }

/* A section moved inside a fold drops its own frame and its own heading: the summary is the
   heading now, and a bordered panel inside a disclosure is two frames deep. */
body.app-dark .plans-fold .voucher-redeem-section,
body.app-dark .plans-fold .pricing-faq {
  margin: 14px 0 0; padding: 0; border: 0; background: none; max-width: none;
}
body.app-dark .plans-fold .voucher-redeem-section h2,
body.app-dark .plans-fold .pricing-faq > h2 { display: none; }

/* The terms, once, after the thing they describe. */
body.app-dark .plans-terms-line {
  margin: 22px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--ad-text-4);
}

@media (max-width: 860px) {
  body.app-dark .plans-rec { flex-direction: column; gap: 3px; }
}

/* ── Paid state: the page becomes an access hub ───────────────────────────────────────────────
   Somebody who has not paid is choosing, so the cards lead. Somebody who has paid came to check
   what they hold or to get back to practising, and leading them with four purchase cards asks a
   question they already answered. The hub answers it instead, and the grid below becomes
   reference. This is the one surface on the page allowed a card treatment - it is the point. */
body.app-dark .plans-hub {
  margin: 18px 0 0; padding: 18px 20px; border-radius: 14px;
  background: rgba(34,211,238,.055);
  border: 1px solid rgba(34,211,238,.32);
  /* A 2px cyan edge down the left, as an inset shadow rather than a border-left: a real border
     would shift the padding box and take the radius with it. This is the one place on the page
     allowed an accent, and it is what makes the card read as the subject rather than as the first
     of five panels. No outer glow - presence, not brightness. */
  box-shadow: inset 2px 0 0 rgba(34,211,238,.55);
  /* Facts left, status right. auto on the second track so the status block takes only the width its
     longest line needs - a fixed column would leave a gap on "Pro Annual" and crowd a longer name. */
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px; align-items: start;
}
body.app-dark .plans-hub-main { min-width: 0; }

/* What each product gives. Two rows, label and value, aligned on the label so the eye can compare
   them without reading both in full. */
body.app-dark .plans-hub-sum {
  list-style: none; margin: 12px 0 0; padding: 0;
  display: grid; gap: 4px;
}
body.app-dark .plans-hub-sum li {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 12.5px; line-height: 1.5; color: rgba(255,255,255,.78);
}
/* One accent per product, and only on the label. Cyan is already the practice-exam colour
   everywhere on this page (the recommended card, the feature ticks, this card's own edge), and
   emerald is already the Guided Labs colour on the dashboard's access group. So this adds no new
   colour to the palette - it just stops the two products sharing one grey.

   A 2px rule rather than coloured text: at 12.5px and 30% weight, tinted type reads as a link
   nobody can click, and these are labels. */
body.app-dark .plans-hub-sum-k {
  flex: 0 0 104px; padding-left: 9px;
  border-left: 2px solid rgba(255,255,255,.14);
  color: rgba(255,255,255,.52);
}
body.app-dark .plans-hub-sum li.is-practice .plans-hub-sum-k {
  border-left-color: rgba(34,211,238,.62);
}
body.app-dark .plans-hub-sum li.is-labs .plans-hub-sum-k {
  border-left-color: rgba(52,211,153,.62);
}

/* "Explore Guided Labs" - the quiet, clickable half of the Labs line. It replaces the words "sold
   separately", which stated a fact and offered no way to act on it, on the one page where somebody
   has already demonstrated they will pay for things. A pill, not a button: it must stay quieter than
   the hero's own CTA, which is the page's primary action. */
body.app-dark .plans-hub-labs-link {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 9px 2px; border-radius: 999px;
  border: 1px solid rgba(52,211,153,.34);
  background: rgba(52,211,153,.09);
  color: #6ee7b7; font-size: 11.5px; font-weight: 600; text-decoration: none;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
body.app-dark .plans-hub-labs-link:hover {
  background: rgba(52,211,153,.16); border-color: rgba(52,211,153,.5); color: #a7f3d0;
  text-decoration: none;
}
/* A literal arrow, not a CSS hex escape. The escape crossed a shell heredoc and a Python
   string on its way into this file and arrived as an octal control character plus "92",
   which rendered as tofu beside the words. The character itself cannot be mangled again. */
body.app-dark .plans-hub-labs-link::after { content: '→'; font-weight: 400; }

/* Status. Right-aligned text, not a panel: a bordered box inside a bordered card is two frames
   deep, and this is three short facts. */
body.app-dark .plans-hub-status {
  text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
}
body.app-dark .plans-hub-status p {
  margin: 0; font-size: 12.5px; color: var(--ad-text-3); line-height: 1.4;
}
body.app-dark .plans-hub-badge {
  display: inline-flex; align-items: center; gap: 6px;
  margin-bottom: 4px; padding: 3px 10px 3px 9px; border-radius: 999px;
  background: rgba(52,211,153,.16); border: 1px solid rgba(52,211,153,.46);
  color: #a7f3d0; font-size: 11px; font-weight: 600; letter-spacing: .01em;
}
/* A dot before the word. "Active" alone is a label; a lit dot beside it is a state, and this card
   is the page's answer to "is my access working". */
body.app-dark .plans-hub-badge::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--ad-emerald); flex: 0 0 auto;
}

@media (max-width: 720px) {
  /* The status block goes under the facts rather than beside them, and left-aligns so it does not
     read as a stray right-hand column on a narrow screen. */
  body.app-dark .plans-hub { grid-template-columns: minmax(0, 1fr); }
  body.app-dark .plans-hub-status { text-align: left; align-items: flex-start; }
}
body.app-dark .plans-hub-k {
  margin: 0; font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ad-text-4);
}
body.app-dark .plans-hub-name {
  margin: 5px 0 0; font-size: 20px; font-weight: 600; color: #fff; letter-spacing: -.01em;
}
body.app-dark .plans-hub-scope {
  /* Brighter than --ad-text-2. This sentence is the answer to "what do I have", which is the
     question the page exists to answer, so it is not body copy. */
  margin: 5px 0 0; font-size: 13.5px; line-height: 1.5; color: rgba(255,255,255,.82);
}
/* The terms are the quietest line: a returning customer wants them confirmed, not announced. */
body.app-dark .plans-hub-terms {
  margin: 4px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ad-text-4);
}
body.app-dark .plans-hub-actions {
  margin: 14px 0 0; display: flex; gap: 10px; flex-wrap: wrap;
}

/* The cards a paid account cannot act on step back. Not disabled and not hidden - a Pro holder may
   still want to read what a Single Exam Pass covers - but they stop competing with the hub for
   first attention. Hover restores them, so nothing becomes unreadable.

   .88, was .78. Combined with the raised text colours above this is a step back rather than a
   barrier; .78 multiplied into .42 body copy was what made the whole row read as unavailable.

   `.popular` is EXEMPT. It is only ever set on the plan being recommended as an upgrade, so dimming
   it while also giving it the cyan edge that means "pick this" was two instructions at once, on the
   one card a pass holder is meant to consider. The recommendation keeps full strength; only the
   cards that are genuinely beside the point step back. */
body.app-dark.plans-paid #pricing-grid .pricing-card:not(.current):not(.popular) {
  opacity: .88; transition: opacity .18s ease;
}
body.app-dark.plans-paid #pricing-grid .pricing-card:not(.current):not(.popular):hover,
body.app-dark.plans-paid #pricing-grid .pricing-card:not(.current):not(.popular):focus-within {
  opacity: 1;
}
body.app-dark.plans-paid #pricing-grid .pricing-card.popular { opacity: 1; }

/* Plan-card actions, once somebody has paid. Four cards carrying solid cyan buttons put three equal
   primaries on the page and left the hero's own CTA - the one that knows which exam to resume - as
   just another cyan rectangle. Outlined here, solid there: the hierarchy gets stated once, in fill.

   Scoped to .plans-paid so the PUBLIC pricing page keeps its solid buttons. That page's whole job is
   the purchase, and quietening its CTAs would be the wrong trade entirely. */
body.app-dark.plans-paid .pricing-action .btn-primary {
  background: rgba(34,211,238,.10);
  border-color: rgba(34,211,238,.42);
  color: #67e8f9;
}
body.app-dark.plans-paid .pricing-action .btn-primary:hover {
  background: rgba(34,211,238,.18);
  border-color: rgba(34,211,238,.62);
  color: #a5f3fc;
}
/* Except on the recommended card, which keeps a filled button - quieter than the hero's, brighter
   than its neighbours'. This is the upgrade path and it should look like one. */
body.app-dark.plans-paid #pricing-grid .pricing-card.popular .pricing-action .btn-primary {
  background: rgba(34,211,238,.24);
  border-color: rgba(34,211,238,.72);
  color: #cffafe;
}
body.app-dark.plans-paid #pricing-grid .pricing-card.popular .pricing-action .btn-primary:hover {
  background: rgba(34,211,238,.36); border-color: #22d3ee; color: #fff;
}
/* The current card keeps full strength and its outline, so "this is the one you hold" survives the
   dimming around it. */
body.app-dark.plans-paid #pricing-grid .pricing-card.current { opacity: 1; }

@media (max-width: 620px) {
  body.app-dark .plans-hub-actions .btn { width: 100%; }
}
