/*
 * shadcn design-language tokens + component CSS, hand-ported for the Go-template auth
 * frontend (the "new-york" neutral palette). This is committed output - there is no
 * Tailwind build in CI. It reproduces shadcn's CSS-variable token set and the class
 * structure of the button/input/card/label components we use. It is NOT the shadcn
 * React library; it is the visual layer only.
 *
 * The auth surface is LIGHT-ONLY: it does not follow the OS dark preference
 * (color-scheme: light below pins native controls light too). It is a hosted
 * brand surface, so it stays on the porcelain palette regardless of the viewer's
 * system theme.
 */

/* Söhne Buch (regular weight) - the Atlan UI typeface. The URL is relative to this
   stylesheet, so it resolves to /auth/static/soehne-buch.woff2 in the embedded asset
   handler. font-display: swap renders the system fallback first, then swaps to Söhne
   once loaded, so first paint is never blocked. */
@font-face {
  font-family: "Soehne";
  src: url("soehne-buch.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* coss-ui design tokens (packages/coss-ui/src/styles/coss.css), converted from
 * coss's hex to this sheet's HSL-triplet format so every hsl(var(--x)) usage
 * keeps working. Warm porcelain neutrals with a near-black primary - the same
 * ramp the dashboard's coss components resolve through. */
:root {
  --background: 60 14% 99%;      /* #fdfdfc porcelain */
  --foreground: 60 3% 15%;       /* #262624 */
  --card: 60 20% 99.5%;          /* #fffffe */
  --card-foreground: 60 3% 15%;
  /* coss keeps primary emphasis near-black, not a product blue. */
  --primary: 60 3% 15%;          /* #262624 */
  --primary-foreground: 60 14% 99%;
  --secondary: 60 12% 96%;       /* #f6f6f3 */
  --secondary-foreground: 60 3% 15%;
  --muted: 60 12% 96%;
  --muted-foreground: 60 3% 42%; /* #6f6f69 */
  --accent: 60 10% 94%;          /* #f1f1ee */
  --destructive: 0 84.2% 60.2%;
  --destructive-foreground: 0 0% 98%;
  --error-background: 0 84.2% 60.2%;
  --error-foreground: 0 72% 32%;
  --border: 60 11% 89%;          /* #e5e5e0 */
  --input: 60 13% 83%;           /* #d7d7d0 */
  --ring: 60 3% 47%;             /* #7b7b74 */
  --radius: 0.625rem;            /* coss --radius */
  /* Pin native controls (inputs, autofill, scrollbars) to light so a viewer in
     OS dark mode still gets the light auth surface. */
  color-scheme: light;
}

* {
  border-color: hsl(var(--border));
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh; /* mobile browsers: the visible height, not the one behind the toolbar */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
  font-family: "Soehne", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding: 2rem 1rem;
  position: relative;
}

/* Uniform graph-paper grid covering the viewport (Efferd-style). The grid frames
   the auth box: its lines run right up to the box edges so the form reads as one
   grid cell. Lives on a fixed pseudo-element behind everything. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, hsl(var(--border) / 0.7) 1px, transparent 1px),
    linear-gradient(to bottom, hsl(var(--border) / 0.7) 1px, transparent 1px);
  background-size: 14rem 14rem;
}

/* The auth box - the form framed as a single grid cell: a crisp border in the grid
   line color, solid fill so the grid reads only OUTSIDE the box, centered content
   with the brand mark at the top. */
.auth-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 28rem;
  padding: 2.5rem 2rem;
  background-color: hsl(var(--background));
  border: 1px solid hsl(var(--border));
}

/* Brand mark - the Atlan wordmark at the top of the auth box, swapped by theme. */
.brand { display: flex; align-items: center; margin-bottom: 1.5rem; }
.brand-mark { height: 1.875rem; width: auto; }
/* Light-only surface: always the light (blue) wordmark; the dark asset is unused. */
.brand-mark-dark { display: none; }

/* No card chrome - the .auth-box provides the frame. The card is just the content
   column, full width within the box. */
.card {
  width: 100%;
  min-width: 0;
  color: hsl(var(--card-foreground));
}
.card-header { padding: 0 0 1.25rem; text-align: center; }
/* Agent client names arrive here as colon-joined machine identifiers with no spaces or
   hyphens, so the browser has no legal break point and the name spills past the box edges.
   `anywhere` over `break-word`: it also shrinks min-content width, so the card can't grow. */
.card-title { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; margin: 0; overflow-wrap: anywhere; }
.card-description { color: hsl(var(--muted-foreground)); margin: 0.5rem 0 0; overflow-wrap: anywhere; }
.card-description strong { color: hsl(var(--foreground)); font-weight: 500; }
.card-content { padding: 0; display: flex; flex-direction: column; gap: 0.875rem; }

/* Label */
.label {
  display: block;
  font-size: 0.875rem;
  font-weight: 500;
  margin-bottom: 0.375rem;
}

/* Input */
/* coss input: rounded-lg with a faint inner top shadow; focus draws border-ring
   plus a 3px ring at ring/24. */
.input {
  display: flex;
  width: 100%;
  height: 2.5rem;
  border-radius: var(--radius);
  border: 1px solid hsl(var(--input));
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
  padding: 0 0.75rem;
  font-size: 0.875rem;
  box-shadow: 0 1px 2px 0 hsl(var(--foreground) / 0.05), inset 0 1px 0 0 hsl(0 0% 0% / 0.04);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.input:focus-visible {
  outline: none;
  border-color: hsl(var(--ring));
  box-shadow: 0 0 0 3px hsl(var(--ring) / 0.24);
}
/* The OTP code field reads as a code entry: centered, spaced, tabular. */
.input-code {
  text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.125rem;
  letter-spacing: 0.4em;
  padding-right: calc(0.75rem - 0.4em);
}

/* One consistent vertical rhythm for the auth form and its code step, so the
   groups read as evenly spaced instead of relying on ad-hoc element margins.
   The :not(.hidden) guard is load-bearing: a bare #code-step ID rule would
   outrank the .hidden utility and the code step could never be hidden. */
#otp-form { display: flex; flex-direction: column; gap: 0.875rem; }
#code-step:not(.hidden) { display: flex; flex-direction: column; gap: 0.875rem; }
/* An empty status line must not leave a phantom gap in the flex column. */
#otp-status:empty { display: none; }

/* OTP segmented field (coss-style): six single-char slots assembled into the
   hidden #code field the form posts. Digits render in the page font (Geist Mono
   on the centered pages). */
.otp-slots { display: flex; gap: 0.5rem; width: 100%; }
.otp-slot {
  flex: 1;
  min-width: 0;
  height: 2.75rem;
  padding: 0;
  text-align: center;
  font-size: 1.125rem;
  color: hsl(var(--foreground));
  background-color: hsl(var(--background));
  border: 1px solid hsl(var(--input));
  border-radius: 0.625rem;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.otp-slot:focus, .otp-slot:focus-visible {
  outline: none;
  z-index: 1;
  border-color: hsl(var(--ring));
  box-shadow: 0 0 0 3px hsl(var(--ring) / 0.24);
}
.otp-slot:disabled { opacity: 0.6; cursor: not-allowed; }

.turnstile-field {
  min-height: 4.0625rem;
  margin-top: 0.75rem;
  width: 100%;
}

/* Button */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 2.5rem;
  padding: 0 1rem;
  border-radius: calc(var(--radius) - 2px);
  border: 1px solid transparent;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  width: 100%;
  text-decoration: none;
  transition: background-color 0.15s, opacity 0.15s;
}
/* coss primary CTA: near-black fill with a subtle top inset highlight and a soft
   tinted shadow; hover fades the fill to 90%. */
.btn-primary {
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
  border-color: hsl(var(--primary));
  box-shadow: 0 1px 2px 0 hsl(var(--primary) / 0.24), inset 0 1px 0 0 hsl(0 0% 100% / 0.16);
}
.btn-primary:hover { background-color: hsl(var(--primary) / 0.9); opacity: 1; }
.btn-primary:active { box-shadow: inset 0 1px 0 0 hsl(0 0% 0% / 0.08); }
.btn-outline {
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
  border-color: hsl(var(--border));
}
.btn-outline:hover { background-color: hsl(var(--secondary)); }
.btn-destructive { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); }
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring));
}
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
.btn svg { width: 1.05rem; height: 1.05rem; flex-shrink: 0; }

