/* ═══════════════════════════════════════════════════════════════
   auth.css — the auth pages: /register, /login, /forgot-password,
   /reset-password and /contact.

   Loaded ONLY by templates/layouts/auth.php, and last, after
   variables / base / components / layout / utilities.

   ── EVERY RULE IS SCOPED UNDER .auth-v2 ──────────────────────
   That is not tidiness, it is the safety property that lets this
   file exist at all. It restyles shared class names (.form-input,
   .btn, .password-strength-track, .form-check) that the entire
   rest of the app uses. Without the scope, loading this sheet on
   one page would change the look of every form in the product.

   With it, the blast radius is exactly the pages that opt in by
   using the auth layout. Every other page in the app renders off
   layout.css and cannot be touched by anything in here.

   ── ON THE NAME .auth-v2 ─────────────────────────────────────
   Historical: it dates from the rebuild that ran beside the old
   auth pages until those were retired. There is no v1 any more.
   The name is kept because it is the namespace of ~200 selectors
   in this file plus three page scripts that query it, and renaming
   it would be a mechanical edit across four files whose blast
   radius is the login form and whose payoff is a nicer word. What
   matters is not the name, it is that the scope exists.

   RULE FOR ANYONE EDITING THIS FILE: if a selector does not start
   with .auth-v2, it is a bug, even if it looks harmless.

   ── WHAT IS DELIBERATELY NOT IN HERE ─────────────────────────
   The base form controls. components.css already gives .form-input
   its fill, border, radius, focus ring and invalid state, and
   layout.css already gives .auth-alert, .password-toggle and the
   .success-* checkmark animation theirs. Re-declaring those would
   be a second copy to keep in sync for no gain. Only the things
   this design genuinely changes are below.
   ═══════════════════════════════════════════════════════════════ */

/* The page background, set on <body> so it reaches the CANVAS: the
   strip behind the status bar and the area iOS exposes when a page is
   rubber-banded past its top. base.css paints the body
   --color-bg-secondary (#F9FAFB), which is right for the app shell and
   wrong here, where the page itself is pure white and the seam shows.

   Only pages rendered through templates/layouts/auth.php carry
   this class. */
/* ── THE SCROLLBAR MUST NOT BE ALLOWED TO CHANGE THE LAYOUT ────
   Every width on these pages is a fraction of the viewport: the shell
   splits into 0.85fr and 1fr, so the panel is 0.85/1.85 of whatever
   space there is. A space-taking scrollbar removes some of that space
   and the panel shrinks with it.

   /register is the only auth page whose form is taller than the
   viewport, so it was the only one that HAD a scrollbar, and its panel
   measured 2.5px narrower than the identical panel on /login. Measured
   on a screenshot, not estimated: right edge at 660.5px vs 663.0px.

   THE SCROLLBAR HERE IS 6px, NOT THE ~15px THE PLATFORM WOULD USE, and
   that number comes from base.css: `::-webkit-scrollbar { width: 6px }`
   is declared globally, unscoped. 0.85/1.85 x 6 = 2.8px, which is the
   difference above. That rule is also the reason this is visible on a
   Mac at all: macOS draws OVERLAY scrollbars that cost no layout width,
   but styling ::-webkit-scrollbar opts the page out of them and back
   into classic space-taking ones. On Windows it was always this way.

   The fix is to stop /register being the only page that pays: force the
   scrollbar to exist everywhere in this layout, so all five auth pages
   measure the same viewport. It costs 6px of width on the pages that do
   not scroll and nothing else. Nothing is drawn there: base.css makes
   the track transparent, and a scroll container with nothing to scroll
   gets no thumb, so the reserved strip is plain white.

   ── ON body, NOT ON html, AND THAT IS LOAD-BEARING ────────────
   The root's overflow is visible, so the viewport takes its overflow
   from body instead, and the element it was taken FROM gets a used
   value of visible. body therefore never becomes a scroll container
   and the sticky panel keeps the viewport as its scrolling ancestor.

   Put the same declaration on html and that inverts: the root stops
   being visible, so it propagates its own overflow, body keeps the
   `overflow-x: hidden` base.css gives it, and body turns into a scroll
   container of full content height. The panel's nearest scrolling
   ancestor would then be a box it can never move inside, and
   position:sticky in the desktop block below would silently degrade to
   position:relative: the panel would scroll away on /register and leave
   an empty white column.

   ── WHAT WAS TRIED FIRST AND DOES NOT WORK ────────────────────
   `scrollbar-gutter: stable` on the root, which is the modern answer to
   exactly this problem and reserves the space without forcing a
   scrollbar. It changed nothing in Chrome, because Chromium does not
   feed a custom ::-webkit-scrollbar width back into the gutter
   calculation, so the gutter it reserved was zero. It is the tidier
   property and it will look like an obvious improvement to whoever
   reads this next; it is not, unless the global rule in base.css goes
   first. */
