/* page.css — the CMS CONTENT PAGE template (About, Privacy, Terms, Contact…).
   Page-specific only: the shell (nav, skip link, footer) comes from
   critical.css + common.css. Every token used here (--white, --yellow,
   --text-strong, --text-dim, --card-bdr, --bg-top, --bg-bot, --r-md, --r-lg)
   is declared in critical.css and nothing is redefined — that file is the
   single source of design tokens.

   THIS FILE DELIBERATELY MIRRORS .test-info / .test-info__inner IN
   typing-test.css. That block is the site's settled treatment for editor-
   authored content, and a second page inventing its own would make the two
   read as different sites. Values are copied, not re-derived; where something
   differs it is noted and the reason given.

   TWO THINGS TO KNOW BEFORE EDITING:

   1. NO AD RAIL, AND NO .page-layout. The content is a full-width band with
      its own centred column, exactly like .test-info — so the two-column
      content/ad grid never enters the picture and there is nothing to opt out
      of.

   2. THE BODY IS DATABASE CONTENT. Everything under .page-cms__inner is
      authored in TinyMCE and arrives with NO CLASSES ON ANY ELEMENT, so it is
      styled entirely through descendant selectors. Never add a class-based
      rule for anything inside that region — it will not match real content.

   RENAME ME. "page" says nothing about what this is. When this becomes the
   about/legal template proper, rename this file, page.html and the .page-*
   classes together. */

/* ══════════════════════════
   CMS REGION

   EVERY RULE BELOW TARGETS AN ELEMENT, NEVER A CLASS. The body is authored in
   TinyMCE and stored in the database, so it arrives as bare h2/p/ul/ol/table/a
   with no classes on anything. A rule like `.page-cms__lead` would simply never
   match once real content replaces the sample, and the page would silently lose
   its styling.

   ONE FIXED 960px COLUMN AT EVERY WIDTH — prose, lists and tables share the
   same left and right edge. The obvious alternative is to cap prose to a
   reading measure (~54ch, which measures 70 characters in Nunito here) and let
   tables break out to the full column. That is the usual editorial pattern and
   it is NOT used here, because typing-test.css records having tried exactly
   that and reverted it: at this content length the ragged left-to-right edge
   read worse than the long lines did. Do not re-introduce it without reading
   that note first.
══════════════════════════ */
/* top→bot, not bot→top as .test-info uses. That block sits BELOW a
   .page-layout band and alternates so the seam between them is invisible;
   this one meets the nav directly, and every band that does — .hero,
   .page-layout — starts at --bg-top. Never a flat colour on a dark
   background. */
.page-cms {
  padding: clamp(36px, 5vw, 60px) 16px clamp(44px, 6vw, 72px);
  background: linear-gradient(to bottom, var(--bg-top), var(--bg-bot));
}

.page-cms__inner {
  max-width: 960px;
  margin: 0 auto;
  padding: 40px;
  background: #262B42;
  border-radius: var(--r-lg, 18px);
  font-size: clamp(16px, 1.6vw, 17px);
  font-weight: 600;
  line-height: 1.8;
  /* NOT --text-dim. That token is white-at-58%, and this card lifts the
     background, so it would land at 5.75:1 here against 6.19:1 on a plain
     band. 0.62 restores it to 6.32:1 — the same fix, for the same reason, as
     .test-info__inner and .faq__a. */
  color: rgba(255, 255, 255, 0.62);
}

/* ── THE PAGE TITLE ──
   ABOVE the card, not inside it, which is where every other page on the site
   puts its h1 — and it keeps the card as pure editor output, so the "no
   classes in the CMS region" rule has no exception to carve out.

   max-width + auto margins because the band is full width: this lines the
   title up with the card's 960px column rather than the viewport.

   THESE SIX DECLARATIONS ARE THE SITE'S PAGE-H1, COPIED NOT CHOSEN.
   .profile-title, .cert__h1, .grade__h1, .tsel__h1 and .lesson__h1 all carry
   exactly this set. profile.css records an earlier version of that page
   inventing its own smaller, heavier heading and being visibly out of step —
   this file made the same mistake at clamp(28px, 3.4vw, 40px)/800 before it
   was caught. Do not re-tune them here; change them everywhere or nowhere. */
