/* contact-us.css — the Contact page.
   Page-specific only: the shell (nav, skip link, footer) comes from
   critical.css + common.css. Every token used here (--yellow, --yellow-dk,
   --navy, --white, --blue, --green, --text-dim, --card-bdr, --bg-top,
   --bg-bot, --r-md, --r-lg, --r-pill, --ff) is declared in critical.css and
   nothing is redefined — that file is the single source of design tokens.

   WHY THIS IS NOT page.css. page.html is the template for pages whose whole
   body is written in TinyMCE, and page.css styles that editor output through
   descendant selectors. This page's centre is a FORM — application markup with
   names, validation and a handler — so none of that applies.

   WHAT IS SHARED, AND IT IS COPIED DELIBERATELY: the band-plus-card shape, the
   card surface, the centred two-tone h1 and the typing-test heading entrance.
   Those are what make the two pages read as one site.

   ON THE DUPLICATION: this is the SECOND copy of the band/card/heading block
   (page.css has the first). Two is fine — the rule on this project is that
   page-local beats shared until most pages want it, and common.css loads on
   every one of the 17 pages. A THIRD copy is the point to stop and lift the
   band, the card surface and the page-h1 into one shared place. Not before. */

/* ══════════════════════════
   BAND

   top→bot, because this band meets the nav directly and every band that does —
   .hero, .page-layout, .page-cms — starts at --bg-top. Never a flat colour on
   a dark background.
══════════════════════════ */
.contact {
  padding: clamp(36px, 5vw, 60px) 16px clamp(44px, 6vw, 72px);
  background: linear-gradient(to bottom, var(--bg-top), var(--bg-bot));
}

/* ══════════════════════════
   PAGE TITLE

   Above the card, which is where every other page on the site puts its h1.
   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, .lesson__h1, .test__h1 and
   .page-title all carry exactly this set, and profile.css records what happens
   when a page invents its own: visibly out of step with every other page. Do
   not re-tune them here; change them everywhere or nowhere.

   CENTRED, like .page-title and .test-hero. Note .profile-title is NOT centred
   — the alignment is a per-page call, the six declarations are not.
══════════════════════════ */
.contact-title {
  max-width: 960px;                  /* the card's column */
  /* The bottom margin is the gap to the card, so the heading owns its own
     spacing — same arrangement and same value as .page-title. */
  margin: 0 auto clamp(22px, 2.6vw, 32px);
  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. */
.contact-title .c-yellow { color: var(--yellow); }

/* ══════════════════════════
   CARD

   Same surface as .page-cms__inner and .test-info__inner: 960px, 40px of
   padding, #262B42, --r-lg.

   NOT --text-dim for the text colour. 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 .page-cms__inner, .test-info__inner and .faq__a.
══════════════════════════ */
.contact__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;
  color: rgba(255, 255, 255, 0.62);
}

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

/* ══════════════════════════
   FORM FIELDS — PAGE-LOCAL ON PURPOSE.

   These MIRROR .field__* in profile.css value for value, which in turn mirrors
   .login-form__* in auth.css. They are not invented here and they are not
   shared: profile.css records an earlier attempt to put field styles in
   common.css being reverted, because common.css loads on all 17 pages and only
   a handful carry a form.

   If the duplication ever has to be resolved, the answer is a forms stylesheet
   loaded only by the pages with forms — never common.css.

   SPACING IS NOT ON THE FIELD. A field carries no outer margin, so
   .contact-form sets the rhythm — otherwise a field inside .field-row fights
   the sibling margin meant for a stacked one.
══════════════════════════ */
.contact-form > * + * { margin-top: 18px; }

/* Name and email share a row on desktop; everything else is full width. */
.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
}
@media (max-width: 620px) {
  .field-row { grid-template-columns: minmax(0, 1fr); }
}

/* min-width:0 is not cosmetic. Inside .field-row the fields are grid items, and
   a grid item defaults to min-width:auto — it refuses to shrink below its
   content, so one long value would push the pair wider than the 1fr tracks
   allow instead of the input scrolling its own text. */
.field { min-width: 0; }