body.auth-v2-page {
  background-color: var(--color-bg);
  overflow-y: scroll;
}

.auth-v2 {
  /* ── brand accents, lifted from landing.css so the auth pages and
        the landing page speak with the same voice ───────────────── */
  --au-lav: #6C5CE0;

  /* ── auth-only surface tokens ──────────────────────────────── */
  --au-panel-ink: #171326;          /* text on the mesh panel */
  --au-panel-ink-2: #4A4460;
  --au-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");

  --font-display: 'Instrument Sans', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* ── THE INSET THE PANEL FLOATS IN ─────────────────────────
     ONE number, read in two places that MUST agree: the padding on
     .au-shell, which is the white margin around the whole split view,
     and the height of .au-aside, which is the viewport minus that
     margin twice. Written as a variable rather than twice as a literal
     because the two are the same measurement, and the desktop
     breakpoints below change it three times. A hard-coded 16px in the
     height calc would silently stop matching the padding at >= 1280px
     and the panel would hang 4px past the bottom inset there only.

     0 on phones, where there is no inset and no panel. */
  --au-gutter: 0px;
}

[data-theme="dark"] .auth-v2 {
  --au-lav: #A78BFA;
  --au-panel-ink: #F3F1FA;
  --au-panel-ink-2: #B6B0CC;
  /* The tick is drawn ON the checkbox, and a checked box is filled
     with --color-primary, which is white in dark mode. A white tick
     on white would vanish. */
  --au-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230A0A0A' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* :not(.btn) is load-bearing, not tidiness.
   `.auth-v2 a` scores (0,1,1) and `.btn-primary` scores (0,1,0), so a
   blanket `color: inherit` here WINS against the button colour and any
   <a class="btn btn-primary"> renders its label in body-text colour on
   a black fill. That is exactly what happened to "Go to the app" on the
   success screen: black on black, invisible.
   Excluding .btn lets components.css keep ownership of button colour. */
.auth-v2 a:not(.btn) { color: inherit; text-decoration: none; }
.auth-v2 a { text-decoration: none; }
.auth-v2 svg { display: block; }

/* ── focus ────────────────────────────────────────────────────
   Form controls are EXCLUDED here. components.css already gives
   them a focus treatment: the border goes to --color-primary and a
   3px --shadow-focus ring appears, both neutral, and that is the
   look the rest of the app uses.

   Adding a lavender :focus-visible outline on top produced two
   concentric rings in two different colours on every click. Worse,
   :focus-visible matches text inputs on MOUSE click too (browsers
   treat typing fields as always needing a visible indicator), so it
   was not even limited to keyboard users.

   Everything that is not a form control keeps the outline, because
   links and icon buttons have no other focus treatment at all. */
.auth-v2 :focus-visible {
  outline: 2px solid var(--color-text-primary);
  outline-offset: 2px;
  border-radius: 6px;
}

.auth-v2 .form-input:focus-visible,
.auth-v2 .form-select:focus-visible,
.auth-v2 .form-textarea:focus-visible,
.auth-v2 input[type="checkbox"]:focus-visible {
  outline: none;
}

/* The checkbox loses the outline above, so it needs the same ring the
   inputs get, or it becomes the one control with no focus state. */
.auth-v2 input[type="checkbox"]:focus-visible {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-focus);
}

/* ═══ page shell ═══════════════════════════════════════════════
   FULL BLEED, at every width. There is no card, no max-width and no
   outer padding: the two columns are the page.

   The earlier version floated a 1120px rounded card on a tinted
   backdrop. It reads as a component sitting on a page rather than as
   the page itself, and on a large monitor most of the screen was
   backdrop. A sign-up screen is the whole task, so it gets the whole
   viewport, which is what Linear, Vercel and Qonto all do.

   One consequence worth knowing: the gradient panel now runs edge to
   edge, so it is a much larger area of colour. That is why the mesh
   below is tuned soft rather than saturated. */