/* Loading state: the JS adds .is-loading, which hides the label and shows a spinner. */
.btn.is-loading { color: transparent; pointer-events: none; }
.btn.is-loading::after {
  content: "";
  position: absolute;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: hsl(var(--primary-foreground));
  animation: spin 0.6s linear infinite;
}
.btn-outline.is-loading::after, .btn-destructive.is-loading::after { color: hsl(var(--foreground)); }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::after { animation-duration: 1.6s; }
}

/* Link-styled button - the "Use a different email" / "Resend code" affordances. */
.btn-link {
  background: none;
  border: none;
  border-radius: 0.125rem;
  padding: 0;
  width: auto;
  height: auto;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.btn-link:hover { color: hsl(var(--foreground)); }
.btn-link:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring));
}
.btn-link:disabled { color: hsl(var(--muted-foreground)); opacity: 0.6; cursor: not-allowed; text-decoration: none; }

/* Divider */
.divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: hsl(var(--muted-foreground));
  font-size: 0.6875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background-color: hsl(var(--border)); }

/* Scope list (consent). Each row leads with a plain-English label so the grant is
   legible at a glance; the raw scope sits below as secondary mono detail. There is
   deliberately NO checkmark: these are requests, not grants, and a checkmark reads as
   "already approved/safe" on the one irreversible click in the flow. */