.field__label {
  display: block;
  margin-bottom: 7px;
  font-size: 13px;
  font-weight: 800;
  color: rgba(255, 255, 255, 0.88);
}

/* The asterisk is decoration: the `required` attribute is what tells a screen
   reader the field is required, and without aria-hidden it would also read out
   "star" on every label. */
.field__req {
  color: var(--yellow);
  margin-left: 2px;
}

/* position:relative so the icon and the caret can sit inside the control
   rather than beside it. */
.field__control {
  position: relative;
  display: block;
}

/* pointer-events:none so clicking the icon still focuses the input under it —
   without it the icon is a dead zone in the middle of a control. */
.field__icon {
  position: absolute;
  top: 50%;
  left: 15px;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--text-dim);
  pointer-events: none;
  transition: color 0.18s;
}
/* focus-within on the WRAPPER, not :focus on the input — the icon precedes the
   input in the DOM, so no sibling combinator can reach back to it. */
.field__control:focus-within .field__icon { color: var(--blue); }

.field__input {
  width: 100%;
  font-family: var(--ff);
  font-size: 15px;
  font-weight: 600;
  color: var(--white);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--card-bdr);
  border-radius: var(--r-md);
  /* 13px 15px is the site's field padding; the left side opens up to clear the
     18px icon plus its 15px inset. */
  padding: 13px 15px 13px 46px;
  transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
  /* Scoped to the field, not :root, so nothing else on the site shifts.
     Chrome's autofill background is light-dark(near-white, slate) — without a
     declared scheme it assumes light and fills saved values near-white, which
     no author rule can override (it is UA !important). Declaring dark here
     makes it pick the dark half. Copied from profile.css / auth.css.

     IT ALSO FIXES THE SELECT DROP-DOWN. The open list is painted by the OS,
     not by CSS, so without color-scheme it renders as the light list in the
     reference screenshot — dark text on white, in the middle of a dark page. */
  color-scheme: dark;
}
.field__input::placeholder { color: rgba(255, 255, 255, 0.40); }
.field__input:focus {
  outline: none;
  border-color: var(--blue);
  background: rgba(255, 255, 255, 0.06);
  box-shadow: 0 0 0 3px rgba(79, 134, 247, 0.22);
}

/* ── Select ──
   appearance:none removes the native arrow so the caret below can be drawn on
   theme; the right padding reserves room for it, and text-overflow keeps a long
   option from running underneath. */
.field__select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 44px;
  cursor: pointer;
  text-overflow: ellipsis;
}
/* The placeholder option reads as placeholder text, not as a chosen value.
   :invalid matches it because it is value="" on a required select. */
.field__select:invalid { color: rgba(255, 255, 255, 0.40); }

/* ── THE OPEN LIST ──
   STYLE <option> DIRECTLY. This is not belt-and-braces on color-scheme, it is
   the fix.

   The popup is painted by the platform, not by this stylesheet.
   `color-scheme: dark` on the field ASKS for a dark popup, and on some engines
   that is enough — on Windows Chrome it is not. The popup stayed white while
   every <option> inherited the select's `color: var(--white)`, so four of the
   five options rendered white-on-white and were invisible; only the greyed
   placeholder and the hovered row could be read at all.

   option background/color ARE honoured inside the popup on every current
   engine, so setting them explicitly is what actually lands.

   NOT color-scheme on :root, which would fix the popup and also repaint
   scrollbars and every form control on all 17 pages. profile.css scoped the
   same property to the field for exactly that reason. */
.field__select option {
  background-color: #1f2438;
  color: #eef6ff;
  font-weight: 600;
}
/* The placeholder row stays placeholder-coloured inside the list too, so it
   does not read as a sixth choice.

   0.55, NOT 0.45. A disabled option is an inactive control and so exempt from
   the 4.5:1 minimum, but 0.45 measured 4.01:1 against the popup and there is
   no reason to sit under the bar on an exemption — 0.55 is 5.27:1 and still
   visibly recessed against the 14.08:1 of the real choices. */
.field__select option[disabled] {
  color: rgba(238, 246, 255, 0.55);
}