.auth-v2 {
  font-family: var(--font-sans);
  background: var(--color-bg);
  color: var(--color-text-primary);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.auth-v2 .au-shell {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  background: var(--color-bg);
  padding: var(--au-gutter);
  min-height: 100vh;
  /* svh, not vh, on the second declaration: on iOS Safari 100vh is the
     height WITHOUT the address bar, so a full-height shell is always
     slightly taller than the screen and the page rubber-bands even when
     there is nothing to scroll. Browsers that do not know svh keep the
     vh line above. */
  min-height: 100svh;
}

/* ═══ LEFT · brand panel (desktop only) ════════════════════════ */
.auth-v2 .au-aside { display: none; }

.auth-v2 .au-mesh {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(64% 50% at 4% 2%, rgba(255,255,255,.92), rgba(255,255,255,0) 62%),
    radial-gradient(92% 74% at 26% 64%, rgba(108,92,224,.56), rgba(108,92,224,0) 74%),
    radial-gradient(80% 64% at 98% 4%, rgba(134,110,240,.38), rgba(134,110,240,0) 72%),
    radial-gradient(86% 70% at 94% 100%, rgba(92,76,214,.36), rgba(92,76,214,0) 74%),
    #F6F4FE;
}

[data-theme="dark"] .auth-v2 .au-mesh {
  background:
    radial-gradient(64% 50% at 4% 2%, rgba(180,166,255,.13), rgba(180,166,255,0) 62%),
    radial-gradient(92% 74% at 26% 64%, rgba(124,106,240,.40), rgba(124,106,240,0) 74%),
    radial-gradient(80% 64% at 98% 4%, rgba(150,118,246,.24), rgba(150,118,246,0) 72%),
    radial-gradient(86% 70% at 94% 100%, rgba(108,92,224,.22), rgba(108,92,224,0) 74%),
    #0A0912;
}

/* Grain. Kills the banding a large soft gradient otherwise shows on
   cheap phone panels, and reads as paper rather than plastic. An
   inline SVG turbulence filter, so it costs no request. */
.auth-v2 .au-grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
  opacity: .22;
  mix-blend-mode: overlay;
}

[data-theme="dark"] .auth-v2 .au-grain { opacity: .16; mix-blend-mode: soft-light; }

/* ═══ RIGHT · form column ══════════════════════════════════════ */
.auth-v2 .au-main {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--color-bg);
  padding: 20px 20px 24px;
}

/* ── top bar ─────────────────────────────────────────────────── */
.auth-v2 .au-top {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 40px;
}

/* ── THE LOCKUP IS COPIED FROM THE LANDING NAVBAR ─────────────
   Values taken from .logo / .logo-mark in site-nav.css, which is what
   templates/partials/site-nav.php renders on "/". They have to agree
   because the two are consecutive frames of one journey: the landing
   page is where almost every signup starts, and the register page is
   the very next thing loaded. A wordmark that shifts from 15px/600 to
   17px/700 across that step reads as a different site, or as the page
   not having finished loading.

   Kept in sync BY HAND, because auth.css cannot import site-nav.css
   without dragging the whole navbar in with it. If the landing logo
   is ever restyled, this block is the other half of that change.

   ONE DELIBERATE DEVIATION: site-nav.css hard-codes --ink (#111827)
   and #fff, because the landing page has no dark mode at all. This
   page does, so the same two colours arrive through the theme tokens
   instead. --color-text-primary IS #111827 in light mode, so the
   light rendering is identical to the landing page, and dark mode
   flips to a light tile with a dark glyph rather than turning into a
   near-black square on a near-black background. */
.auth-v2 .au-lockup { display: inline-flex; align-items: center; gap: 10px; }

.auth-v2 .au-mark {
  width: 32px;
  height: 32px;
  flex: none;
  background: var(--color-text-primary);
  color: var(--color-bg);
  border-radius: 8px;
  display: grid;
  place-items: center;
}

.auth-v2 .au-mark svg { width: 17px; height: 17px; }

.auth-v2 .au-wordmark {
  font-size: 1.0625rem;
  font-weight: var(--weight-bold);
  letter-spacing: -.02em;
}

.auth-v2 .au-top-actions { display: flex; align-items: center; gap: var(--space-1); }

.auth-v2 .au-toplink {
  font-size: 13.5px;
  font-weight: var(--weight-medium);
  color: var(--color-text-secondary);
  padding: var(--space-2) var(--space-3);
  border-radius: 10px;
  white-space: nowrap;
  transition: background-color var(--duration-fast) var(--ease-in-out),
              color var(--duration-fast) var(--ease-in-out);
}

.auth-v2 .au-toplink:hover { background: var(--color-bg-tertiary); color: var(--color-text-primary); }

/* ── the form body ───────────────────────────────────────────── */
.auth-v2 .au-body {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 28px 0 8px;
}

/* margin:auto rather than justify-content:center on the parent.
   The two look identical when the form fits, and behave completely
   differently when it does not: justify-content:center pushes the
   overflow out of BOTH ends, and the top end of a scroll container
   cannot be scrolled back to, so on a short window the headline and
   the first fields become unreachable. Auto margins resolve to zero
   once there is no free space, so the form simply starts at the top
   and the page scrolls. */
.auth-v2 .au-col { width: 100%; max-width: 392px; margin: auto; }

.auth-v2 .au-h1 {
  font-family: var(--font-display);
  font-size: clamp(28px, 7.8vw, 32px);
  line-height: 1.12;
  font-weight: var(--weight-semibold);
  letter-spacing: -.028em;
  color: var(--color-text-primary);
  margin: 0;
}