.scopes { list-style: none; margin: 0; padding: 0.25rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
.scope {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding: 0.625rem 0.75rem;
  background-color: hsl(var(--secondary));
  border-radius: calc(var(--radius) - 2px);
}
/* Same unbreakable identifiers land in these rows - client IDs, actor subjects, redirect
   hostnames - so they wrap on the same terms. */
.scope-label { font-size: 0.8125rem; font-weight: 500; overflow-wrap: anywhere; }
.scope-raw { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.75rem; color: hsl(var(--muted-foreground)); overflow-wrap: anywhere; }

/* Reassurance line above the Deny/Allow row: lowers the stakes of Allow by telling the
   user the grant is reversible. */
.scope-revoke { margin: 0.25rem 0 0.875rem; }

/* Org picker list */
.org-list { display: flex; flex-direction: column; gap: 0.5rem; }
/* A picker row reads as org-name + chevron, left-aligned, taller than a plain btn. */
.org-btn {
  justify-content: space-between;
  height: auto;
  min-height: 3rem;
  padding: 0.625rem 0.875rem;
  text-align: left;
}
.org-name { font-weight: 500; }
.org-btn svg { flex-shrink: 0; width: 1rem; height: 1rem; color: hsl(var(--muted-foreground)); }
.picker-recovery { align-self: center; margin-top: 0.25rem; }

/* Helper / error text. margin:0 so the browser's default <p> margin doesn't
   stack on top of the flex-gap rhythm in the code step. */
.muted { color: hsl(var(--muted-foreground)); font-size: 0.8125rem; margin: 0; }
/* In the flex code step the gap handles spacing, so the label needs no margin. */
#code-step .label { margin-bottom: 0; }
.error-text {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.625rem 0.75rem;
  border-radius: calc(var(--radius) - 2px);
  background-color: hsl(var(--error-background) / 0.1);
  color: hsl(var(--error-foreground));
  font-size: 0.8125rem;
}
.error-text svg { flex-shrink: 0; width: 1rem; height: 1rem; margin-top: 0.0625rem; }
/* The server-rendered error takes focus programmatically (tabindex=-1) so assistive
   tech lands on it; it is not an interactive control, so no focus ring. */
.error-text[tabindex="-1"]:focus { outline: none; }
.btn-row { display: flex; gap: 0.5rem; }
.btn-row .btn { width: auto; flex: 1; }

/* The footer row under the code field: resend on the left, change-email on the right.
   Spacing comes from the #code-step flex gap, so no own margin. */
.field-actions { display: flex; align-items: center; justify-content: space-between; }

/* Cross-link between the sign-in and sign-up pages. */
.card-footer { text-align: center; color: hsl(var(--muted-foreground)); font-size: 0.8125rem; margin: 0.25rem 0 0; }
.card-footer a { color: hsl(var(--foreground)); font-weight: 500; text-decoration: underline; text-underline-offset: 2px; }

/* Legal fine-print under the sign-in options, matching the desktop app's welcome
   screen: muted throughout, and the two links underline brighter on hover. */
.legal { text-align: center; color: hsl(var(--muted-foreground)); font-size: 0.75rem; line-height: 1.5; margin: 0.25rem auto 0; max-width: 16rem; text-wrap: balance; }
.legal a { color: inherit; text-decoration: underline; text-decoration-color: hsl(var(--muted-foreground) / 0.4); text-underline-offset: 2px; }
.legal a:hover { text-decoration-color: currentColor; }

/* Spacing utilities - replace the inline <div style="height:..."> spacer hacks. */
.mt-3 { margin-top: 0.75rem; }
.hidden { display: none; }

/* The login page's two screens. `is-code-step` on the card (server-set on a code-step
   re-render, toggled by app.js otherwise) swaps the heading, hides the other sign-in
   methods and shows the code field, so the code screen asks for one thing only. */
.card.is-code-step .email-only { display: none; }
.card:not(.is-code-step) .code-only { display: none; }

/* A send failure in #otp-status wears the error look (app.js swaps the class and role);
   a confirmation stays muted. Both center under the form like the other helper lines. */
#otp-status { text-align: center; }
#otp-status.error-text { text-align: left; }
.otp-spam-hint { text-align: center; }

/* Company SSO disclosure: the summary is the button, so the native marker goes and a
   chevron says it opens in place; the form below it keeps the card's rhythm. */
.sso-details > summary { list-style: none; }
.sso-details > summary::-webkit-details-marker { display: none; }
.sso-details > summary .sso-chevron { transition: transform 0.15s; }
.sso-details[open] > summary .sso-chevron { transform: rotate(180deg); }
.sso-form { display: flex; flex-direction: column; gap: 0.625rem; margin-top: 0.875rem; }
.sso-form .label { margin-bottom: 0; }

/* Helper under a field (the org-name hint); the gap below comes from the button's mt-3. */
.field-hint { margin-top: 0.375rem; }

/* "Signed in as ..." on the consent screen: the account being delegated, with the way out. */
.account-line { text-align: center; }
.account-line strong { color: hsl(var(--foreground)); font-weight: 500; }
.account-line a { color: hsl(var(--foreground)); text-decoration: underline; text-underline-offset: 2px; }

/* Delegated-access rows: name, id and dates on the left, a compact Revoke on the right;
   the row wraps on a narrow screen so the button drops under the text. */
.grant-row { flex-direction: row; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem 1rem; }
.grant-info { display: flex; flex-direction: column; gap: 0.125rem; min-width: 0; flex: 1 1 12rem; }
.grant-revoke { flex: 0 0 auto; }

/* --- "centered" auth treatment: login + signup only ----------------------
 * Ported from the devl.dev centered-signin / centered-signup reference and
 * aligned to the dashboard's coss-ui typography: Geist for headings, Geist
 * Mono for body / other text (both self-hosted - the auth CSP allows only
 * same-origin fonts). Scoped to body.auth-centered, which the layout's
 * "bodyclass" block sets by default, so every page of the sign-in journey
 * (login, picker, org-name, consent, error, ...) shares one look and a
 * mid-flow page never reads as a different site. The primary button is
 * near-black to match the reference; swap the .btn-primary background lines
 * to hsl(var(--primary)) for Atlan blue. */
@font-face {
  font-family: "Geist";
  src: url("geist-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("geist-mono-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Headings and body text both use Geist (the dashboard's UI font). Geist Mono
   is reserved for the OTP code boxes below - it reads as a code, not prose. */
body.auth-centered {
  font-family: "Geist", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  /* White page with a faint SVG-turbulence grain on top (data: URI -
     self-contained, CSP img-src allows data:). */
  background-color: hsl(0 0% 100%);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 160px 160px;
}
body.auth-centered .otp-slot {
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* The reference is a clean, flat card - no grid, no shadow, no page tint. */
body.auth-centered::before { display: none; }

body.auth-centered .auth-box {
  border-radius: 1rem;
  padding: 2.75rem 2.25rem;
}
/* Headings: Geist (the dashboard's heading font). */
body.auth-centered .card-title {
  font-family: "Geist", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  font-size: 1.625rem;
  letter-spacing: -0.01em;
}

/* Rounder, slightly taller controls, matching the reference. */
body.auth-centered .input,
body.auth-centered .btn { border-radius: 0.625rem; height: 2.75rem; }
/* A secondary action inside a row (the grants page's Revoke) is sized to its label. */
body.auth-centered .btn.btn-compact { width: auto; height: 2.25rem; padding: 0 0.875rem; border-radius: 0.5rem; }

/* The primary CTA color comes from the coss --primary token (near-black in
   light, near-white in dark) via the base .btn-primary rule - no override here. */

/* --- Aurora shimmer backdrop --------------------------------------------
 * The onboarding flow's slow-drifting gradient wash, ported 1:1 from the
 * dashboard (apps/dashboard/src/styles/aurora.css, the <AuroraBackground>).
 * Decorative, aria-hidden, pointer-events:none; the opaque card paints above
 * it so the shimmer reads in the margins. Brand ramp adapted to this sheet's
 * Atlan-blue token (the coss --primary-* tokens don't exist here). */
@keyframes aurora {
  from { background-position: 50% 50%, 50% 50%; }
  to   { background-position: 350% 50%, 350% 50%; }
}
.aurora-layer {
  --aurora: repeating-linear-gradient(100deg,
    hsl(238 74% 58%) 10%, hsl(238 74% 47%) 15%, hsl(238 80% 70%) 20%,
    hsl(238 74% 55%) 25%, hsl(238 74% 47%) 30%);
  --white-gradient: repeating-linear-gradient(100deg,
    #fff 0%, #fff 7%, transparent 10%, transparent 12%, #fff 16%);

  pointer-events: none;
  position: fixed;
  inset: -20px;
  z-index: -1;
  background-image: var(--white-gradient), var(--aurora);
  background-size: 300%, 200%;
  background-position: 50% 50%, 50% 50%;
  filter: invert(1) blur(10px);
  opacity: 0.24;
  will-change: background-position;
  animation: aurora var(--aurora-speed, 60s) ease-in-out infinite alternate;
  /* Hidden by default so it never flashes on load before the shader mounts.
     init.js reveals it (body.aurora-fallback) ONLY when the shader can't run. */
  display: none;
}
.aurora-layer::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--white-gradient), var(--aurora);
  background-size: 200%, 100%;
  background-attachment: fixed;
  mix-blend-mode: difference;
}
/* Fade the wash toward a corner so it reads as ambient, not a flat fill. */
.aurora-layer[data-radial] {
  -webkit-mask-image: radial-gradient(ellipse at 100% 0%, black 10%, transparent 70%);
  mask-image: radial-gradient(ellipse at 100% 0%, black 10%, transparent 70%);
}

/* Paper Shaders mesh-gradient canvas mounts here (progressive enhancement over
   the CSS aurora). Same fixed full-bleed backdrop, faded toward a corner. When
   the shader mounts, init.js sets body.shader-active to hide the CSS fallback. */
#shader-bg {
  position: fixed;
  inset: -20px;
  z-index: -1;
  pointer-events: none;
  opacity: 1;
  /* Soft edge fade so the bloom dissolves into the page toward the corners
     rather than reading as a contained disc on flat white. */
  -webkit-mask-image: radial-gradient(circle at 50% 45%, black 35%, transparent 88%);
  mask-image: radial-gradient(circle at 50% 45%, black 35%, transparent 88%);
}
#shader-bg canvas { display: block; width: 100% !important; height: 100% !important; }
/* Fallback only: shown when init.js determines the shader cannot mount (no
   WebGL2, or init threw). The default is hidden, so the common WebGL path
   never flashes the aurora on reload. */
body.aurora-fallback .aurora-layer { display: block; }
@media (prefers-reduced-motion: reduce) {
  .aurora-layer { animation: none; }
}

/* ==========================================================================
 * Atlan mascot - the matte two-dot companion from the dashboard app (branch
 * "activate"), ported for the login/signup card (replaces the wordmark there).
 * Structure lives in templates/mascot.html; this carries the fixed brand
 * palette and the motion, scoped under .atl-mascot. Only the states the auth
 * flow uses are ported: idle (rest), attentive (watching you type your email),
 * shy (eyes closed while you type the one-time code), error.
 * ========================================================================== */

.atl-mascot { display: block; margin: 0 auto; }

/* Resting motion: a slow breath, an occasional blink, a pulsing floor shadow. */
.atl-mascot .m-root { transform-origin: 100px 124px; }
.atl-mascot .m-body {
  transform-box: view-box;
  transform-origin: 100px 104px;
  animation: m-breathe 3.8s ease-in-out infinite;
}
.atl-mascot .m-eyes {
  transform-box: view-box;
  transform-origin: 103px 106px;
  animation: m-blink 4.8s ease-in-out infinite;
  transition: transform 0.3s ease;
}
.atl-mascot .m-floor { animation: m-floorpulse 3.8s ease-in-out infinite; }
.atl-mascot .m-arc,
.atl-mascot .m-ring,
.atl-mascot .m-orbiter,
.atl-mascot .m-spark { opacity: 0; }
.atl-mascot .m-spark { transform-box: fill-box; transform-origin: center; }

/* Fixed brand illustration palette. */
.atl-mascot .m-floor { fill: #04062e; opacity: 0.75; }
.atl-mascot .m-ring { stroke: #7fd6f5; }
.atl-mascot .m-orbiter-outer { fill: #bfeaff; }
.atl-mascot .m-orbiter-inner { fill: #eafaff; }
.atl-mascot .m-pod-brow { stroke: rgba(196, 214, 255, 0.7); }
.atl-mascot .m-eye-shadow { fill: #070d4a; }
.atl-mascot .m-eye-hi { fill: #f2fdff; }
.atl-mascot .m-arc { stroke: #9fecff; }
.atl-mascot .m-lid { stroke: #9fecff; opacity: 0; }
.atl-mascot .m-spark path { fill: #bfeaff; }

/* Gradient stops. */
.mg-pod-0 { stop-color: #3f5cff; }
.mg-pod-1 { stop-color: #2641f4; }
.mg-pod-2 { stop-color: #1a2cc8; }
.mg-pod-hi-0 { stop-color: rgba(150, 180, 255, 0.5); }
.mg-pod-hi-1 { stop-color: rgba(150, 180, 255, 0); }
.mg-pod-ao-0 { stop-color: rgba(10, 16, 90, 0); }
.mg-pod-ao-1 { stop-color: rgba(9, 14, 80, 0.4); }
.mg-eye-0 { stop-color: #c4f6ff; }
.mg-eye-1 { stop-color: #6fe0ff; }
.mg-eye-2 { stop-color: #34c4f6; }
.mg-eye-glow-0 { stop-color: rgba(90, 224, 255, 0.55); }
.mg-eye-glow-1 { stop-color: rgba(90, 224, 255, 0); }

.atl-mascot .m-dot,
.atl-mascot .m-eye-hi,
.atl-mascot .m-eye-glow,
.atl-mascot .m-lid,
.atl-mascot .m-arc { transition: opacity 0.45s ease; }

/* Attentive: watching you type your email - leans in. The eyes themselves
   track the pointer (app.js translates each .m-eye group). */
.atl-mascot.s-attentive .m-body { animation: m-lean 2.8s ease-in-out infinite; }

/* Shy: you're typing your one-time code - eyes properly closed: the dot, its
   highlight AND the glow all fade out, leaving only soft downward lid curves
   (resting lashes). Not peeking. */
.atl-mascot.s-shy .m-eyes { animation: none; }
.atl-mascot.s-shy .m-dot,
.atl-mascot.s-shy .m-eye-hi,
.atl-mascot.s-shy .m-eye-glow { opacity: 0; }
.atl-mascot.s-shy .m-lid { opacity: 1; }
.atl-mascot.s-shy .m-body { animation: m-breathe 5.5s ease-in-out infinite; }

/* Error: dimmed, eyes soften - "that didn't work", still inviting a retry. */
.atl-mascot.s-error { filter: grayscale(0.5) brightness(0.94); }
.atl-mascot.s-error .m-body { animation: m-breathe 5s ease-in-out infinite; }
.atl-mascot.s-error .m-eyes { transform: scaleY(0.6); animation: none; }

@keyframes m-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.03); }
}
@keyframes m-floorpulse {
  0%, 100% { transform: scaleX(1); opacity: 0.8; }
  50% { transform: scaleX(0.9); opacity: 0.55; }
}
@keyframes m-blink {
  0%, 92%, 100% { transform: scaleY(1); }
  96% { transform: scaleY(0.08); }
}
@keyframes m-lean {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-5px) scale(1.04); }
}

/* Honour reduced-motion: hold the friendly resting pose, no looping motion.
   The shy eye-close still applies (it is a transform, not an animation). */
@media (prefers-reduced-motion: reduce) {
  .atl-mascot .m-body,
  .atl-mascot .m-eyes,
  .atl-mascot .m-floor,
  .atl-mascot .m-spark { animation: none !important; }
}