.field__caret {
  position: absolute;
  top: 50%;
  right: 15px;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--text-dim);
  /* Without this the caret is a dead spot that swallows the click that should
     open the menu. */
  pointer-events: none;
  transition: color 0.18s;
}
.field__control--select:focus-within .field__caret { color: var(--blue); }

/* ── Textarea ──
   No icon, so no left inset to clear. Vertical-only resize: horizontal resize
   lets a visitor drag it past the card edge. */
.field__textarea {
  padding-left: 15px;
  min-height: 150px;
  line-height: 1.7;
  resize: vertical;
  display: block;
}

/* ══════════════════════════
   HONEYPOT

   NOT display:none and NOT [hidden]. A number of bots skip fields hidden that
   way, which defeats the entire trap — the point is that a bot DOES fill it.
   Clipped to a 1px box instead: invisible to people, present to a parser.
══════════════════════════ */
.contact-form__trap {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ══════════════════════════
   SUBMIT

   The site's primary-action treatment: the coral gradient on navy that
   .btn-primary already uses. This is the one coral thing in the card, which is
   the test --yellow has to pass on any page.
══════════════════════════ */
.contact-form__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-width: 190px;
  padding: 14px 30px;
  font-family: var(--ff);
  font-size: 15px;
  font-weight: 800;
  color: var(--navy);
  background: linear-gradient(135deg, var(--yellow), var(--yellow-dk));
  border: none;
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: transform 0.16s ease, box-shadow 0.16s ease, filter 0.16s ease;
}
.contact-form__submit:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(255, 140, 97, 0.24);
}
.contact-form__submit:focus-visible { outline: 2px solid #eef6ff; outline-offset: 3px; }
/* While sending. Dimmed rather than recoloured, so the button does not change
   identity mid-action; not-allowed says the click will not land. */
.contact-form__submit:disabled {
  cursor: not-allowed;
  filter: saturate(0.55) brightness(0.9);
  transform: none;
  box-shadow: none;
}

/* ══════════════════════════
   INTRO

   Inherits size, weight and line-height from .contact__inner, so only the
   spacing to the form is set here. The colour is inherited too: it is the
   card's 0.62 body text, the same as the prose in page.css.

   NOTE: the Web3Forms/privacy line that used to sit under the button was
   removed at the user's request. The DISCLOSURE OBLIGATION DID NOT GO WITH IT
   — submissions still pass through a third party, so Web3Forms has to be
   named as a processor in /privacy. It just is not named on this page.
══════════════════════════ */
.contact-intro {
  margin: 0 0 26px;
  /* Prose, so it gets a reading measure even though the fields below run the
     full width of the card — a 880px line of body copy is about 114
     characters. This is the one block here that is a paragraph rather than a
     control. */
  max-width: 62ch;
}

/* ══════════════════════════
   SUCCESS AND ERROR MESSAGES

   Structure copied from .form-alert in profile.css so a confirmation looks the
   same wherever it appears on the site. Only the colour differs by outcome.

   THE ERROR IS NOT #dc3545, AND THAT IS DELIBERATE. profile.css records the
   danger zone being built with #dc3545 and toned down because it glares on
   dark navy — the user then had the coral removed too. #dc3545 measures
   3.08:1 on this card, well under the 4.5:1 a message has to meet.

   MEASURE AGAINST THE ALERT'S OWN TINT, NOT THE CARD. The 0.10 background
   lifts the backdrop, so a colour that passes on the card can still fail in
   place — #e5787f was picked first at 4.86:1 on the card and turned out to be
   4.22:1 where it actually sits. #f5abb2 is 6.12:1 on its tint and 7.53:1 on
   the card, under the 8.94:1 brightness ceiling --text-strong sets, and it
   lands within 0.1 of the success alert's 6.22:1 so the two read as equally
   present.

   IT DOES NOT SEPARATE CLEANLY FROM THE CORAL ACCENT and it cannot: every red
   light enough to pass here lands within ΔE 14 of #FF8C61 (this one is 10.2),
   because the site's accent already occupies that part of the space. That is
   acceptable HERE and would not be on a chart: this is a bordered block with
   an icon and a full sentence, so the outcome never rests on hue alone.
══════════════════════════ */
.form-alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0 0 20px;
  padding: 13px 16px;
  border-radius: var(--r-md);
  font-size: 14px;
  font-weight: 700;
  line-height: 1.6;
}
/* Needed because the flex display above would otherwise beat the [hidden]
   attribute's UA display:none. */
