.hidden { display: none !important; }

.overlay {
  position: fixed;
  inset: 0;
  background: rgba(14, 16, 19, 0.5);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 200;
  opacity: 0;
  animation: overlayIn 0.18s ease forwards;
}
.overlay.hidden { display: none; }
@keyframes overlayIn { to { opacity: 1; } }

.overlay .auth-card {
  position: relative;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  opacity: 0;
  animation: panelIn 0.24s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes panelIn {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.modal-close:hover { color: var(--ink); background: var(--page-bg); }

.auth-main {
  min-height: calc(100vh - 73px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
}

.auth-card {
  width: 100%;
  max-width: 400px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 40px 36px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
}

.auth-eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
  text-transform: uppercase;
  margin-bottom: 10px;
}

.auth-title {
  font-family: 'Sora', sans-serif;
  font-weight: 700;
  font-size: 26px;
  margin: 0 0 8px;
}

.auth-sub {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.6;
  margin: 0 0 28px;
}

.auth-form { display: flex; flex-direction: column; gap: 18px; }

.auth-field { display: flex; flex-direction: column; gap: 7px; }
.auth-field label { font-size: 13px; font-weight: 600; color: var(--ink); }
.auth-field input {
  font: inherit;
  font-size: 15px;
  padding: 11px 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--page-bg);
  color: var(--ink);
  transition: border-color 0.15s ease;
}
.auth-field input:focus-visible {
  outline: none;
  border-color: var(--accent);
}

.auth-field textarea {
  font: inherit;
  font-size: 15px;
  padding: 11px 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--page-bg);
  color: var(--ink);
  resize: vertical;
  min-height: 110px;
  transition: border-color 0.15s ease;
}
.auth-field textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
}

.auth-optional { font-weight: 400; color: var(--muted); }

.auth-error {
  margin: 0;
  padding: 10px 14px;
  border-radius: 10px;
  background: color-mix(in srgb, #e32d37 12%, transparent);
  color: #ff8383;
  font-size: 13px;
  font-weight: 600;
}

.auth-success {
  margin: 0;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--accent-dark);
  font-size: 13px;
  font-weight: 600;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--muted-light);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.auth-forgot {
  text-align: right;
  margin-top: -8px;
}
.auth-forgot a { font-size: 13px; color: var(--muted); }
.auth-forgot a:hover { color: var(--accent); }

.auth-submit {
  margin-top: 4px;
  padding: 13px;
  font-size: 15px;
  border-radius: 10px;
}
.auth-submit:disabled { opacity: 0.6; cursor: default; }

.auth-switch {
  margin: 24px 0 0;
  text-align: center;
  font-size: 14px;
  color: var(--muted);
}
.auth-switch a { color: var(--accent); font-weight: 600; }
.auth-switch a:hover { opacity: 0.8; }

/* ==========================================================================
   The sign-in card.
   ==========================================================================

   Everything below is scoped to body.auth-page, which today is only
   views/login.html. The other four pages that use .auth-card (register,
   forgot-password, reset-password, verify-email) keep the plain card above
   untouched, so this can be tried on one page before the rest follow.

   Deliberately NOT done by putting .landing-page on the body. The original
   reason has since gone away: public/overrides.css used to carry a
   body.landing-page h1 { font-size: 70px !important }, right for a hero and
   large enough to swallow this card whole, and that rule was deleted on
   2026-08-06 when it turned out to be overflowing phones.

   Kept anyway, for a reason that has not gone away: body.landing-page pulls in
   the whole landing layout, the wallpaper layers and the story rules, and this
   page wants four palette tokens out of that. Those four are restated below. */

