/*
 * IKON Community™ — auth pages (sign up, verify, login, password reset)
 * Ported from design/IKON-Community-UI-Mockup.html's .auth-split/.f-input/
 * .otp-box patterns, adapted from static mockup markup to real functional
 * <input> elements (focus states, validation hints, disabled states —
 * none of which exist in the static mockup). Tokens come from theme.css.
 */

.auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  padding: var(--space-8) var(--space-4);
}

.auth-card {
  max-width: 960px;
  width: 100%;
  background: var(--paper);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}

.auth-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 560px;
}

@media (max-width: 900px) {
  .auth-split {
    grid-template-columns: 1fr;
    min-height: 0;
  }
}

/* ---- Left panel: brand + tagline ---- */
.auth-visual {
  background: var(--indigo);
  color: #fff;
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

.auth-visual .brand {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.05rem;
}

.auth-visual .brand span {
  color: var(--gold);
}

.auth-visual h3 {
  color: #fff;
  font-size: 1.5rem;
  max-width: 340px;
  line-height: 1.25;
  margin: var(--space-6) 0 var(--space-4);
}

.auth-visual .tagline {
  color: rgba(255, 255, 255, 0.68);
  font-size: 0.9rem;
  line-height: 1.6;
  max-width: 320px;
}

.auth-visual .foot {
  margin-top: auto;
  font-size: 0.76rem;
  color: rgba(255, 255, 255, 0.5);
  font-family: var(--font-mono);
}

.auth-visual .foot a {
  color: rgba(255, 255, 255, 0.85);
  text-decoration: underline;
}

/* ---- Animated orbit graphic ----
 * Ported from design/IKON-Community-UI-Mockup.html's "2026 redesign" style
 * block (~line 1866) — same as the landing hero's connected-avatars graphic,
 * this component only exists in that later pass, not the first block. Shown
 * on both the Sign Up and Login auth-visual panels in the mockup.
 */
.auth-orbit {
  position: relative;
  height: 170px;
  margin: var(--space-2) 0 4px;
}

.ao-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  border: 1px dashed rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-full);
}

.ao-ring-1 {
  width: 260px;
  height: 260px;
  margin: -130px 0 0 -130px;
  animation: orbit-rotate 70s linear infinite;
}

.ao-ring-2 {
  width: 190px;
  height: 190px;
  margin: -95px 0 0 -95px;
  animation: orbit-rotate 50s linear infinite reverse;
}

@keyframes orbit-rotate {
  to { transform: rotate(360deg); }
}

.ao-node {
  position: absolute;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 600;
  color: #fff;
  border: 2px solid rgba(255, 255, 255, 0.35);
  box-shadow: var(--shadow-md);
}

.ao-center {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  border-radius: var(--radius-full);
  background: linear-gradient(135deg, var(--coral), #a83420);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 14px 30px rgba(203, 45, 57, 0.4);
  border: 3px solid rgba(255, 255, 255, 0.25);
}

.ao-center svg {
  width: 26px;
  height: 26px;
  stroke: #fff;
  fill: none;
  stroke-width: 1.8;
}

.auth-livestat {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 6px 0 var(--space-5);
}

.auth-livestat .als-stack {
  display: flex;
}

.auth-livestat .als-stack span {
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  border: 2px solid var(--indigo);
  margin-left: -8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  color: #fff;
  font-weight: 600;
}

.auth-livestat .als-stack span:first-child {
  margin-left: 0;
}

.auth-livestat strong {
  color: #fff;
  font-size: 0.9rem;
}

.auth-livestat .als-lbl {
  color: rgba(255, 255, 255, 0.55);
  font-size: 0.76rem;
}

.auth-footstat {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.45);
  letter-spacing: 0.04em;
}

/* ---- Right panel: the actual form ---- */
.auth-form-panel {
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.auth-form-panel h3 {
  font-size: 1.3rem;
  margin-bottom: 6px;
}

.auth-form-panel p.sub {
  color: var(--ink-soft);
  font-size: 0.86rem;
  margin-bottom: var(--space-6);
}

.f-row {
  margin-bottom: var(--space-4);
}

.f-row label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 6px;
}