.auth-v2 .au-sub {
  margin: 9px 0 0;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--color-text-secondary);
  /* 40ch, not 34. The measure was set against a 43-character line; the
     copy is now 74, and at the old width that is three ragged lines
     sitting in a box visibly narrower than the fields below it. This
     lands on two. Still short of the 392px column on purpose: a
     subtitle set to the full width of the form reads as a paragraph
     rather than as a caption on the headline. */
  max-width: 40ch;
}

/* ── alert ───────────────────────────────────────────────────── */
/* layout.css already styles .auth-alert. Only the top margin differs
   here, because there is a headline above it rather than a logo. */
.auth-v2 .auth-alert { margin-top: var(--space-5); }

/* ── THE GAP UNDER THE SUBTITLE ───────────────────────────────
   .au-sub has margin-bottom 0, and on /register and /login that
   is right: the next thing is .au-oauth, which brings its own 22px.
   /forgot-password and /reset-password have no Google block, so the
   form started immediately under the subtitle and the first label sat
   almost against the sentence above it.

   A SIBLING RULE RATHER THAN A MARGIN ON .au-sub, because a margin
   there would stack with .au-oauth's on the two pages that already
   look right. This matches only when the form is the very next element
   after the alert, which is exactly the case where nothing else has
   supplied the gap.

   It self-corrects, which is the reason to write it this way. With the
   Google client credentials cleared out of core/.env, /register and
   /login lose .au-oauth too, and this rule starts matching there and
   keeps the spacing identical. No page has to remember to opt in.

   NOTE FOR ANYONE ADDING A PAGE: the alert is always rendered, hidden
   or not, so `+` still matches. Put anything new AFTER the form, or
   this stops firing. */
.auth-v2 .auth-alert + form { margin-top: 22px; }

/* ── fields ──────────────────────────────────────────────────── */
.auth-v2 .au-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }

/* scroll-margin so that scrollIntoView() on a field that failed
   validation does not tuck it under the sticky bar or the very top
   edge of the viewport. */
.auth-v2 .form-group { margin-bottom: var(--space-4); scroll-margin-top: 84px; }

.auth-v2 .au-labelrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

.auth-v2 .form-label { margin-bottom: 0; }

.auth-v2 .form-label-link {
  font-size: 12.5px;
  font-weight: var(--weight-medium);
  color: var(--color-text-tertiary);
}

.auth-v2 .form-label-link:hover { color: var(--color-text-primary); }

.auth-v2 .au-field { position: relative; }

/* components.css sets everything else on .form-input. These two are
   the additions: font-family, because an <input> does not inherit it
   and Safari would fall back to its own UI font, and appearance,
   because iOS otherwise rounds the corners and adds an inner shadow
   of its own. */
.auth-v2 .form-input {
  font-family: inherit;
  -webkit-appearance: none;
  appearance: none;
}

/* Chrome repaints an autofilled field bright blue and throws the whole
   panel off. A very long inset shadow is the only reliable way back,
   because background-color itself is not honoured on autofill. */
.auth-v2 .form-input:-webkit-autofill,
.auth-v2 .form-input:-webkit-autofill:hover,
.auth-v2 .form-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--color-text-primary);
  -webkit-box-shadow: 0 0 0 1000px var(--color-input-bg) inset;
  caret-color: var(--color-text-primary);
}

/* ── THE FOCUS RING HAD TO BE PUT BACK ────────────────────────
   The rule above and the focus rule in components.css want the same
   property. In WebKit and Blink, -webkit-box-shadow IS box-shadow, so
   whichever wins takes the whole declaration rather than merging.

   And the rule above wins: `.auth-v2 .form-input:-webkit-autofill:focus`
   scores (0,3,1) against `.form-input:focus` at (0,2,0). The result was
   that focusing an AUTOFILLED field changed the border colour and
   nothing else, while every other field on the site also got the 3px
   ring. Same page, two different focus states, depending on something
   the user cannot see.

   IT ONLY EVER SHOWED ON /login, which is why it survived the whole
   register rebuild: register asks for given-name and new-password, and
   a browser fills in neither, so no field there is ever in the autofill
   state. Login is autofilled from saved credentials on nearly every
   visit, so it was the one page where the ring was reliably missing.

   The fix is to declare BOTH shadows in one value. They cannot collide:
   the ring is drawn outside the border box and the fill is inset, so
   order between them is cosmetic. The ring goes first only because it
   is the part that has to be legible. */
.auth-v2 .form-input:-webkit-autofill:focus {
  -webkit-box-shadow: var(--shadow-focus), 0 0 0 1000px var(--color-input-bg) inset;
  box-shadow: var(--shadow-focus), 0 0 0 1000px var(--color-input-bg) inset;
}