body.auth-page {
  --page-bg: #0c1e10;
  --ink: #eef2eb;
  --muted: #e0e1db;
  --muted-light: #e0e1db;
  --border: rgba(238, 242, 235, 0.11);
  --accent: #7dbf8a;
  --accent-soft: color-mix(in srgb, var(--accent) 14%, white);
  --accent-dark: color-mix(in srgb, var(--accent) 78%, black);
  --header-bg: rgba(0, 0, 0, 0.8);
  /* The label colour for anything sitting on the accent. The accent is light
     enough that white on it is 1.95:1; the page colour under it is 8.92:1.
     Same inversion the landing page's calls to action make. */
  --on-accent: #0c1e10;
}

/* The wallpaper is position:fixed at z-index 0, so the page has to be lifted
   off it rather than the other way round. */
body.auth-page .site-header { position: relative; z-index: 50; }
body.auth-page .auth-main {
  position: relative;
  z-index: 1;
  /* The header is not sticky here, so the card centres in what is left of the
     viewport under it. */
  min-height: calc(100vh - 73px);
  padding: 40px 24px 64px;
}

/* --- Shell: the tilting frame -------------------------------------------- */

/* The perspective lives on the parent and the rotation on the child, because a
   single element cannot hold both without the perspective being applied after
   the transform. --tilt-x/--tilt-y are written by public/auth-card.js; with no
   script, no pointer or reduced motion they stay 0deg and this is a flat card. */
body.auth-page .auth-main { perspective: 1500px; }

.auth-shell {
  position: relative;
  width: 100%;
  max-width: 400px;
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
  /* Deliberately no transform-style: preserve-3d. The card has nothing behind
     it that needs its own depth, and preserve-3d puts the frame and the card
     into a 3D rendering context, where paint order comes from their position in
     space and z-index is ignored outright. The beams then painted under the
     card's backdrop-filter and came through as a smudge. Flat, the frame's
     z-index means what it says. */
  /* Long enough that the card keeps drifting for a moment after the pointer
     stops, which is what makes it read as weight rather than as tracking. The
     name of the service means calm; a card that snaps to the cursor says the
     opposite. */
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
  animation: authRise 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes authRise {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; }
}

/* --- The light running around the border --------------------------------- */

/* Four beams, one per edge, each a gradient that is transparent at both ends
   and bright in the middle, slid the length of its edge. They travel head to
   tail around the card: top left-to-right, then right down, then bottom
   right-to-left, then left up, each starting 0.6s after the one before.
 *
 * The frame is its own element rather than pseudo-elements on the card, because
 * the card needs overflow:hidden for its inner pattern and that would clip the
 * beams at the exact corners where they are most visible. */
.auth-frame {
  position: absolute;
  inset: -1px;
  border-radius: 21px;
  overflow: hidden;
  pointer-events: none;
  /* Above the card, not behind it. Behind, the card's own backdrop-filter
     covers all but the one pixel of frame that sticks out past it, and a 2px
     beam showing 1px of itself through a blur is not visible at all: the first
     cut of this looked like a plain card. On top, the beam rides the border
     the way it is meant to. Nothing here takes pointer events, so it cannot
     get in the way of the form underneath. */
  z-index: 2;
}

/* Bone rather than the sage accent. The accent is a mid green on a dark green
   card, about 1.6:1 against the border it runs along, and at two pixels wide
   behind a blur it simply did not read: the first cut of this was invisible in
   a screenshot. Bone is the same brand light the body text is set in, and it
   separates. */