.page-title {
  max-width: 960px;                  /* the card's column */
  /* The bottom margin is the gap to the card, so the heading owns its own
     spacing — the same arrangement, and the same value, as .profile-title. */
  margin: 0 auto clamp(22px, 2.6vw, 32px);
  /* CENTRED, while the card's body stays left-aligned. Note that
     .profile-title is NOT centred — the six declarations below are what this
     page copies from it, not the alignment. text-wrap: balance earns its place
     here: centred text with a short last line is the case it exists for. */
  text-align: center;
  font-size: clamp(28px, 3.8vw, 52px);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.5px;
  color: #eef6ff;
  text-wrap: balance;
}
/* The accent word. .c-yellow is how the whole site marks it in the markup. */
.page-title .c-yellow { color: var(--yellow); }

/* A STRAY EDITOR h1 is styled to look like an h2, so picking the wrong block
   format does not also look broken — but the real fix is restricting TinyMCE's
   block formats. No :not() guard is needed: the page's own h1 lives outside
   this card, so every h1 matched in here is by definition a mistake. */
.page-cms__inner h1,
.page-cms__inner h2 {
  position: relative;
  padding-bottom: 15px;              /* room for the accent rule */
  font-size: clamp(22px, 2.4vw, 28px);
  font-weight: 800;
  line-height: 1.3;
  color: var(--text-strong);
}

/* Short accent rule under each major heading. A pseudo-element because the
   heading comes from the editor and cannot carry extra markup. h3 deliberately
   has none — the rule is what separates the two levels. */
.page-cms__inner h1::after,
.page-cms__inner h2::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 46px;
  height: 3px;
  border-radius: 2px;
  background: var(--yellow);
}

.page-cms__inner h3 {
  font-size: clamp(18px, 1.8vw, 21px);
  font-weight: 800;
  line-height: 1.35;
  color: var(--text-strong);
}

/* Spacing as SIBLING RELATIONSHIPS rather than fixed margins on each element:
   an editor can reorder blocks freely, and `> * + *` covers every pairing —
   p+ul, ul+p, table+h2 — including combinations nobody enumerated. */
.page-cms__inner > * + *  { margin-top: 14px; }
.page-cms__inner > * + h2 { margin-top: 40px; }
.page-cms__inner > * + h3 { margin-top: 34px; }
.page-cms__inner > h1 + *,
.page-cms__inner > h2 + *,
.page-cms__inner > h3 + * { margin-top: 12px; }

/* text-wrap: pretty stops a single word being orphaned on the last line. */
.page-cms__inner > p { text-wrap: pretty; }

/* The reset in critical.css sets `ul{list-style:none}`, so a list from the CMS
   arrives with NO marker — and `li::marker` cannot bring one back once the
   list-style is gone. A dash drawn with ::before restores it and echoes the
   heading rule. `ol` is untouched by that reset and keeps its numbers: an
   editor who picks a numbered list wants the numbers. */
.page-cms__inner ul { padding-left: 0; }
.page-cms__inner ul > li {
  position: relative;
  padding-left: 22px;
}
.page-cms__inner ul > li::before {
  content: '–';
  position: absolute;
  left: 2px;
  top: 0;
  font-weight: 800;
  color: var(--yellow);
}
.page-cms__inner ol { padding-left: 22px; }
.page-cms__inner ol > li::marker { color: var(--yellow); font-weight: 800; }
.page-cms__inner li + li { margin-top: 8px; }

.page-cms__inner strong { font-weight: 800; color: var(--text-strong); }
.page-cms__inner em { font-style: italic; }

.page-cms__inner a {
  color: var(--yellow);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.page-cms__inner a:hover { color: #ffffff; }

/* ── Tables ──
   The element most likely to break a CMS block, because a table needs far more
   width than anything else an editor can insert.

   `separate`, not `collapse`: border-radius is silently ignored on a collapsed
   table. Cells carry only a bottom border so the seams still read as one line. */
.page-cms__inner table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid var(--card-bdr);
  border-radius: var(--r-md);
  overflow: hidden;                  /* clips the header fill to the radius */
  font-size: 15px;
  text-align: left;
}

/* TinyMCE puts the caption above the table, and it doubles as the table's
   accessible name — so it stays in the flow rather than being hidden. */
.page-cms__inner caption {
  caption-side: top;
  padding-bottom: 10px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-dim);
  text-align: left;
}

.page-cms__inner th,
.page-cms__inner td {
  padding: 13px 16px;
  border-bottom: 1px solid var(--card-bdr);
  vertical-align: top;
  text-align: left;
}
.page-cms__inner tr:last-child th,
.page-cms__inner tr:last-child td { border-bottom: 0; }

.page-cms__inner thead th {
  background: rgba(255, 255, 255, 0.05);
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-strong);
  white-space: nowrap;
}