.f-row input[type='text'],
.f-row input[type='email'],
.f-row input[type='password'],
.f-row input[type='tel'],
.f-row input[type='url'] {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 11px 14px;
  font-size: 0.88rem;
  font-family: var(--font-body);
  /* var(--paper), not a hardcoded light hex — this was the actual bug:
     a fixed near-white background paired with the theme-reactive
     var(--ink) text color meant dark mode flipped the text near-white
     too, onto a background that never got any darker (nearly invisible
     typed text/placeholder in dark mode). --paper already flips to a
     dark navy-ish surface in dark mode (theme.css), matching every
     other card/panel on the page, so text and background now always
     move together. */
  background: var(--paper);
  color: var(--ink);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
}

.f-row input[type='text']::placeholder,
.f-row input[type='email']::placeholder,
.f-row input[type='password']::placeholder,
.f-row input[type='tel']::placeholder,
.f-row input[type='url']::placeholder {
  /* Explicit, rather than left to the browser default — with
     color-scheme: dark set sitewide in dark mode (theme.css's :root),
     some browsers lighten their own default placeholder gray to suit
     an assumed dark input background, which fought the (previously
     hardcoded-light) background above and produced exactly the
     reported "nearly invisible" placeholder text. */
  color: var(--muted);
}

.f-row input:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(203, 45, 57, 0.12);
}

.f-hint {
  font-size: 0.74rem;
  color: var(--muted);
  margin-top: 6px;
}

.f-hint.checking {
  color: var(--muted);
}

.f-hint.valid {
  color: var(--teal);
}

.f-hint.invalid {
  color: var(--gold);
}

.f-error {
  font-size: 0.74rem;
  color: var(--gold);
  margin-top: 6px;
}

/* Conditional callout — currently only used below Practitioner ID
   (signup.php, [data-practitioner-note]), toggled by public/js/auth.js
   alongside [data-practitioner-hint]: shown only while the live check's
   status is 'not_found', to explain that can just mean a recent
   ikonskills.ac enrolment hasn't synced yet. Ships [hidden] by default
   in the view; the explicit [hidden] rule below is needed because
   .f-note's own `display: flex` is an author style and would otherwise
   beat the UA stylesheet's `[hidden] { display: none }`. Shape lives on
   .f-note; color comes from a variant modifier (.f-note-warning below
   is the only one in use today, reusing --gold — this app's existing
   error/warning red, already applied to .f-error above — rather than a
   one-off color). */
.f-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  font-size: 0.74rem;
  line-height: 1.5;
}

.f-note[hidden] {
  display: none;
}

.f-note-icon {
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  margin-top: 1px;
}

.f-note-warning {
  background: rgba(203, 45, 57, 0.08);
  border-left: 3px solid var(--gold);
  color: var(--ink-soft);
}

.f-note-warning .f-note-icon {
  color: var(--gold);
}

.f-note-warning a {
  color: var(--gold);
  font-weight: 600;
  text-decoration: underline;
}

.f-note-warning strong {
  color: var(--ink);
}

.submit-btn {
  display: block;
  width: 100%;
  background: var(--gold);
  color: #fff;
  border: 0;
  padding: 12px;
  border-radius: var(--radius-md);
  text-align: center;
  font-weight: 600;
  font-size: 0.9rem;
  font-family: var(--font-body);
  margin-top: 6px;
  cursor: pointer;
  transition: opacity var(--transition-fast);
}

.submit-btn:hover {
  opacity: 0.92;
}

.submit-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Same shape as .submit-btn (block, full-width, radius, padding,
   font-weight/size) but outline/ghost-filled instead of solid, so it
   reads as a real button — not a plain text link — while staying
   visually secondary to the actual submit CTA it sits near. Used by
   the "How to obtain your Practitioner ID" link on Sign Up. */
.submit-btn-outline {
  display: block;
  width: 100%;
  background: transparent;
  color: var(--gold);
  border: 1.5px solid var(--gold);
  padding: 10.5px 12px;
  border-radius: var(--radius-md);
  text-align: center;
  font-weight: 600;
  font-size: 0.9rem;
  font-family: var(--font-body);
  text-decoration: none;
  margin: 14px 0 var(--space-4);
  cursor: pointer;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.submit-btn-outline:hover {
  background: var(--gold);
  color: #fff;
}

.auth-footnote {
  text-align: center;
  font-size: 0.82rem;
  color: var(--ink-soft);
  margin-top: var(--space-4);
}

.auth-footnote a {
  color: var(--gold);
  font-weight: 600;
}

.auth-back-link {
  display: inline-block;
  margin-bottom: var(--space-4);
  font-size: 0.82rem;
  color: var(--ink-soft);
}

.auth-back-link:hover {
  color: var(--gold);
}

/* ---- OTP entry ---- */
.otp-row {
  display: flex;
  gap: 8px;
  margin: 6px 0;
  justify-content: center;
}

.otp-box {
  width: 44px;
  height: 52px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  text-align: center;
  font-size: 1.3rem;
  font-family: var(--font-mono);
  background: #fbfbfc;
  color: var(--ink);
}

.otp-box:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(203, 45, 57, 0.12);
}