.auth-beam {
  position: absolute;
  opacity: 0.9;
  filter: blur(1px);
}
.auth-beam-top,
.auth-beam-bottom {
  height: 2px;
  width: 50%;
  background: linear-gradient(to right, transparent, #eef2eb, transparent);
}
.auth-beam-right,
.auth-beam-left {
  width: 2px;
  height: 50%;
  background: linear-gradient(to bottom, transparent, #eef2eb, transparent);
}

/* 3.5s each: 2.5s of travel, then a second parked out of frame, which is the
   gap that keeps four beams from reading as a solid moving rectangle. */
.auth-beam-top { top: 0; left: -50%; animation: authBeamX 3.5s ease-in-out infinite; }
.auth-beam-right { top: -50%; right: 0; animation: authBeamY 3.5s ease-in-out infinite 0.6s; }
.auth-beam-bottom { bottom: 0; right: -50%; animation: authBeamXReverse 3.5s ease-in-out infinite 1.2s; }
.auth-beam-left { bottom: -50%; left: 0; animation: authBeamYReverse 3.5s ease-in-out infinite 1.8s; }

@keyframes authBeamX {
  0% { left: -50%; }
  71% { left: 100%; }
  100% { left: 100%; }
}
@keyframes authBeamXReverse {
  0% { right: -50%; }
  71% { right: 100%; }
  100% { right: 100%; }
}
@keyframes authBeamY {
  0% { top: -50%; }
  71% { top: 100%; }
  100% { top: 100%; }
}
@keyframes authBeamYReverse {
  0% { bottom: -50%; }
  71% { bottom: 100%; }
  100% { bottom: 100%; }
}

/* Corner sparks: what a beam leaves behind as it turns. Off the same 3.5s lap
   as the beams so they pulse in step with what passes them. */
.auth-spark {
  position: absolute;
  border-radius: 50%;
  background: #eef2eb;
  filter: blur(2px);
  opacity: 0.3;
  animation: authSpark 3.5s ease-in-out infinite;
}
.auth-spark-tl { top: 0; left: 0; width: 5px; height: 5px; }
.auth-spark-tr { top: 0; right: 0; width: 8px; height: 8px; animation-delay: 0.6s; }
.auth-spark-br { bottom: 0; right: 0; width: 8px; height: 8px; animation-delay: 1.2s; }
.auth-spark-bl { bottom: 0; left: 0; width: 5px; height: 5px; animation-delay: 1.8s; }
@keyframes authSpark {
  0%, 100% { opacity: 0.2; }
  20% { opacity: 0.55; }
  60% { opacity: 0.2; }
}

/* --- The card ------------------------------------------------------------ */

body.auth-page .auth-card {
  position: relative;
  z-index: 1;
  max-width: none;
  padding: 30px 28px 28px;
  border-radius: 20px;
  /* Translucent rather than the solid --card-bg, so the pour underneath shows
     through and the card reads as glass laid on the floor. The blur is what
     keeps body text legible over a moving background. */
  background: rgba(4, 29, 20, 0.55);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(238, 242, 235, 0.06);
  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.45);
  overflow: hidden;
}

/* A fine cross-hatch at 3%, the only thing that says this pane has a surface. */
body.auth-page .auth-card::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.03;
  background-image:
    linear-gradient(135deg, #ffffff 0.5px, transparent 0.5px),
    linear-gradient(45deg, #ffffff 0.5px, transparent 0.5px);
  background-size: 30px 30px;
}

/* Everything the card holds sits above that pattern. */
body.auth-page .auth-card > * { position: relative; }

.auth-head { text-align: center; margin-bottom: 22px; }

.auth-mark {
  width: 42px;
  height: 42px;
  margin: 0 auto 12px;
  border-radius: 50%;
  border: 1px solid rgba(238, 242, 235, 0.12);
  background: linear-gradient(140deg, rgba(238, 242, 235, 0.1), transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Sora', sans-serif;
  font-weight: 800;
  font-size: 19px;
  color: var(--ink);
  animation: authMark 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes authMark {
  from { opacity: 0; transform: scale(0.5); }
  to { opacity: 1; transform: scale(1); }
}

body.auth-page .auth-title {
  font-size: 23px;
  margin: 0 0 6px;
  animation: authFade 0.5s ease both 0.2s;
}
body.auth-page .auth-sub {
  font-size: 13px;
  line-height: 1.55;
  margin: 0;
  color: rgba(238, 242, 235, 0.62);
  animation: authFade 0.5s ease both 0.3s;
}
@keyframes authFade {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* --- Fields -------------------------------------------------------------- */

body.auth-page .auth-form { gap: 14px; }

/* The icon and the reveal button are positioned against this, and the input
   fills it. The label is still in the markup, visually hidden, so the field is
   announced by name even though the design shows a placeholder. */
.auth-input {
  position: relative;
  display: flex;
  align-items: center;
  border-radius: 10px;
}

.auth-input-icon {
  position: absolute;
  left: 12px;
  width: 16px;
  height: 16px;
  color: rgba(238, 242, 235, 0.4);
  transition: color 0.3s ease;
  pointer-events: none;
}
.auth-input:focus-within .auth-input-icon { color: var(--ink); }

body.auth-page .auth-field input {
  width: 100%;
  height: 42px;
  padding: 0 12px 0 38px;
  font-size: 14px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: rgba(238, 242, 235, 0.05);
  color: var(--ink);
  transition: background 0.3s ease, border-color 0.3s ease;
}
body.auth-page .auth-field input::placeholder { color: rgba(238, 242, 235, 0.32); }
body.auth-page .auth-field input:focus-visible {
  outline: none;
  background: rgba(238, 242, 235, 0.1);
  border-color: rgba(238, 242, 235, 0.2);
}
/* The password field also has to clear the reveal button on the right. */
body.auth-page .auth-field input[type='password'],
body.auth-page .auth-field input#password { padding-right: 40px; }

.auth-peek {
  position: absolute;
  right: 6px;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: none;
  color: rgba(238, 242, 235, 0.4);
  cursor: pointer;
  transition: color 0.2s ease;
}
.auth-peek:hover { color: var(--ink); }
.auth-peek svg { width: 16px; height: 16px; }
/* Pressed means the password is showing, so the button now offers to hide it. */
.auth-peek .auth-peek-hide { display: none; }
.auth-peek[aria-pressed='true'] .auth-peek-show { display: none; }
.auth-peek[aria-pressed='true'] .auth-peek-hide { display: block; }

body.auth-page .auth-forgot { margin: -4px 0 0; }
body.auth-page .auth-forgot a {
  font-size: 12px;
  color: rgba(238, 242, 235, 0.6);
}
body.auth-page .auth-forgot a:hover { color: var(--ink); }

/* --- Submit -------------------------------------------------------------- */

body.auth-page .auth-submit {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 42px;
  margin-top: 6px;
  padding: 0;
  border: none;
  border-radius: 10px;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.2s ease;
}
body.auth-page .auth-submit:hover { background: color-mix(in srgb, var(--accent) 88%, white); }
body.auth-page .auth-submit:active { transform: scale(0.985); }

.auth-submit-label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.auth-submit-arrow {
  width: 13px;
  height: 13px;
  transition: transform 0.3s ease;
}
body.auth-page .auth-submit:hover .auth-submit-arrow { transform: translateX(3px); }

/* public/auth.js disables the button for the length of the request and enables
   it again on failure, so :disabled is the real in-flight state and needs no
   extra bookkeeping. */
.auth-submit-spinner { display: none; }
body.auth-page .auth-submit:disabled { opacity: 1; cursor: default; }
body.auth-page .auth-submit:disabled .auth-submit-label { display: none; }
body.auth-page .auth-submit:disabled .auth-submit-spinner {
  display: block;
  width: 15px;
  height: 15px;
  border: 2px solid color-mix(in srgb, var(--on-accent) 45%, transparent);
  border-top-color: transparent;
  border-radius: 50%;
  animation: authSpin 0.7s linear infinite;
}
@keyframes authSpin { to { transform: rotate(360deg); } }

/* A single sweep of light across the button while the request is out, so the
   wait has something moving in it besides the spinner. */
body.auth-page .auth-submit:disabled::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.35), transparent);
  animation: authSweep 1.5s ease-in-out infinite;
}
@keyframes authSweep {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* --- Below the form ------------------------------------------------------ */

body.auth-page .auth-divider {
  margin: 18px 0;
  color: rgba(238, 242, 235, 0.4);
}
body.auth-page .auth-divider::before,
body.auth-page .auth-divider::after { background: rgba(238, 242, 235, 0.08); }

/* Google renders its own button into this mount as an iframe, so it cannot be
   restyled from here. Centring it is the whole of what this can do. */
body.auth-page #google-signin-btn {
  display: flex;
  justify-content: center;
}

body.auth-page .auth-switch {
  margin-top: 18px;
  font-size: 12px;
  color: rgba(238, 242, 235, 0.6);
}
body.auth-page .auth-switch a { color: var(--ink); }

body.auth-page .auth-error {
  background: rgba(227, 45, 55, 0.16);
  color: #ffa8a8;
  font-size: 12px;
}

/* --- Small screens -------------------------------------------------------- */

@media (max-width: 480px) {
  body.auth-page .auth-main { padding: 24px 16px 48px; }
  body.auth-page .auth-card { padding: 26px 20px 24px; }
}

/* The answer-time promise under the contact form's intro. Set apart from the
   paragraph above it with the accent and a dot, because it is a commitment
   rather than more description, and it is the line that decides whether
   somebody bothers to write at all. */
.contact-promise {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: -4px 0 18px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--accent);
}

.contact-promise::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* Password strength meter, built by public/password-meter.js. Four segments
   rather than one continuous bar: a discrete count reads as "3 of 4" at a
   glance, where a sliding fill invites the reader to judge a percentage that
   was never that precise.

   The colour is never the only signal. .pw-meter-text always spells the
   verdict out in words, both because WCAG 1.4.1 does not accept colour alone
   and because red-green is exactly the pair most colour-blind readers lose. */
.pw-meter { margin-top: 9px; }

.pw-meter-track {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}

.pw-meter-track i {
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  transition: background-color 160ms ease;
}

/* Filled segments take the colour of the score as a whole, so a weak password
   is four dim segments and one red one rather than a rainbow. */
.pw-meter-track i[data-on='1'] { background: #ff9a8a; }
.pw-meter-track i[data-on='2'] { background: #ffc46a; }
.pw-meter-track i[data-on='3'] { background: #82db9b; }
.pw-meter-track i[data-on='4'] { background: #3fd880; }

.pw-meter-text {
  margin: 6px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  font-weight: 500;
  color: var(--muted);
  min-height: 18px; /* Reserves the line so the form does not jump as it fills in. */
}

/* Level 0 is a rule the password breaks, not a weak-but-allowed password, so
   the message is the reason and it is coloured like the form's other errors.
   #ff9a8a on the card background clears 4.5:1. */
.pw-meter[data-level='0'] .pw-meter-text { color: #ff9a8a; }

/* Everything above that moves is decoration: the beams, the sparks, the tilt,
   the entrance. Under reduced motion the card is the same card, lit and still.
   The spinner is the exception, it is the only feedback that a request is in
   flight, so it slows down rather than stopping. */
@media (prefers-reduced-motion: reduce) {
  .auth-shell { animation: none; transition: none; }
  .auth-beam { animation: none; }
  /* Parked on the visible half of each edge instead of off-frame, so the card
     keeps its lit border with nothing travelling. */
  .auth-beam-top { left: 25%; }
  .auth-beam-bottom { right: 25%; }
  .auth-beam-right { top: 25%; }
  .auth-beam-left { bottom: 25%; }
  .auth-spark { animation: none; opacity: 0.35; }
  .auth-mark,
  body.auth-page .auth-title,
  body.auth-page .auth-sub { animation: none; }
  body.auth-page .auth-submit:disabled::after { animation: none; opacity: 0; }
  body.auth-page .auth-submit:disabled .auth-submit-spinner { animation-duration: 1.6s; }
  /* The segments still change colour, they just change instantly. The colour
     is the information; the fade was only ever polish. */
  .pw-meter-track i { transition: none; }
}