/* Quiet green tick once a field validates, so the form gives credit as
   it is filled instead of only ever pointing out mistakes. */
.auth-v2 .au-ok {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%) scale(.6);
  width: 16px;
  height: 16px;
  color: var(--color-success);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}

.auth-v2 .au-field.is-valid .au-ok { opacity: 1; transform: translateY(-50%) scale(1); }
.auth-v2 .au-field.is-valid .form-input { padding-right: var(--space-8); }

/* An empty .form-error still occupies a line box, which makes the form
   jump by a few pixels the first time a message appears. */
.auth-v2 .form-error:empty { display: none; }

/* ── password ────────────────────────────────────────────────── */
/* The strength meter is still ONE width-driven fill, so the scoring
   code stays a single number, but a repeating mask cuts it into four
   segments. A segmented bar reads as a score; a continuous one reads
   as a progress bar, and people wait for progress bars to finish. */
.auth-v2 .password-strength-track {
  height: 5px;
  -webkit-mask-image: repeating-linear-gradient(90deg, #000 0 calc(25% - 4px), transparent calc(25% - 4px) 25%);
  mask-image: repeating-linear-gradient(90deg, #000 0 calc(25% - 4px), transparent calc(25% - 4px) 25%);
}

.auth-v2 .password-strength-label { min-width: 66px; text-align: right; }

/* ── consent block ───────────────────────────────────────────── */
/* One checkbox now (the newsletter), so this is a single label rather
   than a stack. See the note in register.php for why the Privacy
   Policy tick above it is gone. */
.auth-v2 .au-consent { margin: 20px 0 18px; }

/* components.css uses accent-color, which cannot be sized or given a
   radius. This is a fully custom box: same 6px radius as the inputs,
   so the two do not look like they came from different forms. */
.auth-v2 .form-check input[type="checkbox"] {
  flex: none;
  width: 19px;
  height: 19px;
  margin-top: 1px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--color-input-bg);
  border: 1.5px solid var(--color-border-strong);
  border-radius: 6px;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-in-out),
              border-color var(--duration-fast) var(--ease-in-out);
}

.auth-v2 .form-check input[type="checkbox"]:checked {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  background-image: var(--au-check);
  background-size: 13px;
  background-position: center;
  background-repeat: no-repeat;
}

.auth-v2 .au-consent-text {
  font-size: 13px;
  line-height: 1.5;
  color: var(--color-text-secondary);
}

/* An underline drawn with box-shadow rather than text-decoration, so
   it sits a little lower and does not clip the descenders in "Policy". */
/* Only .au-consent-text now. .au-legal has its own, quieter treatment
   further down; leaving it in this rule kept the box-shadow underline
   alive, because text-decoration:none does not cancel a box-shadow. */
.auth-v2 .au-consent-text a {
  color: var(--color-text-primary);
  font-weight: var(--weight-medium);
  box-shadow: inset 0 -1px 0 var(--color-border-strong);
  transition: box-shadow var(--duration-fast) var(--ease-in-out),
              color var(--duration-fast) var(--ease-in-out);
}

.auth-v2 .au-consent-text a:hover { color: var(--au-lav); box-shadow: inset 0 -1px 0 var(--au-lav); }

/* ── the single legal notice, under the submit button ──────────
   Sits BELOW the button in the reading order without competing with
   it: tertiary text, centred, and a max-width so it breaks into two
   tidy lines rather than one wide one.

   NO PERMANENT UNDERLINE. Two underlined phrases inside a two-line
   grey paragraph turned the quietest text on the page into the
   busiest, which is the opposite of what a footnote should do. The
   links stay findable through weight and a step of colour instead.

   The hover underline stays, and it is not decoration. Colour alone
   is not a sufficient signal that something is a link (WCAG 1.4.1),
   so there has to be a non-colour cue somewhere; on hover and focus
   is where it costs nothing visually. Keyboard users get the focus
   ring from the block near the top of this file. */
.auth-v2 .au-legal {
  margin: 22px auto 0;
  max-width: 36ch;
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--color-text-tertiary);
  text-align: center;
}

.auth-v2 .au-legal a {
  color: var(--color-text-secondary);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-in-out);
}