.link-btn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-family: var(--font-body);
  color: var(--gold);
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}

.link-btn:disabled {
  color: var(--muted);
  cursor: not-allowed;
  text-decoration: none;
}

/* ---- Alerts / flash messages ---- */
.alert {
  border-radius: var(--radius-md);
  padding: 12px 14px;
  font-size: 0.84rem;
  line-height: 1.5;
  margin-bottom: var(--space-4);
}

.alert-error {
  background: rgba(203, 45, 57, 0.06);
  border: 1px solid rgba(203, 45, 57, 0.3);
  color: var(--gold);
}

.alert-success {
  background: var(--mint);
  border: 1px solid rgba(62, 142, 130, 0.3);
  color: var(--teal);
}

.alert-notice {
  background: #fbfbfc;
  border: 1px solid var(--line);
  color: var(--ink-soft);
}

.alert-dev {
  background: #fff7e6;
  border: 1px solid #e5c07b;
  color: #8a5a17;
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

/* Soft amber "heads up" notice — currently only the Login page's
   IKON SKILLS-vs-IKON-Community disambiguation banner. Same amber
   palette .alert-dev already uses (that one's scoped to dev-mode
   messages specifically; this is the general-purpose version of the
   same tone) rather than a one-off color. */
.alert-notice-amber {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: #fff7e6;
  border: 1px solid #e5c07b;
  color: #8a5a17;
}

.alert-notice-amber-icon {
  flex-shrink: 0;
  width: 17px;
  height: 17px;
  margin-top: 1px;
  color: #b7791f;
}

.alert-notice-amber a {
  color: #8a5a17;
  font-weight: 600;
  text-decoration: underline;
}

/* =================================================================
 * Login + Sign Up pages — 2026 redesign pass. Everything below is
 * scoped so it can't touch Verify/Reset, which also share this
 * stylesheet: the font override keys off body[data-page="login"/
 * "register"] (only login.php/signup.php set that attribute); every new
 * visual element uses a class name that ONLY those two pages' own
 * markup renders (.login-orbit modifier, .security-status-pill,
 * .pwd-toggle-btn, .card-shake, .input-icon-wrap, etc.) —
 * adding a new class never affects the shared base rules (.auth-orbit,
 * .ao-node, .auth-card, .submit-btn...) Verify/Reset still use
 * untouched. Classes introduced for Login (.pwd-toggle-btn, .btn-
 * spinner, .btn-arrow, .card-shake, the spinnerPopIn/btnSpin keyframes)
 * are reused as-is on Sign Up below rather than duplicated — none of
 * them were page-scoped to begin with, they're just unique class names
 * no other page happens to render.
 *
 * Extended to admin/login.php (body[data-page="admin-login"]) in the
 * Admin Login redesign pass for the exact same reason: .pwd-toggle-btn,
 * .caps-lock-note, .security-status-pill, .btn-spinner/.btn-arrow, and
 * the cardShake/spinnerPopIn/btnSpin/ambientBreathe keyframes are all
 * reused as-is there too rather than redefined — admin/verify.php
 * (the real OTP second-factor step right after this page, untouched by
 * this pass) never renders any of these class names, so nothing here
 * reaches it. */
body[data-page="login"],
body[data-page="register"],
body[data-page="admin-login"] {
  --font-heading: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'JetBrains Mono', 'Fira Code', 'IBM Plex Mono', monospace;
}

/* ---- Ambient orbit enhancements (login only — see .login-orbit) ----
 * All three are pure CSS, so accessibility.css's blanket reduced-motion
 * kill switch (html[data-a11y-motion="reduced"] *) neutralizes them
 * automatically once that attribute is set — no JS-side duplication
 * needed here, unlike the confetti canvas below, because none of these
 * rely on a hidden-until-animated starting state: each one's resting
 * frame (no animation running) is already a complete, intentional
 * picture on its own, just a static one. */
.login-orbit {
  overflow: visible;
}

.ambient-glow {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 200px;
  height: 200px;
  margin: -100px 0 0 -100px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(203, 45, 57, 0.22) 0%, rgba(203, 45, 57, 0.05) 55%, transparent 72%);
  filter: blur(20px);
  pointer-events: none;
  animation: ambientBreathe 6s ease-in-out infinite alternate;
}