.form-alert[hidden] { display: none; }

.form-alert__icon {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  /* Nudged onto the first line's optical centre; align-items is flex-start so
     the icon stays put when the message wraps to two lines. */
  margin-top: 3px;
}

.form-alert--err {
  border: 1px solid rgba(245, 171, 178, 0.32);
  background: rgba(245, 171, 178, 0.10);
  color: #f5abb2;
}
.form-alert--err a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ══════════════════════════
   SUCCESS PANEL — REPLACES THE FORM

   Not a banner above a live form. A confirmation sitting on top of the fields
   that produced it is ambiguous — the form is right there, so a second click
   sends a duplicate. The intro goes with it, because it says "use the form
   below".

   The ERROR stays a banner, and the asymmetry is deliberate: a failed send
   leaves the message in the fields, and hiding them would destroy the thing
   the visitor needs in order to retry.

   RESTRAINED, NOT A GREEN BLOCK. Only the icon carries the colour; the
   heading is the card-heading white and the sentence is the card's own body
   text. A full green panel would be the brightest thing on the page, which is
   the opposite of what this site does with colour — the same reasoning that
   took the red out of profile.css's danger zone.
══════════════════════════ */
.contact-success {
  display: grid;
  justify-items: center;
  text-align: center;
  /* Generous, because this is now the card's whole content — without it the
     960px card collapses to a thin strip the moment the form is hidden. */
  padding: clamp(28px, 4vw, 44px) 20px;
  /* The swap is otherwise instant, and an instant swap of the card's whole
     contents reads as a page reload rather than as a result.

     NO EXPLICIT TRIGGER NEEDED: going from display:none to display:grid
     restarts a CSS animation on its own, so this plays every time the panel is
     revealed, including on a second send.

     SHORTER AND SHALLOWER THAN THE PAGE ENTRANCE (0.45s / 12px against 0.9s /
     20px). This is a response to a click, not an arrival — the visitor is
     already looking at this spot and waiting, so the same leisurely curve
     would read as lag. */
  animation: contactPanelIn 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Needed because the grid display above would otherwise beat the [hidden]
   attribute's UA display:none. */
.contact-success[hidden] { display: none; }

/* NO FOCUS RING ON THIS PANEL, and it is not an oversight.

   The script focuses it so the reading position follows the content when the
   form disappears, but it carries tabindex="-1": it is not in the tab order,
   not an operable control, and therefore outside WCAG 2.4.7, which applies to
   keyboard-operable interfaces.

   It was written as :focus first, which drew a 2px ring around the entire
   panel — a white box round the confirmation that reads as a rendering fault.
   Switching to :focus-visible did NOT fix it: Chrome treats programmatic focus
   as keyboard-driven whenever it cannot attribute the move to a pointer, so
   the ring still painted. The heuristic is not something to rely on here.

   What replaces it: the focus move itself is what a screen reader announces,
   and Send another message below has its own clear ring the moment anyone
   tabs to it. */
.contact-success { outline: none; }

.contact-success__icon {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  border: 1px solid rgba(52, 211, 153, 0.30);
  background: rgba(52, 211, 153, 0.10);
  color: var(--green);
  /* Lands just after the panel, so the tick reads as confirming rather than as
     part of the furniture arriving. 0.12s is the whole beat — any longer and
     it is a second animation the visitor has to wait through.

     The scale composes with the panel's translate rather than fighting it:
     different properties on nested elements, which is the case where that is
     fine. The panel's own transform is what MUST NOT be duplicated here. */
  animation: contactTickIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.12s both;
}
.contact-success__icon svg { width: 26px; height: 26px; }

.contact-success__title {
  margin: 16px 0 0;
  font-size: clamp(20px, 2.2vw, 25px);
  font-weight: 800;
  line-height: 1.25;
  color: #eef6ff;
}

.contact-success__text {
  margin: 9px 0 0;
  max-width: 44ch;
  /* Inherits the card's size, weight and 0.62 colour. */
}

/* Secondary, not the coral primary: sending another message is an escape
   hatch, not the thing we want them to do next. */
.contact-success__again {
  margin-top: 22px;
  padding: 11px 24px;
  font-family: var(--ff);
  font-size: 14px;
  font-weight: 800;
  color: #eef6ff;
  background: transparent;
  border: 1px solid var(--card-bdr);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: border-color 0.16s, background 0.16s;
}
.contact-success__again:hover {
  border-color: rgba(219, 238, 255, 0.34);
  background: rgba(255, 255, 255, 0.05);
}
.contact-success__again:focus-visible { outline: 2px solid #eef6ff; outline-offset: 3px; }

/* ══════════════════════════
   WHEN THE FORM CHANGES, READ THIS FIRST

   Things already decided, recorded so they are not rediscovered:
     - FIELD STYLES STAY IN THIS FILE. See the note above the field block.
     - The honeypot must stay clipped, never display:none.
     - Every input has a real <label>; a placeholder is not a label.
     - The success state is a page region with role="status" and the error is
       role="alert" — never an alert() dialog.
     - No CAPTCHA. The honeypot, Web3Forms' own filtering and the time-trap in
       the page script are enough at this volume, and hCaptcha is one of the
       heaviest third-party scripts there is.
══════════════════════════ */

/* ══════════════════════════
   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. Identical to page.css, which
   is the point.

   THE CARD'S DELAY IS 0.75s, THE SAME AS page.css. That is what makes the
   entrance strictly sequential rather than overlapping: measured by scrubbing
   the timeline, the h1 is fully in at 750ms and the card's first visible frame
   is 875ms, so the heading always finishes before the content starts. An
   earlier version used 0.25s and the card began at 375ms, over the top of an
   h1 that was only ~80% in.

   THE COST, RECORDED SO IT IS NOT REDISCOVERED: an element starting at
   opacity 0 records its Largest Contentful Paint when the animation ENDS, so
   this card's delay is added on top of render time and it lands at 1.65s
   against a 2.5s LCP budget. NOW THAT THE FORM IS IN, this card is heavier
   than the placeholder it was measured with — if it becomes the LCP element,
   0.55s buys 0.2s back and still reads as heading-then-content (the h1 is
   ~95% in by then).
══════════════════════════ */
@keyframes contactFadeUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The success swap. Separate keyframes from contactFadeUp rather than a reused
   one at a different duration, because the distance differs too — 12px, not
   20px. */
@keyframes contactPanelIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The tick. Slight overshoot in the easing, so it settles rather than stopping
   dead — the one place on this page where a spring curve earns its keep. */
@keyframes contactTickIn {
  from { opacity: 0; transform: scale(0.8); }
  to   { opacity: 1; transform: scale(1); }
}

/* A SEPARATE --anim MODIFIER rather than baked into .contact-title: the
   opacity: 0 must always travel with the animation that clears it, so the
   static rule leaves the heading readable on its own. */
.contact-title--anim {
  opacity: 0;
  transform: translateY(20px);
  animation: contactFadeUp 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. */
.contact-title--anim .float-in {
  animation-delay: 0.55s;
}

.contact__inner {
  opacity: 0;
  animation: contactFadeUp 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 and floats in — a modifier's `animation: none`
   does not reach a child element, and critical.css has no reduced-motion block
   of its own. */
@media (prefers-reduced-motion: reduce) {
  .contact-title--anim,
  .contact-title--anim .float-in,
  .contact__inner,
  /* The success swap too. `both` fill means these hold their `from` frame if
     the animation is cancelled rather than replaced, so opacity and transform
     have to be restored explicitly — animation: none alone would leave the
     panel invisible and the tick at 0.8. */
  .contact-success,
  .contact-success__icon {
    opacity: 1;
    transform: none;
    animation: none;
  }
  .contact-form__submit, .contact-success__again { transition: none; }
  .contact-form__submit:hover { transform: none; }
}