.auth-v2 .au-legal a:hover {
  color: var(--color-text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── legal footer ────────────────────────────────────────────
   Dimmed hard. This row and the notice above it point at the same
   two policies, so the footer is the one that has to recede: it is
   navigation of last resort, not something anybody on a sign-up
   screen is looking for. Roughly the weight the copyright line had
   before it was removed. */
.auth-v2 .auth-legal-footer {
  position: relative;
  z-index: 1;
  margin-top: 28px;
  text-align: center;
}

.auth-v2 .auth-legal-links { font-size: 11.5px; gap: var(--space-2); }

.auth-v2 .auth-legal-links a {
  color: var(--color-text-tertiary);
  opacity: .62;
  transition: opacity var(--duration-fast) var(--ease-in-out);
}

.auth-v2 .auth-legal-links a:hover { opacity: 1; color: var(--color-text-secondary); }
.auth-v2 .auth-legal-sep { opacity: .4; }

/* ── buttons ─────────────────────────────────────────────────── */
.auth-v2 .btn-block { display: flex; width: 100%; }

/* A soft downward shadow rather than a hard one: the button is the
   only thing on the page with elevation, so it reads as the action
   without needing a colour nothing else on the page uses. */
.auth-v2 .btn-primary {
  box-shadow: 0 10px 22px -12px rgba(17, 24, 39, .75), 0 1px 2px rgba(17, 24, 39, .10);
}

.auth-v2 .btn-primary:active { transform: translateY(1px); box-shadow: 0 4px 10px -8px rgba(17, 24, 39, .7); }
[data-theme="dark"] .auth-v2 .btn-primary { box-shadow: 0 10px 24px -14px rgba(255, 255, 255, .35); }
.auth-v2 .btn:disabled { opacity: .55; cursor: not-allowed; pointer-events: none; }

/* OAuth: neutral surface, never Google blue. */
.auth-v2 .btn-oauth {
  background: var(--color-input-bg);
  color: var(--color-text-primary);
  border-color: var(--color-border);
  font-weight: var(--weight-medium);
}

.auth-v2 .btn-oauth:hover { background: var(--color-bg-secondary); border-color: var(--color-border-strong); }
.auth-v2 .btn-oauth:active { transform: translateY(1px); }
.auth-v2 .au-g { width: 18px; height: 18px; flex: none; }

.auth-v2 .au-oauth { margin-top: 22px; }

.auth-v2 .au-or { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-4) 0; }
.auth-v2 .au-or::before,
.auth-v2 .au-or::after { content: ''; flex: 1; height: 1px; background: var(--color-border); }
.auth-v2 .au-or span { font-size: var(--text-xs); color: var(--color-text-tertiary); }

/* No rule above the footer. The button is already a hard visual stop;
   a border directly under it only chopped the tail into more pieces. */
.auth-v2 .auth-footer {
  margin-top: var(--space-5);
  text-align: center;
  font-size: 13.5px;
  color: var(--color-text-secondary);
}

.auth-v2 .auth-footer a { color: var(--color-text-primary); font-weight: var(--weight-semibold); }
.auth-v2 .auth-footer a:hover { color: var(--au-lav); }

/* ── sticky CTA, phones only ─────────────────────────────────── */
.auth-v2 .au-sticky {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  padding: 10px var(--space-4) calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
  transform: translateY(110%);
  transition: transform var(--duration-normal) var(--ease-out);
  display: none;
}

.auth-v2 .au-sticky.is-on { transform: none; }

@supports (backdrop-filter: blur(1px)) {
  .auth-v2 .au-sticky {
    background: color-mix(in srgb, var(--color-bg) 82%, transparent);
    backdrop-filter: blur(14px) saturate(1.3);
  }
}

/* ═══ submit spacing ═══════════════════════════════════════════
   /register has a checkbox between its last field and its button,
   which supplies the gap. /login and the two password pages do
   not, and without this their button sits directly against the field
   above it and reads as part of the same control.

   A class rather than a rule on .btn-primary, because the button on
   the success screens must NOT get it: layout.css already spaces
   those, and both would apply. */
.auth-v2 .au-submit { margin-top: var(--space-5); }

/* ═══ note box ═════════════════════════════════════════════════
   The "check your spam folder" panel on the forgot-password result.
   Deliberately quiet: it is a footnote for the minority who need it,
   not a second message competing with the headline above it.

   text-align:left inside a centred .auth-success. Centred prose is
   hard to read at more than one line, and this is three. */
.auth-v2 .au-note {
  margin-top: var(--space-5);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--color-text-secondary);
  text-align: left;
}

.auth-v2 .au-note strong { color: var(--color-text-primary); font-weight: var(--weight-semibold); }

/* An address is a string to be copied by eye, so it gets a face that
   does not turn rn into m. word-break because a long address must
   wrap rather than widen the box on a 360px phone. */
.auth-v2 .au-note-mono {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px;
  color: var(--color-text-primary);
  word-break: break-all;
}

/* ═══ contact form ═════════════════════════════════════════════ */

/* components.css already gives .form-textarea its fill, border, radius
   and focus ring. Only what this design changes is here. */