@keyframes ambientBreathe {
  from { opacity: 0.55; transform: scale(0.94); }
  to { opacity: 0.9; transform: scale(1.06); }
}

.radar-sweep {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 260px;
  height: 260px;
  margin: -130px 0 0 -130px;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(
    from 0deg,
    rgba(203, 45, 57, 0.16) 0deg,
    rgba(203, 45, 57, 0.02) 50deg,
    transparent 62deg,
    transparent 360deg
  );
  animation: radarRotate 10s linear infinite;
}

@keyframes radarRotate {
  to { transform: rotate(360deg); }
}

/* Thin dashed lines from each practitioner node toward the center icon
 * — same "peer connection" motif as the sidebar's own line-drawing
 * background (practitioner.css) and the public landing hero's connected-
 * avatars graphic, not a new idea introduced just for this page. */
.orbit-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.orbit-lines path {
  fill: none;
  stroke: rgba(255, 255, 255, 0.16);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}

/* ---- Security status pill (padlock + "Secured"/"Visible") ----
 * Two real states, tied to the same type="password"/"text" toggle
 * .pwd-toggle-btn below already needs to track — not a fabricated
 * "security score". Bold + a real green (var(--teal), this app's own
 * positive/success token — same color Connect/Dashboard already use for
 * "live"/verified states) by default, since a Practitioner should read
 * "you're safe" at a glance without parsing a sentence; only flips to
 * the amber "Visible" state while the password is actually shown as
 * plain text on screen. */
.security-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  /* --radius-pill (a fixed 20px), not --radius-full (50%) — 50% on a
     box whose width and height differ (this one is, by design: icon +
     text side by side) doesn't clip into a pill, it clips into an
     ellipse scaled independently on each axis, i.e. exactly the
     "stretched oval" bug. --radius-pill is the token this codebase
     already reserves for exactly this shape (see its own comment in
     theme.css: "pills, badges, chips"); --radius-full is for actual
     circles (avatars, dots). */
  border-radius: var(--radius-pill);
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-5);
  transition: background-color 150ms, border-color 150ms, color 150ms;
}

.security-status-pill.is-secured {
  background: var(--mint);
  border: 1px solid rgba(62, 142, 130, 0.3);
  color: var(--teal);
}

.security-status-pill.is-visible {
  background: #fffaf0;
  border: 1px solid #f5d99a;
  color: #92650f;
}

.security-pill-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.security-pill-icon path[data-shackle] {
  transform-origin: 12px 10px;
  transition: transform 200ms ease;
}

.security-status-pill.is-visible .security-pill-icon path[data-shackle] {
  transform: translateY(1.5px) scaleY(0.4);
}

/* ---- Password field: show/hide toggle + Caps Lock note ----
 * Same pwd-field-wrap/pwd-toggle-btn recipe as Settings' change-password
 * section (practitioner.css) — pure UI, no backend involved either
 * place. */
.pwd-field-wrap {
  position: relative;
}

/* Same specificity fight as .input-icon-wrap input above — a plain
   `.pwd-field-wrap input` (0,1,1) always lost to the shared .f-row
   input[type='password'] rule's own (0,2,1), so the eye-toggle button
   was sitting on top of the input's own text (Login's password field,
   and both password fields here) rather than clearing it. */
.f-row .pwd-field-wrap input[type='password'] {
  padding-right: 40px;
}

.pwd-toggle-btn {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  cursor: pointer;
  background: none;
  border: 0;
}

.pwd-toggle-btn svg {
  width: 17px;
  height: 17px;
}

/* A quiet, informational note — "Case sensitive" is a fact about the
 * password field, not a warning, so it reads calmer than the amber
 * pill above's own "Visible" state (a real heads-up: the password is
 * currently readable on screen). Still driven by the same real
 * getModifierState('CapsLock') check as before, just worded plainly
 * instead of spelling out the key's name. */
