/* ═══════════════════════════════════════════════════════════════════════════
   DSA MITRA — AUTH DESIGN SYSTEM
   Shared by login.html, signup.html and reset-password.html.

   This file replaced three separately-drifting stylesheets. login.html and
   signup.html had a full BLUE system (--brand:#1A52C5) with a dark+orange theme
   bolted on top via ~20 `!important` overrides, and reset-password.html was
   still pure blue. Change a colour here and all three move together — that is
   the whole point of this file existing.

   Fonts (loaded by the pages): Syne = display, Instrument Sans = body,
   IBM Plex Mono = micro-labels. Same trio as index.html.
   ═══════════════════════════════════════════════════════════════════════════ */

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}

/* ── THE PALETTE IS ONE OVERRIDABLE BLOCK ───────────────────────────────────
   Everything below this rule paints through these tokens and NOTHING paints a
   brand colour literally. That is not tidiness — it is the whole white-label
   mechanism: on a tenant's own domain the server appends a <style> that
   re-declares this block from their published website's theme (see
   backend/src/modules/website/brandInject.ts → authTheme), and the page
   repaints navy/gold/whatever without a second stylesheet existing.

   So: a raw `rgba(22,163,74,…)` added below is a colour that CANNOT follow a
   tenant's theme. Reach for a token, or add one.

   The *-rgb triplets exist because half these colours are used at an alpha —
   rgba() needs the channels, not a hex. Each mirrors its hex twin above it;
   change one, change both.

   --green / --red are SEMANTIC, not brand: a success tick stays green on a navy
   site. They are deliberately NOT overridden by the injector.               */