.auth-v2 .au-textarea {
  font-family: inherit;   /* a <textarea> does not inherit it, and Safari
                             falls back to its own UI font without this */
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  /* 104, down from 132. The template asks for rows="4", which computes
     to about 114px at --text-base with line-height 1.6. The old floor
     was ABOVE that, so lowering the rows count alone changed nothing:
     min-height simply won and the box stayed 132px. The two have to
     move together or neither does.

     Still a floor rather than a fixed height, so the field cannot
     collapse to a single line on a browser that ignores rows. */
  min-height: 104px;
  padding: 11px 13px;
  line-height: 1.6;

  /* VERTICAL ONLY. A textarea that can be dragged wider breaks out of
     the 392px column and leaves the field visibly out of line with the
     three above it, with no way back except reloading. Growing
     downwards costs nothing. */
  resize: vertical;
}

/* Sits in the .au-labelrow opposite the label, which is the same slot
   "Forgot password?" occupies on /login. Empty until the message is
   within 500 characters of the ceiling, so it reads as a warning rather
   than as a limit being policed from the first keystroke. */
.auth-v2 .au-counter {
  font-size: var(--text-xs);
  color: var(--color-text-tertiary);
  font-variant-numeric: tabular-nums;  /* stops the number jittering as
                                          digits change width */
}

.auth-v2 .au-counter.is-low { color: var(--color-danger); }

/* ── HONEYPOT ─────────────────────────────────────────────────
   The field a bot fills in and a person never sees.

   NOT display:none, and not visibility:hidden. Both are the first
   thing a form-filling script checks for, and a field hidden that way
   is skipped, which turns the trap into decoration. Moving it out of
   the viewport leaves it visible to anything reading the DOM and
   invisible to anyone reading the screen.

   The <input> keeps tabindex="-1" and the wrapper keeps aria-hidden in
   the markup, so keyboard and screen reader users never reach it
   either. All four parts are load-bearing; see the note in
   templates/pages/contact.php. */
.auth-v2 .au-hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ═══ success screen ═══════════════════════════════════════════ */
/* layout.css owns the checkmark animation. Only the type changes. */
.auth-v2 .success-title {
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: var(--weight-semibold);
  letter-spacing: -.025em;
}

.auth-v2 .auth-success .btn { margin-top: 26px; }

/* Registration is finished and the session is live, so the two things
   that offer a way INTO an account both stop making sense: "Sign in"
   in the corner, and the sticky "Create free account" bar. Set by
   showSuccess() in register.js. */
.auth-v2.is-done .au-top-actions { display: none; }
.auth-v2.is-done .au-sticky { display: none; }

/* ═══════════════════════════════════════════════════════════════
   DESKTOP · the split view
   ═══════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  /* ── THE PANEL IS AN INSET TILE, THE PAGE IS NOT ─────────────
     Padding on the shell, radius on the panel. The page itself stays
     full bleed and white to all four edges; what floats inside it is
     the gradient.

     Two earlier versions were both wrong in the same way. The first
     put a rounded card on a tinted backdrop, so the whole screen read
     as a component. The second took the radius off everything, so the
     gradient ran into the window edge and read as a browser chrome
     accident rather than as a deliberate shape.

     This is the version that works: the white is the page, the tile is
     an object on it. box-sizing is border-box globally (base.css), so
     this padding sits INSIDE the 100svh above and does not push the
     shell past the viewport. */
  .auth-v2 .au-shell {
    --au-gutter: 16px;
    /* Not 50/50. An even split gives the form column exactly as much
       room as the panel, which makes a 392px field column look
       stranded in the middle of a very wide empty half. Weighting it
       the other way (panel smaller than form) matches how this layout
       reads everywhere it is used well: the panel is the supporting
       element, the form is the task. */
    grid-template-columns: 0.85fr 1fr;
  }

  /* ── THE PANEL IS SIZED TO THE VIEWPORT, NOT TO THE PAGE ─────
     A grid item stretches to the height of its ROW by default, and the
     row is as tall as the taller of the two columns. On /login,
     /contact and both password pages the form is shorter than the
     screen, the row is therefore exactly the 100svh min-height, and the
     panel gets the 16px inset above AND below it that this layout is
     drawn around.

     /register is the one page whose form is TALLER than the viewport.
     The row grew to fit it, the panel stretched with it, and the
     bottom inset went below the fold: on first paint the gradient ran
     straight off the bottom edge with a 16px gap still at the top. The
     panel was never wrong, it was just measuring the wrong thing.

     Fixing it means giving the panel its own height instead of
     inheriting the row's, and then pinning it so it does not simply
     scroll away and leave the lower half of a tall form beside an
     empty white column.

     WHY NOT the alternatives:
       · shrink the register form until it fits. Trades a cosmetic
         problem on one breakpoint for a cramped form on all of them.
       · position:fixed. Takes the panel out of flow, so the grid
         column collapses to zero and the form jumps left.
       · height:100svh with no sticky. Correct on arrival, then the
         panel scrolls off and the column goes blank. */
  .auth-v2 .au-aside {
    display: flex;
    flex-direction: column;
    justify-content: space-between;

    /* sticky, not relative. Still a positioned ancestor, so .au-mesh
       and .au-grain (both inset:0 absolute) are unaffected. */
    position: sticky;
    top: var(--au-gutter);

    /* align-self is what makes the two lines above do anything at all.
       Without it the item still stretches to the full row, a sticky box
       with no room to travel inside its containing block behaves
       exactly like a relative one, and nothing changes. Starting the
       item and giving it an explicit height turns the rest of the grid
       area into its runway. */
    align-self: start;

    /* vh first as the fallback for anything that does not know svh. On
       desktop the two are the same number; the pair is kept for
       consistency with .au-shell above. */
    height: calc(100vh - var(--au-gutter) * 2);
    height: calc(100svh - var(--au-gutter) * 2);

    overflow: hidden;
    border-radius: 24px;
    padding: 40px;
    isolation: isolate;
  }

  /* NOT .au-aside > *. That would also catch .au-mesh and .au-grain and
     knock both out of position:absolute, which drops the gradient into
     the flow as a zero-height box. */
  .auth-v2 .au-aside-top,
  .auth-v2 .au-aside-foot { position: relative; z-index: 2; }

  .auth-v2 .au-main { padding: 28px 40px 32px; }
  .auth-v2 .au-main .au-lockup { display: none; }
  .auth-v2 .au-main .au-top-actions { margin-left: auto; }
  .auth-v2 .au-body { padding: 32px 0 8px; }
}