/* A plain inline hint, not a boxed row — no border/background/padding
   of its own, so it reads as a quiet caption under the field (same
   weight as .f-hint elsewhere) instead of a separate alert-like block.
   Still only real, state-driven text: shown/hidden by the same real
   getModifierState('CapsLock') check as before. */
.caps-lock-note {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted);
  margin-top: 6px;
}

/* A real, pre-existing bug this redesign pass caught by actually
 * looking at a fresh render (caught here first, but it was already
 * live on the Practitioner login too — same class, same missing rule):
 * `.caps-lock-note` sets its own `display` unconditionally, which beats
 * the UA stylesheet's `[hidden] { display: none }` at equal specificity
 * (author rules always win ties against UA rules) — so the note
 * rendered permanently visible from first paint, the HTML `hidden`
 * attribute (and auth-login.js's own `capsNote.hidden = true/false`
 * toggling, which is that same attribute) doing nothing at all. Same
 * bug family .submit-btn.is-loading's own comment already documents
 * for .btn-spinner; same fix — an explicit, later, [hidden]-scoped rule
 * restores the real "only show while Caps Lock is actually on" state. */
.caps-lock-note[hidden] {
  display: none;
}

.caps-lock-note svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* ---- Submit button: real in-flight spinner ----
 * .is-loading is toggled by auth-login.js purely around its own real
 * fetch() promise — added the instant the request goes out, removed
 * the instant it resolves/rejects, never a fixed setTimeout. Idle vs
 * loading content is a straight display swap driven by that ONE class
 * (both the spinner and the label+arrow are styled off it below), not
 * an attribute-vs-class fight: the earlier version toggled the
 * spinner's `hidden` attribute while `.btn-spinner` also carried its
 * own `display: inline-block` — at equal specificity an author-
 * stylesheet class rule beats the browser's own `[hidden]{display:none}`
 * UA rule, so the spinner was rendering (and spinning) at all times,
 * sitting right on top of "Log in" — that's the overlap bug. Routing
 * everything through .is-loading instead means there's exactly one
 * rule that ever sets either element's display, so there's nothing
 * left to contradict it. */
.btn-spinner {
  width: 15px;
  height: 15px;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-top-color: #fff;
  border-radius: 50%;
  display: none;
}

.submit-btn.is-loading .btn-spinner {
  display: inline-block;
  animation: spinnerPopIn 180ms ease, btnSpin 0.7s linear infinite;
}

.submit-btn.is-loading [data-login-btn-text],
.submit-btn.is-loading [data-register-btn-text],
.submit-btn.is-loading [data-admin-login-btn-text],
.submit-btn.is-loading .btn-arrow {
  display: none;
}

@keyframes spinnerPopIn {
  from { opacity: 0; transform: scale(0.5); }
  to { opacity: 1; transform: scale(1); }
}

@keyframes btnSpin {
  to { transform: rotate(360deg); }
}

/* Scoped to Login + Sign Up + Admin Login specifically (not a
   redefinition of the shared .submit-btn rule above) — Verify/Reset's
   own plain-text submit buttons stay exactly as they were. Needed here
   because these pages' buttons now hold a spinner span and an arrow
   icon alongside their label text, which the shared rule's plain flat
   background/text-align:center doesn't cover. Gradient + glow + a real
   press-down on :active is the same "confident, unmistakably
   clickable, unmistakably alive" primary-CTA treatment big platforms
   use on their own sign-in/sign-up buttons — not flat var(--gold), a
   subtle vertical lift toward the top edge instead, with a soft
   colored glow (not a flat drop shadow) that deepens on hover. Written
   once here and applied to all three pages rather than duplicated per
   page — the treatment itself isn't page-specific, only the pages
   allowed to have it are. */
body[data-page="login"] .submit-btn,
body[data-page="register"] .submit-btn,
body[data-page="admin-login"] .submit-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 0.95rem;
  padding: 13px;
  background: linear-gradient(180deg, #dd4952 0%, var(--gold) 55%, #b6242e 100%);
  box-shadow: 0 4px 16px rgba(203, 45, 57, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.22);
  transition: transform 150ms ease, box-shadow 150ms ease, opacity var(--transition-fast);
}