/* The row label is the first cell whether the editor emits <th scope="row"> or
   a plain <td> — TinyMCE does not reliably add the th, so both are styled. */
.page-cms__inner tbody th,
.page-cms__inner tbody td:first-child {
  font-weight: 800;
  color: var(--text-strong);
}
.page-cms__inner tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.02); }

@media (max-width: 760px) {
  /* 40px of card padding inside 16px of band padding leaves under 290px of
     content on a 360px phone. */
  .page-cms__inner { padding: 24px 18px; }

  /* `display: block` wraps the rows in an ANONYMOUS TABLE BOX, so thead/tbody
     column alignment survives while the element scrolls sideways. A wrapper div
     would be cleaner, but the CMS cannot produce one. */
  .page-cms__inner table {
    display: block;
    overflow-x: auto;
    font-size: 14px;
  }
}

/* ══════════════════════════
   ENTRY ANIMATION

   THE typing-test.html HEADING ENTRANCE, copied value for value — 0.9s on
   cubic-bezier(0.22, 1, 0.36, 1) from translateY(20px) at 0.1s, with the accent
   word floating in separately at 0.55s, and the block below it at 0.75s. The
   keyframes below are identical to testHeadIn. Do not re-tune them here.

   THREE STEPS: heading, accent word, card. The card animates as a WHOLE, not
   its contents — animating the contents paints the card's background and border
   at full opacity from the first frame, and a card that fills in afterwards
   reads as a loading failure.

   The heading and the card are SIBLINGS, which is what makes this safe. An
   earlier version had the h1 inside the card and could only animate the card
   alone, because animating both compounds their transforms. If the h1 ever
   moves back inside, this has to collapse to one step again.

   LCP COST, AND IT IS REAL. An element starting at opacity 0 records its
   Largest Contentful Paint when the animation ENDS, not when it first paints.
   The heading finishes at 1.0s and the card at 1.65s, so whichever of the two
   is the LCP element, that is the number. typing-test.html pays the same
   1.65s — but its 0.75s block is one short paragraph, whereas here it is the
   page's entire content. If Core Web Vitals matter more than the entrance on
   this template, cut the card's delay to ~0.25s: the heading still leads and
   the accent word still lands last.

   KEYFRAME NAME: page-local, like profileFadeUp / dashFadeUp / teachFadeUp /
   lessonHeadIn. That is now a fifth near-identical copy site-wide; worth
   lifting one into critical.css at some point, but not as a side effect of
   building this page.
══════════════════════════ */
@keyframes pageFadeUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The heading, as a SEPARATE --anim MODIFIER rather than baked into
   .page-title. That is how .test__h1 / .test__h1--anim splits it, and the
   reason is that the static rule must leave the heading readable on its own:
   the opacity: 0 lives with the animation that clears it, never without it. */
.page-title--anim {
  opacity: 0;
  transform: translateY(20px);
  animation: pageFadeUp 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.1s forwards;
}

/* The accent word floats in just after the heading settles. .float-in in
   critical.css does the work — 1.2s of gentleFloat from translateY(16px) — and
   only its delay is overridden here, exactly as .test__h1--anim does it.

   Its 16px rides ON TOP of the heading's 20px, so the word starts 36px down and
   catches up. That compounding is the effect, not a bug — it is why the word
   arrives separately instead of moving as part of the line. */
.page-title--anim .float-in {
  animation-delay: 0.55s;
}

/* 0.75s, so the card lands after the accent word rather than through it. Same
   value .test__intro uses for the block below that page's heading. */
.page-cms__inner {
  opacity: 0;
  animation: pageFadeUp 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.75s forwards;
}

/* Everything visible immediately, no motion. MUST come after the rules above:
   a media query adds no specificity, so source order is the only thing
   deciding between two equal-specificity declarations. transform: none is
   needed as well as animation: none, because --anim sets a STATIC 20px offset
   that would otherwise be left in place.

   .float-in IS LISTED HERE ON PURPOSE, and typing-test.css does not list it.
   Measured on that page under reduced motion: the h1 stops, but the accent
   word still runs gentleFloat at opacity 0 and floats in — the modifier's
   `animation: none` does not reach a child element, and critical.css has no
   reduced-motion block of its own. Worth fixing there too. */
@media (prefers-reduced-motion: reduce) {
  .page-title--anim,
  .page-title--anim .float-in,
  .page-cms__inner {
    opacity: 1;
    transform: none;
    animation: none;
  }
}