@media (min-width: 1280px) {
  .auth-v2 .au-shell { --au-gutter: 20px; }
  .auth-v2 .au-aside { padding: 52px; }
  .auth-v2 .au-main { padding: 32px 56px 36px; }
}

@media (min-width: 1600px) {
  .auth-v2 .au-shell {
    --au-gutter: 24px;
    /* Stop the panel eating the screen on an ultrawide. Past this width
       the form column is the thing that should grow. */
    grid-template-columns: minmax(0, 780px) 1fr;
  }
  .auth-v2 .au-aside { padding: 64px; }
}

/* Aside internals. Only ever rendered at >= 900px. */
.auth-v2 .au-aside-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.auth-v2 .au-aside .au-lockup { display: inline-flex; }
.auth-v2 .au-aside .au-mark { background: var(--au-panel-ink); color: #FBFAFF; }
[data-theme="dark"] .auth-v2 .au-aside .au-mark { background: rgba(255,255,255,.92); color: #0C0A13; }
.auth-v2 .au-aside .au-wordmark { color: var(--au-panel-ink); }
.auth-v2 .au-aside-foot { max-width: 30ch; }

.auth-v2 .au-eyebrow {
  margin: 0;
  font-size: 11px;
  font-weight: var(--weight-semibold);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--au-panel-ink-2);
}

.auth-v2 .au-aside-h {
  font-family: var(--font-display);
  margin: 11px 0 0;
  font-size: clamp(28px, 2.6vw, 34px);
  line-height: 1.1;
  font-weight: var(--weight-semibold);
  letter-spacing: -.03em;
  color: var(--au-panel-ink);
}

/* ═══════════════════════════════════════════════════════════════
   PHONE
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 899px) {
  /* ── THE BUG THIS FIXES ──────────────────────────────────────
     .au-body used to be flex:none here, so it took only its natural
     height. .au-main is a full-height column, which meant every pixel
     of leftover space piled up in one lump BELOW the legal footer.
     On a short phone that was invisible; on a 956pt iPhone 16 Pro Max
     it was a couple of hundred pixels of nothing under the footer
     while the header looked crammed against the top edge.

     flex:1 gives that space back to the body, so the footer settles at
     the bottom of the screen and the slack goes around the form via
     the auto margins instead of collecting in one place. The taller
     the phone, the more the form breathes, which is the opposite of
     what it did before. */
  .auth-v2 .au-main {
    padding: calc(16px + env(safe-area-inset-top, 0px)) 18px calc(16px + env(safe-area-inset-bottom, 0px));
  }

  .auth-v2 .au-col { max-width: 440px; }

  /* Bottom padding larger than the base: it is the minimum gap to the
     footer once the auto margins have run out on a small screen. */
  .auth-v2 .au-body { padding: 26px 0 20px; }

  .auth-v2 .auth-legal-footer { margin-top: 20px; }
  .auth-v2 .au-sticky { display: block; }
}

/* Below 360px the two name fields stop fitting side by side. */
@media (max-width: 359px) {
  .auth-v2 .au-row { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .auth-v2 *,
  .auth-v2 *::before,
  .auth-v2 *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