:root{
  /* brand3 is the ACCENT-ON-CANVAS colour. On the old dark canvas that meant a
     LIGHTER tint (#FB923C); on this light one it has to be a DARKER one, or every
     accent it paints — kickers, links, focused labels — goes unreadable. */
  --brand:#16A34A;  --brand2:#22C55E;  --brand3:#15803D;
  --brand-rgb:22,163,74;   /* = --brand  */
  --brand2-rgb:34,197,94;  /* = --brand2 */
  --ink:#0C1F14;    --ink-dim:#4B5F55; --ink-faint:#8A9A91;
  --ink-rgb:20,60,38;      /* canvas ink — hairlines, shadows, faint tints  */
  --deep-rgb:20,83,45;     /* darkest brand shade — the far light beam only  */
  --bg:#F7FBF8;
  --bg-rgb:247,251,248;    /* = --bg; the vignette + ghost fill need it at alpha */
  --card2:#FBFEFC;         /* card gradient, bottom stop  */
  --field-bg:#F5FAF7;      /* input resting fill          */
  /* the mark-badge plate the logo sits on — a dark brand-tinted square */
  --badge:linear-gradient(150deg,#0F2A1C,#163A27);
  /* the brand panel behind the mark. Deep on purpose: it has to hold white text
     and a white logo plate, and it sits next to a near-white form column. */
  --panel:linear-gradient(158deg,#06180E 0%,#0C3A1E 52%,#12572C 100%);
  --panel-ink:#FFFFFF;
  /* ink ON a --brand fill. White works for any primary dark enough to be a
     primary; the injector flips it for a light one rather than shipping
     white-on-yellow. */
  --btn-ink:#FFFFFF;
  --line:rgba(var(--ink-rgb),0.10);
  --line2:rgba(var(--ink-rgb),0.07);
  /* --green is the FILL/mark green; --green-ink is its text-on-white twin, which
     has to be darker to clear 4.5:1 (#16A34A only manages 3.3:1). */
  --green:#16A34A;  --green-rgb:22,163,74;  --green-ink:#15803D;  --red:#DC2626;
  --mono:'IBM Plex Mono',ui-monospace,'Courier New',monospace;
}

html{font-size:16px}
body{
  font-family:'Instrument Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  background:var(--bg); color:var(--ink);
  min-height:100dvh; line-height:1.6;
  -webkit-font-smoothing:antialiased; letter-spacing:-0.005em;
  display:flex; align-items:center; justify-content:center;
  padding:32px 20px; position:relative; overflow-x:hidden;
}
/* The split pages lay themselves out edge to edge, so body stops centring a box
   and just gets out of the way. Scoped to a class rather than :has(.split) so the
   rule is deterministic — and so a page that has NOT been converted keeps the
   original centred-card layout instead of silently half-adopting this one. */
body.auth-split{display:block; padding:0; align-items:stretch}
h1,h2{font-family:'Syne',sans-serif; line-height:1.15; letter-spacing:-0.02em}
a{color:inherit; text-decoration:none}
button{font-family:inherit; cursor:pointer}
img,svg{max-width:100%}


/* ═══ ATMOSPHERE ════════════════════════════════════════════════════════════
   Seven stacked layers, injected by auth-bg.js. Order matters: everything below
   sits at z-index 0-1, the content shell sits at 2, grain at 3 (over everything,
   including the card — real film grain doesn't stop at the edge of an object).  */

/* ONE light source, deliberately DIM. The page's life comes from MOTION, not
   brightness. A big bright blob reads as smudge; a small dim one reads as light. */
.aurora{
  position:fixed; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(46% 40% at 50% 34%, rgba(var(--brand-rgb),0.10) 0%, rgba(var(--brand-rgb),0.03) 44%, transparent 70%),
    radial-gradient(120% 70% at 50% 120%, rgba(var(--deep-rgb),0.06) 0%, transparent 60%);
  animation:drift 24s ease-in-out infinite alternate;
}
/* a thin band of light along the very top of the viewport — implies the room the
   card is lit from, without hazing the whole canvas */
.aurora::after{
  content:''; position:absolute; top:0; left:0; right:0; height:1px;
  background:linear-gradient(90deg,transparent,rgba(var(--brand-rgb),.40),transparent);
}
@keyframes drift{
  0%  {transform:scale(1)    translateY(0)}
  100%{transform:scale(1.06) translateY(-1.2%)}
}

/* THE LATTICE, IN TWO PLANES.
   A single grid is flat. Two — a fine dot-field in front, a coarse line grid
   behind — that shift by DIFFERENT amounts on mouse move produce real parallax.
   The near plane must travel further than the far one; that ratio IS the depth.
   They also give the moving layers a reference frame: motion across nothing just
   looks like a rendering glitch. --px/--py are set by auth-bg.js (range -1..1). */
.plane{
  position:fixed; inset:-8%; z-index:0; pointer-events:none;
  mask-image:radial-gradient(80% 72% at 50% 45%, #000 0%, transparent 100%);
  -webkit-mask-image:radial-gradient(80% 72% at 50% 45%, #000 0%, transparent 100%);
  will-change:transform;
}
.plane.far{
  opacity:.05;
  background-image:
    linear-gradient(rgba(var(--ink-rgb),.9) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--ink-rgb),.9) 1px, transparent 1px);
  background-size:128px 128px;
  transform:translate3d(calc(var(--px,0) * 8px), calc(var(--py,0) * 8px), 0);
}
.plane.near{
  opacity:.16;
  background-image:radial-gradient(circle, rgba(var(--ink-rgb),.55) 1px, transparent 1px);
  background-size:32px 32px;
  transform:translate3d(calc(var(--px,0) * 24px), calc(var(--py,0) * 24px), 0);
}

/* THE SPOTLIGHT — a warm light that TRAILS the cursor. The lag lives in JS (a
   lerp), not in a CSS transition: a transition eases then STOPS, which the eye
   reads as "an animation played". A lerp never quite arrives, which the eye
   reads as "that light has weight". */
.spot{
  position:fixed; inset:0; z-index:0; pointer-events:none;
  background:radial-gradient(420px circle at var(--sx,50%) var(--sy,38%),
              rgba(var(--brand-rgb),.14) 0%, rgba(var(--brand-rgb),.05) 35%, transparent 62%);
}

/* LIGHT BEAMS — two wide, very faint diagonal bars sweeping the canvas on long,
   offset loops (28s / 38s). Slow enough that you never catch one "starting". */
.beams{position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden}
.beam{
  position:absolute; top:-60%; height:220%; width:46vw;
  background:linear-gradient(90deg,transparent,rgba(var(--brand-rgb),.07),transparent);
  transform:rotate(14deg); filter:blur(28px);
}
.beam.b1{left:-50vw; animation:sweep 28s linear infinite}
.beam.b2{left:-50vw; width:30vw; animation:sweep 38s linear infinite; animation-delay:-14s;
         background:linear-gradient(90deg,transparent,rgba(var(--deep-rgb),.035),transparent)}
@keyframes sweep{
  0%  {transform:translateX(0)     rotate(14deg)}
  100%{transform:translateX(190vw) rotate(14deg)}
}

/* DATA TRACERS — thin comets falling down grid columns. A CRM is a thing data
   flows through, so this is meaning, not decoration.
   The durations are all DIFFERENT on purpose (11/15/12/17/20s): if they shared a
   period the eye would lock onto the pattern, and a visible pattern is exactly
   what makes an animated background feel cheap. The negative delays start them
   mid-flight, so the page isn't empty for the first few seconds. */
.flow{position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden}
.tr{
  position:absolute; top:-25%; width:1px; height:24%;
  background:linear-gradient(180deg,transparent,rgba(var(--brand2-rgb),.5),rgba(var(--brand-rgb),.85),transparent);
  animation:fall 9s linear infinite;
}
.tr.t1{left:12%;              animation-duration:11s; animation-delay:-2s}
.tr.t2{left:31%; opacity:.55; animation-duration:15s; animation-delay:-9s}
.tr.t3{left:69%; opacity:.7;  animation-duration:12s; animation-delay:-5s}
.tr.t4{left:87%; opacity:.45; animation-duration:17s; animation-delay:-13s}
.tr.t5{left:50%; opacity:.3;  animation-duration:20s; animation-delay:-7s}
@keyframes fall{
  0%  {transform:translateY(0);    opacity:0}
  12% {opacity:1}
  88% {opacity:1}
  100%{transform:translateY(560%); opacity:0}
}

/* NETWORK NODES — points that flare and die, like activity on a live system. */
.nodes{position:fixed; inset:0; z-index:0; pointer-events:none}
.nd{
  position:absolute; width:5px; height:5px; border-radius:50%;
  background:var(--brand3); opacity:0;
  box-shadow:0 0 12px 2px rgba(var(--brand2-rgb),.75);
  animation:blip 7s ease-in-out infinite;
}
@keyframes blip{
  0%,72%,100%{opacity:0;   transform:scale(.5)}
  78%        {opacity:.95; transform:scale(1)}
  88%        {opacity:0;   transform:scale(1.9)}
}
.nd:nth-child(1){left:16%; top:22%; animation-delay:-0.5s}
.nd:nth-child(2){left:82%; top:31%; animation-delay:-3.2s; animation-duration:9s}
.nd:nth-child(3){left:24%; top:71%; animation-delay:-5.7s; animation-duration:11s}
.nd:nth-child(4){left:74%; top:78%; animation-delay:-1.9s; animation-duration:8s}
.nd:nth-child(5){left:47%; top:12%; animation-delay:-6.6s; animation-duration:12s}
.nd:nth-child(6){left:9%;  top:52%; animation-delay:-4.1s; animation-duration:10s}

/* ═══ GHOST WIDGETS ═════════════════════════════════════════════════════════
   Pieces of the actual CRM — a KPI tile, a pipeline, a lead card, a bar chart —
   drifting in the space BEHIND the login card. Not decoration and not a
   screenshot: a reminder of what is on the other side of this door.

   Three rules keep them from becoming clutter, and all three matter:

   1. DEPTH OF FIELD. The camera is focused on the card, so everything behind it
      is out of focus. Each ghost's blur, opacity and parallax distance are tied
      to ONE number (--d, its depth): far => blurrier, dimmer, moves less.
      Without this they read as stickers pasted on the wallpaper.

   2. THEY NEVER TOUCH THE CENTRE. All of them live out at the edges, where the
      vignette is already eating them. You came here to sign in, not to read a
      chart.

   3. PARALLAX IS PURE CSS. --px/--py are set once per frame by auth-bg.js; each
      ghost multiplies them by its own --d. No per-element JS, so adding a ghost
      costs nothing at runtime.                                                 */
.ghosts{position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden}

.ghost{
  position:absolute;
  font-family:var(--mono); color:var(--ink);
  background:linear-gradient(180deg, rgba(255,255,255,.94), rgba(var(--bg-rgb),.94));
  border:1px solid rgba(var(--ink-rgb),.10);
  border-radius:14px; padding:14px 16px;
  box-shadow:0 24px 60px -22px rgba(var(--ink-rgb),.35);
  /* the ONE number that defines this ghost's distance from the camera */
  --d:16;
  opacity:calc(.05 + var(--d) / 260);          /* nearer => more present */
  filter:blur(calc((34 - var(--d)) * 0.09px)); /* nearer => sharper      */
  transform:translate3d(calc(var(--px,0) * var(--d) * 1px),
                        calc(var(--py,0) * var(--d) * 1px), 0);
  will-change:transform;
}
/* a lit top edge, exactly like the real card — same light source, so they read
   as being in the same room */
.ghost::before{
  content:''; position:absolute; top:-1px; left:16%; right:16%; height:1px;
  background:linear-gradient(90deg,transparent,rgba(var(--brand-rgb),.5),transparent);
}

.g-label{font-size:8.5px; letter-spacing:.18em; text-transform:uppercase; color:var(--ink-faint); margin-bottom:7px}
.g-big{font-family:'Syne',sans-serif; font-size:20px; font-weight:800; letter-spacing:-.01em}
/* an UP-trend tick — semantic green, so it stays green on a navy brand */
.g-up{font-size:9px; color:var(--green); margin-left:6px; font-family:var(--mono)}

/* — KPI tile — */
.g-kpi{width:172px}

/* — bar chart — bars breathe on a long, staggered loop so the chart looks like
   it is being fed live data rather than sitting still */
.g-chart{width:194px}
.g-bars{display:flex; align-items:flex-end; gap:5px; height:52px; margin-top:10px}
.g-bars i{
  flex:1; border-radius:3px 3px 0 0;
  background:linear-gradient(180deg,var(--brand2),rgba(var(--brand-rgb),.25));
  transform-origin:bottom; animation:grow 5.5s ease-in-out infinite;
}
.g-bars i:nth-child(1){height:38%; animation-delay:-.2s}
.g-bars i:nth-child(2){height:58%; animation-delay:-1.1s}
.g-bars i:nth-child(3){height:44%; animation-delay:-2.4s}
.g-bars i:nth-child(4){height:78%; animation-delay:-.7s}
.g-bars i:nth-child(5){height:62%; animation-delay:-3.1s}
.g-bars i:nth-child(6){height:92%; animation-delay:-1.8s}
.g-bars i:nth-child(7){height:70%; animation-delay:-4.0s}
@keyframes grow{0%,100%{transform:scaleY(.82)} 50%{transform:scaleY(1)}}

/* — pipeline — */
.g-pipe{width:200px}
.g-stage{display:flex; align-items:center; gap:8px; margin-top:9px; font-size:9px; color:var(--ink-dim)}
.g-stage span{flex:0 0 54px; letter-spacing:.06em}
.g-stage b{flex:0 0 16px; text-align:right; color:var(--ink); font-size:9px}
.g-track{flex:1; height:4px; border-radius:3px; background:rgba(var(--ink-rgb),.10); overflow:hidden}
.g-track i{display:block; height:100%; border-radius:3px; background:linear-gradient(90deg,var(--brand),var(--brand3))}

/* — lead card — */
.g-lead{width:210px}
.g-row{display:flex; align-items:center; gap:9px; margin-top:10px}
.g-avt{
  width:26px; height:26px; border-radius:50%; flex-shrink:0;
  display:grid; place-items:center; font-size:9px; font-weight:600;
  background:rgba(var(--brand-rgb),.12); border:1px solid rgba(var(--brand-rgb),.30); color:var(--brand3);
}
.g-nm{font-size:10px; color:var(--ink); font-family:'Instrument Sans',sans-serif; font-weight:600}
.g-sub{font-size:8.5px; color:var(--ink-faint); margin-top:1px}
.g-amt{margin-left:auto; font-size:10px; font-weight:600; color:var(--brand3)}

/* — sparkline — */
.g-spark{width:180px}
.g-spark svg{display:block; margin-top:8px; width:100%; height:44px; overflow:visible}
/* the line draws itself, over and over — the "live feed" tell */
.g-spark path.ln{
  fill:none; stroke:var(--brand2); stroke-width:2; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:260; stroke-dashoffset:260; animation:draw 9s ease-in-out infinite;
}
@keyframes draw{
  0%       {stroke-dashoffset:260}
  45%,72%  {stroke-dashoffset:0}
  100%     {stroke-dashoffset:0; opacity:0}
}

/* Ghosts are a DESKTOP flourish. On a phone the card already fills the screen —
   they would only be clutter behind it, and they cost battery to composite. */
@media (max-width:980px){ .ghosts{display:none} }

/* Vignette — pulls the eye to centre and hides where the grid mask ends. It also
   does the real work of dissolving the ghosts' outer edges into the dark. */
.vig{
  position:fixed; inset:0; z-index:1; pointer-events:none;
  background:radial-gradient(120% 100% at 50% 45%, transparent 40%, rgba(var(--bg-rgb),.85) 100%);
}

/* GRAIN — SVG film noise. The single biggest upgrade on any dark page: flat CSS
   gradients are PERFECTLY smooth, and perfect smoothness is what the eye reads as
   "cheap render". Real surfaces have noise. You won't see it; you'll feel it. */
.grain{
  position:fixed; inset:0; z-index:3; pointer-events:none; opacity:.16; mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}


/* ═══ SHELL ═════════════════════════════════════════════════════════════════ */
.shell{position:relative; z-index:2; width:100%; max-width:488px}
.shell.wide{max-width:560px}   /* signup — more fields need more room */

@keyframes rise{from{opacity:0; transform:translateY(14px)} to{opacity:1; transform:none}}
.r{opacity:0; animation:rise .7s cubic-bezier(.2,.7,.2,1) forwards}
.r1{animation-delay:.05s} .r2{animation-delay:.14s} .r3{animation-delay:.23s}


/* ═══ SPLIT ═════════════════════════════════════════════════════════════════
   Two columns: a brand panel carrying the customer's identity, and the form.

   The panel exists because this page is reached by clicking "Login" on the
   tenant's own website. Landing on a bare form is a discontinuity — the panel
   is the handshake that says you are still on their site. It is deliberately
   CONTENT-FREE beyond the logo and the name: any marketing copy here would be
   a second place to keep their positioning up to date, and it would go stale.

   On a phone it collapses to a short header strip. It never scrolls away with
   the form, and it never competes with it for attention.                      */
.split{
  position:relative; z-index:2;
  display:grid; grid-template-columns:minmax(0,42fr) minmax(0,58fr);
  width:100%; min-height:100dvh;
}
.brandside{
  position:relative; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  padding:48px 44px;
  background:var(--panel); color:var(--panel-ink);
}
/* A lattice, then a soft light over it — the same two-layer trick the canvas
   uses, so the panel reads as part of the same room rather than a colour block. */
.brandside::before{
  content:''; position:absolute; inset:0; pointer-events:none; opacity:.28;
  background-image:
    linear-gradient(rgba(255,255,255,.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.10) 1px, transparent 1px);
  background-size:56px 56px;
  mask-image:radial-gradient(72% 62% at 50% 42%, #000 0%, transparent 100%);
  -webkit-mask-image:radial-gradient(72% 62% at 50% 42%, #000 0%, transparent 100%);
}
.brandside::after{
  content:''; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(58% 46% at 50% 34%, rgba(255,255,255,.14) 0%, transparent 68%);
}
.brandside-in{position:relative; z-index:1; width:100%; max-width:340px}
/* the form column scrolls on its own, so a long card never drags the panel with it */
.formside{
  display:flex; align-items:center; justify-content:center;
  padding:48px 32px; min-width:0;
}

/* ═══ MARK ══════════════════════════════════════════════════════════════════ */
.mark{display:flex; flex-direction:column; align-items:center; gap:20px}
.mark-badge{
  position:relative; width:64px; height:64px; border-radius:19px;
  background:var(--badge);
  border:1px solid rgba(255,255,255,.16);
  display:grid; place-items:center;
  box-shadow:0 18px 44px -14px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.20);
}
/* ── A TENANT'S OWN LOGO ──────────────────────────────────────────────────
   Not the same object as the platform's mark, and styling it as one is what
   broke this page: an uploaded logo is a horizontal lockup (this one is 1.76:1)
   with DARK ink on transparency. Forced into the 44px square above, on a
   near-black plate, it came out both microscopic and invisible.

   So a real logo gets its own treatment — a WHITE plate, auto width, natural
   aspect ratio. White because an uploaded logo is almost always drawn for a
   light website header, so dark ink is the case that must not fail; on a dark
   panel the plate then reads as a deliberate brand card rather than a patch. */
.mark-badge.has-logo{
  width:auto; height:auto; border-radius:18px;
  background:#FFFFFF; border:1px solid rgba(255,255,255,.5);
  padding:16px 22px; max-width:100%;
  box-shadow:0 20px 50px -16px rgba(0,0,0,.45);
}
.mark-badge.has-logo .logo-img{
  display:block; width:auto; height:auto;
  max-width:220px; max-height:64px; object-fit:contain;
}
/* halo — the mark EMITS light rather than sitting on top of the panel */
.mark-badge::after{
  content:''; position:absolute; inset:-18px; border-radius:34px; z-index:-1;
  background:radial-gradient(circle, rgba(255,255,255,.20) 0%, transparent 68%);
}
.wordmark{
  font-family:'Syne',sans-serif; font-size:19px; font-weight:800;
  letter-spacing:.15em; text-transform:uppercase; text-align:center;
  line-height:1.45; color:var(--panel-ink);
}
/* a hairline under the name, so the lockup has a base instead of floating */
.mark::after{
  content:''; width:52px; height:2px; border-radius:2px;
  background:rgba(255,255,255,.28);
}


/* ═══ CARD ══════════════════════════════════════════════════════════════════
   This rule inverted when the page went light, and the reason is worth keeping.

   On the old DARK canvas, elevation could NOT come from a shadow — a black shadow
   on a black page is invisible — so it came from the SURFACE being lighter than
   the canvas (#241F1B against #070605: a wide gap, so the card visibly floated).

   On a LIGHT canvas that trick is gone: the card is already white and there is
   nothing lighter left to reach for. So elevation now comes from the one thing
   that could not work before — a real, layered SHADOW. It is tinted with the
   canvas's own green rather than grey, because a grey shadow on a tinted page is
   the classic tell of a theme that was recoloured instead of re-thought.        */
.card{
  position:relative; border-radius:24px; padding:44px 42px 34px;
  background:linear-gradient(180deg,#FFFFFF 0%, var(--card2) 100%);
  border:1px solid var(--line);
  box-shadow:
    0 1px 0 rgba(255,255,255,.9) inset,
    0 2px 6px rgba(var(--ink-rgb),.05),
    0 30px 70px -24px rgba(var(--ink-rgb),.28),
    0 0 70px -20px rgba(var(--brand-rgb),.14);
}
/* THE SIGNATURE: a lit top edge, brightest at centre. It tells the eye there is a
   light source above the card, which is what makes the card feel like an object
   in a room instead of a rectangle on a screen. This one hairline does more for
   "premium" than any gradient. */
.card::before{
  content:''; position:absolute; top:-1px; left:8%; right:8%; height:1px; z-index:2;
  background:linear-gradient(90deg,transparent,rgba(var(--brand-rgb),.85),transparent);
}
/* SPOTLIGHT BORDER — the card's 1px rim brightens wherever the cursor is near it,
   like a metal bevel catching light. Technique: paint a radial glow across the
   whole box, then XOR-mask out everything except the 1px padding ring, so the glow
   survives ONLY on the border. --cx/--cy come from auth-bg.js in the card's own
   coordinate space. Hidden until a real pointer exists (no cursor on phones). */
.card::after{
  content:''; position:absolute; inset:0; border-radius:inherit; padding:1px;
  background:radial-gradient(240px circle at var(--cx,50%) var(--cy,0%),
              rgba(var(--brand-rgb),.75) 0%, rgba(var(--brand-rgb),.25) 35%, transparent 65%);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude;
  opacity:0; transition:opacity .45s ease; pointer-events:none;
}
body.pointer .card::after{opacity:1}


/* ═══ CARD HEADINGS ═════════════════════════════════════════════════════════ */
.kicker{
  font-family:var(--mono); font-size:10px; font-weight:500;
  letter-spacing:.22em; text-transform:uppercase; color:var(--brand3);
  display:flex; align-items:center; gap:9px; margin-bottom:13px;
}
.kicker::after{content:''; flex:1; height:1px; background:linear-gradient(90deg,var(--line),transparent)}

.card h1{font-size:33px; margin-bottom:10px}
.lede{font-size:15px; color:var(--ink-dim); margin-bottom:32px; line-height:1.65}


/* ═══ FIELDS ════════════════════════════════════════════════════════════════ */
.field{margin-bottom:18px}
.field-row{display:grid; grid-template-columns:1fr 1fr; gap:14px}
.field label{
  display:block; font-family:var(--mono); font-size:10px; font-weight:500;
  letter-spacing:.16em; text-transform:uppercase; color:var(--ink-faint);
  margin-bottom:8px; transition:color .2s;
}
/* the label lights up when you focus its input — a tiny confirmation that the
   page is paying attention to you */
.field:focus-within label{color:var(--brand3)}

.field input, .field select{
  width:100%; padding:14px 15px; font-size:15px; font-family:inherit;
  color:var(--ink); background:var(--field-bg);
  border:1px solid var(--line); border-radius:12px;
  transition:border-color .18s, box-shadow .18s, background .18s;
}
.field select{cursor:pointer; appearance:none; -webkit-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%238A9A91' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 15px center; padding-right:38px}
.field select option{background:#FFFFFF; color:var(--ink)}
.field input::placeholder{color:var(--ink-faint); opacity:.78}
.field input:focus, .field select:focus{
  outline:none; background:#FFFFFF; border-color:rgba(var(--brand-rgb),.7);
  box-shadow:0 0 0 3px rgba(var(--brand-rgb),.13), 0 0 22px -8px rgba(var(--brand-rgb),.45);
}
.field .hint{font-family:var(--mono); font-size:10px; color:var(--ink-faint); margin-top:7px; letter-spacing:.06em}

.pwd{position:relative}
.pwd input{padding-right:64px}
.pwd-toggle{
  position:absolute; right:8px; top:50%; transform:translateY(-50%);
  background:none; border:none; padding:7px 9px; border-radius:7px;
  font-family:var(--mono); font-size:10px; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ink-faint);
  transition:.16s;
}
.pwd-toggle:hover{color:var(--brand3); background:rgba(var(--brand-rgb),.09)}


/* ═══ CHECKS / LINKS ════════════════════════════════════════════════════════ */
.row{display:flex; justify-content:space-between; align-items:center; margin:16px 0 22px; font-size:13px; gap:12px; flex-wrap:wrap}
.check{display:inline-flex; align-items:center; gap:9px; color:var(--ink-dim); cursor:pointer; user-select:none}
.check input{width:15px; height:15px; accent-color:var(--brand); cursor:pointer}
.link{color:var(--ink-dim); font-weight:500; transition:color .16s}
.link:hover{color:var(--brand3)}

.check-row{display:flex; align-items:flex-start; gap:10px; font-size:12.5px; color:var(--ink-faint);
  margin:6px 0 22px; line-height:1.6; cursor:pointer}
.check-row input{margin-top:3px; width:15px; height:15px; accent-color:var(--brand); cursor:pointer; flex-shrink:0}
.check-row a{color:var(--ink-dim); border-bottom:1px solid var(--line)}
.check-row a:hover{color:var(--brand3); border-color:var(--brand3)}


/* ═══ BUTTON ════════════════════════════════════════════════════════════════ */
.btn{
  position:relative; width:100%; padding:15px 22px; overflow:hidden;
  font-size:14.5px; font-weight:700; letter-spacing:.01em;
  color:var(--btn-ink); border:none; border-radius:12px;
  background:linear-gradient(180deg,var(--brand2) 0%, var(--brand) 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.28) inset, 0 10px 30px -10px rgba(var(--brand-rgb),.55);
  display:inline-flex; align-items:center; justify-content:center; gap:9px;
  transition:transform .16s, box-shadow .22s, filter .18s, opacity .18s;
}
.btn:hover:not(:disabled){
  transform:translateY(-1px); filter:brightness(1.06);
  box-shadow:0 1px 0 rgba(255,255,255,.34) inset, 0 18px 42px -12px rgba(var(--brand-rgb),.7);
}
.btn:active:not(:disabled){transform:translateY(0)}
.btn:disabled{opacity:.55; cursor:not-allowed; box-shadow:none}
/* light sweeps across on hover — the detail people notice but can't name */
.btn::after{
  content:''; position:absolute; inset:0; transform:translateX(-120%);
  background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.32) 50%,transparent 70%);
}
.btn:hover:not(:disabled)::after{transform:translateX(120%); transition:transform .85s cubic-bezier(.3,.6,.3,1)}
.arrow{transition:transform .22s; display:inline-block}
.btn:hover:not(:disabled) .arrow{transform:translateX(3px)}

.btn-note{
  text-align:center; margin-top:14px;
  font-family:var(--mono); font-size:10px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--ink-faint);
}


/* ═══ NOTES / MESSAGES ══════════════════════════════════════════════════════ */
.note{
  display:flex; gap:9px; align-items:flex-start;
  margin-top:16px; padding:11px 13px; border-radius:10px;
  background:rgba(var(--ink-rgb),.025); border:1px dashed var(--line);
  font-family:var(--mono); font-size:10.5px; line-height:1.6;
  color:var(--ink-faint); letter-spacing:.01em;
}
.note b{color:var(--ink-dim); font-weight:600}
.note-ic{color:var(--brand3); flex-shrink:0}

/* login + signup error banner (class name kept: the pages' JS toggles .show) */
.err{
  display:none; margin-bottom:18px; padding:12px 14px; border-radius:11px;
  background:rgba(220,38,38,.07); border:1px solid rgba(220,38,38,.28);
  color:#B91C1C; font-size:13px; line-height:1.55;
}
.err.show{display:block; animation:rise .3s ease forwards; opacity:1}
.err a{color:var(--brand3); font-weight:700; border-bottom:1px solid var(--brand3)}

/* reset-password message box (its JS sets .msg.err / .msg.ok) */
.msg{display:none; margin-top:18px; padding:12px 14px; border-radius:11px; font-size:13px; line-height:1.55}
.msg.err{display:block; background:rgba(220,38,38,.07); border:1px solid rgba(220,38,38,.28); color:#B91C1C}
/* success — semantic green, NOT the brand ramp (a green tick reads as "done" on
   any brand; a navy one reads as another button) */
.msg.ok {display:block; background:rgba(var(--green-rgb),.08);  border:1px solid rgba(var(--green-rgb),.28); color:var(--green-ink)}


/* ═══ ANDROID APP CTA ═══════════════════════════════════════════════════════
   This used to be "📱 Get the Android app" — an emoji plus an underlined link,
   sitting inside the footer directly under the Terms/Privacy line, in the same
   size and colour. Two things were killing it:

     1. It was PARKED IN THE BOILERPLATE. The eye files everything under a rule,
        next to "Terms and Privacy Policy", as legal small-print and skips it. No
        amount of styling saves an element that is standing in the wrong place —
        so it now lives in its own bordered row, above the legal line.
     2. The emoji was a LIABILITY, not an icon. 📱 renders differently on every OS
        (and as a blue tofu box on some), it can't take the brand colour, and it
        can't respond to hover. Replaced with a real Android SVG that inherits
        currentColor.

   It stays OUTLINED, not solid: the page already has one solid button, and that
   one is the primary action. Two solid buttons = neither wins.

   A tenant can hide both rows from Settings → Branding; that strips the markup
   server-side, so these rules simply match nothing on their domain.            */
.app-cta{
  display:flex; align-items:center; gap:13px;
  margin-top:22px; padding:13px 15px;
  border:1px solid var(--line); border-radius:14px;
  background:linear-gradient(180deg, rgba(var(--ink-rgb),.028), rgba(var(--ink-rgb),.008));
  transition:border-color .2s, background .2s, transform .18s, box-shadow .25s;
}
.app-cta:hover{
  border-color:rgba(var(--brand-rgb),.5); transform:translateY(-1px);
  background:linear-gradient(180deg, rgba(var(--brand-rgb),.09), rgba(var(--brand-rgb),.03));
  box-shadow:0 14px 34px -14px rgba(var(--brand-rgb),.45);
}
.app-ic{
  width:38px; height:38px; border-radius:11px; flex-shrink:0;
  display:grid; place-items:center; color:var(--brand3);
  background:rgba(var(--brand-rgb),.10); border:1px solid rgba(var(--brand-rgb),.24);
  transition:background .2s, color .2s, transform .25s;
}
.app-cta:hover .app-ic{background:rgba(var(--brand-rgb),.18); color:var(--brand3); transform:scale(1.06)}
.app-tx{min-width:0}
/* both are <span>s inside a <span> — they must be blocks to stack */
.app-t{display:block; font-size:13.5px; font-weight:600; color:var(--ink); line-height:1.35}
.app-s{
  display:block; font-family:var(--mono); font-size:9.5px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ink-faint); margin-top:3px;
}
/* The QR sits between the text and the arrow. It is hidden in the markup and only
   revealed once qr.js has actually painted a matrix, so a failed encode leaves the
   row looking exactly as it did before rather than showing an empty white square. */
.app-qr{
  margin-left:auto; flex-shrink:0; display:grid; place-items:center;
  padding:4px; border-radius:8px; background:#fff;
  border:1px solid var(--line);
  box-shadow:0 4px 12px -4px rgba(var(--ink-rgb),.22);
  transition:transform .25s, box-shadow .25s;
}
.app-qr canvas{display:block; width:60px; height:60px; image-rendering:pixelated}
.app-cta:hover .app-qr{transform:scale(1.06); box-shadow:0 8px 20px -6px rgba(var(--brand-rgb),.4)}

.app-go{
  margin-left:14px; flex-shrink:0; color:var(--ink-faint); font-size:15px;
  transition:transform .22s, color .2s;
}
/* no QR (encode failed / older browser): the arrow takes the space back */
.app-cta > .app-qr[hidden] ~ .app-go{margin-left:auto}
.app-cta:hover .app-go{transform:translateX(3px); color:var(--brand3)}


/* ═══ FOOT ══════════════════════════════════════════════════════════════════ */
.rule{height:1px; background:var(--line2); margin:26px 0 18px}

.foot{text-align:center; font-size:12.5px; color:var(--ink-faint); line-height:1.8}
.foot a{color:var(--ink-dim); border-bottom:1px solid var(--line); padding-bottom:1px; transition:.16s}
.foot a:hover{color:var(--brand3); border-color:var(--brand3)}
.foot a.plain{border:none}

.newhere{margin-top:22px; text-align:center; font-size:13px; color:var(--ink-faint)}
.newhere a{color:var(--brand3); font-weight:600}
.newhere a:hover{text-decoration:underline}

/* Sits over the brand panel now, so it takes the panel's ink, not the canvas's —
   --ink-faint on a deep navy is unreadable. */
.back{
  position:fixed; top:24px; left:26px; z-index:4;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.12em; text-transform:uppercase;
  color:rgba(255,255,255,.62); display:inline-flex; gap:8px; align-items:center;
  padding:9px 13px; border-radius:9px; border:1px solid transparent; transition:.18s;
}
.back:hover{color:var(--panel-ink); border-color:rgba(255,255,255,.24); background:rgba(255,255,255,.08)}


/* ═══ SIGNUP SUCCESS STATE ══════════════════════════════════════════════════
   signup.html's showSuccess() replaces #fp-body's innerHTML with this markup,
   so these class names are load-bearing — they are written from JS. */
.success-card{text-align:center; padding:8px 0; animation:rise .45s ease forwards}
.success-ic{
  width:72px; height:72px; border-radius:50%; margin:0 auto 20px;
  display:grid; place-items:center; font-size:32px; color:var(--btn-ink);
  background:linear-gradient(150deg,var(--brand2),var(--brand3));
  box-shadow:0 16px 44px -12px rgba(var(--brand-rgb),.5);
  animation:pop .4s cubic-bezier(.2,1.4,.5,1);
}
@keyframes pop{0%{transform:scale(.5);opacity:0} 100%{transform:scale(1);opacity:1}}
.success-h{font-family:'Syne',sans-serif; font-size:26px; margin-bottom:10px}
.success-p{font-size:14px; color:var(--ink-dim); line-height:1.7; max-width:380px; margin:0 auto 24px}
.success-p b{color:var(--ink)}
.success-actions{display:flex; gap:10px; justify-content:center; flex-wrap:wrap}
.btn-solid-sm{
  padding:12px 20px; font-size:13px; font-weight:700; color:var(--btn-ink); border:none; border-radius:10px;
  background:linear-gradient(180deg,var(--brand2),var(--brand));
  box-shadow:0 8px 24px -8px rgba(var(--brand-rgb),.5); transition:.18s;
}
.btn-solid-sm:hover{filter:brightness(1.08); transform:translateY(-1px)}
.btn-outline-sm{
  padding:12px 20px; font-size:13px; font-weight:700; color:var(--ink-dim);
  background:transparent; border:1px solid var(--line); border-radius:10px; transition:.18s;
}
.btn-outline-sm:hover{color:var(--ink); border-color:var(--brand3)}


/* ═══ RESPONSIVE ════════════════════════════════════════════════════════════
   Below 980px the panel stops being a column and becomes a short header strip.
   It keeps the logo — that is the whole reason it exists — but drops to a
   horizontal lockup so it costs a band, not a screenful, above the form. */
@media (max-width:980px){
  .split{grid-template-columns:1fr; min-height:0}
  .brandside{padding:52px 24px 26px; min-height:0}
  .brandside-in{max-width:none}
  .mark{flex-direction:row; justify-content:center; gap:15px}
  .mark::after{display:none}          /* the underline needs a stacked lockup */
  .mark-badge{width:52px; height:52px; border-radius:15px}
  .mark-badge.has-logo{padding:10px 14px; border-radius:13px}
  .mark-badge.has-logo .logo-img{max-width:158px; max-height:42px}
  .wordmark{font-size:15px; text-align:left; letter-spacing:.12em}
  .formside{padding:30px 20px 44px; align-items:flex-start}
}
@media (max-width:560px){
  .back{position:static; margin:0; padding:16px 18px 0; align-self:flex-start}
  .brandside{padding:20px 18px 22px}
  .formside{padding:22px 16px 40px}
  .card{padding:30px 22px 26px; border-radius:18px}
  .card h1{font-size:26px}
  .lede{font-size:14px; margin-bottom:26px}
  .field-row{grid-template-columns:1fr; gap:0}
  .beam{filter:blur(20px)}
  .mark{gap:12px}
  .mark-badge{width:46px; height:46px}
  .mark-badge.has-logo .logo-img{max-width:132px; max-height:36px}
  .wordmark{font-size:13.5px}
}

/* ═══ REDUCED MOTION ════════════════════════════════════════════════════════
   A still page for anyone who asks for one. The spotlight survives — it's a
   response to the user, not an animation playing at them. */
@media (prefers-reduced-motion:reduce){
  .aurora,.beam,.tr,.nd,.r,.success-ic,.g-bars i,.g-spark path.ln{animation:none !important}
  .r{opacity:1}
  .g-spark path.ln{stroke-dashoffset:0}
  .flow,.nodes{display:none}
  .plane,.ghost{transform:none !important}
}