body[data-page="login"] .submit-btn:hover:not(:disabled),
body[data-page="register"] .submit-btn:hover:not(:disabled),
body[data-page="admin-login"] .submit-btn:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(203, 45, 57, 0.44), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  opacity: 1;
}

body[data-page="login"] .submit-btn:active:not(:disabled),
body[data-page="register"] .submit-btn:active:not(:disabled),
body[data-page="admin-login"] .submit-btn:active:not(:disabled) {
  transform: translateY(1px) scale(0.985);
  box-shadow: 0 2px 8px rgba(203, 45, 57, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15);
}

.btn-arrow {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  transition: transform var(--transition-fast);
}

body[data-page="login"] .submit-btn:hover:not(:disabled) .btn-arrow,
body[data-page="register"] .submit-btn:hover:not(:disabled) .btn-arrow,
body[data-page="admin-login"] .submit-btn:hover:not(:disabled) .btn-arrow {
  transform: translateX(2px);
}

/* ---- Card shake — real failed-login state only ----
 * .card-shake is added by auth-login.js (and admin-login.js, same
 * technique) on page load ONLY when the form's wrapper carries
 * data-login-failed — set server-side from error('form'), the exact
 * same flash LoginController::submit()/AdminAuthController::submit()
 * set on a genuine wrong-password/locked-account/etc. response. Never
 * added optimistically or client-side-guessed. .admin-login-card is
 * included alongside .auth-card here rather than given its own
 * duplicate keyframe — same shape, same trigger, just a different
 * container class on that one page. */
@keyframes cardShake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-8px); }
  40%, 80% { transform: translateX(8px); }
}

.auth-card.card-shake,
.admin-login-card.card-shake {
  animation: cardShake 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
}

@media (max-width: 900px) {
  .login-orbit {
    height: 150px;
  }

  .ambient-glow {
    width: 160px;
    height: 160px;
    margin: -80px 0 0 -80px;
  }

  .radar-sweep {
    width: 220px;
    height: 220px;
    margin: -110px 0 0 -110px;
  }
}

@media (max-width: 480px) {
  .security-status-pill {
    font-size: 0.68rem;
    padding: 5px 11px;
  }
}

/* =================================================================
 * Sign Up page — 2026 redesign pass, continued (icons-in-fields + the
 * "How to Obtain your Practitioner ID" banner). Same scoping discipline
 * as the block above: new class names only Sign Up's own markup
 * renders, nothing here touches the shared .f-row/.modern-input-
 * equivalent rules Verify/Reset still use.
 * ================================================================= */

/* ---- Field icons ----
 * One flat icon per field (person / ID card / envelope / lock / shield-
 * check), same stroke-only vocabulary as every icon elsewhere on this
 * platform (1.75-2.2 stroke, round caps/joins, no fill except an
 * occasional solid dot) — not the reference's mismatched glyph set.
 * .input-icon-wrap only adds left padding for the icon; on the two
 * password fields it's combined with the existing .pwd-field-wrap
 * class (already styled above) for the right-side show/hide toggle —
 * both paddings apply together since they touch different sides. */
.input-icon-wrap {
  position: relative;
}

/* `.input-icon-wrap input { padding-left: 42px }` alone LOSES this
   fight, always, regardless of where it sits in the file: the shared
   rule above (.f-row input[type='text'], ...) is two class/attribute-
   level selectors (.f-row + [type=...]) against this wrapper's one
   (.input-icon-wrap) — (0,2,1) beats (0,1,1) on specificity alone, no
   tie, so the shared rule's own padding-left:14px always won and the
   icon sat on top of the first couple of characters. Matching the same
   two-class-level shape plus this wrapper's own class — three
   class-level selectors here — is what actually wins, deterministically,
   not by relying on cascade order. Same fix, same reasoning, applied to
   .pwd-field-wrap's own padding-right below (that one was losing to the
   shared rule too, so the eye-toggle button on Login's password field
   and both password fields here were quietly overlapping their own
   input text before this fix, not just this Practitioner ID field). */
.f-row .input-icon-wrap input[type='text'],
.f-row .input-icon-wrap input[type='email'],
.f-row .input-icon-wrap input[type='password'] {
  padding-left: 42px;
}

.field-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--muted);
  pointer-events: none;
  transition: color 150ms ease;
}

.input-icon-wrap:focus-within .field-icon {
  color: var(--gold);
}

