/* ============================================================================
   GENERATED FILE, DO NOT EDIT. Extracted from web/css/style.css between its
   SHARED-LOGIN sentinels by ops/gen-shared.js. Edit the source there, then:
     node ops/gen-shared.js
   ============================================================================ */

/* ==== SHARED-LOGIN-START ====================================================
   Everything between these sentinels is extracted verbatim into ops/css/login-shared.css by
   ops/gen-shared.js, because the ops console's sign-in screen is the same screen: same stage,
   same two columns, same frosted card. Copying it by hand would have drifted the first time
   this was touched.

   Edit here, then run `node ops/gen-shared.js`. Do not move the sentinels without checking
   what falls inside them; anything added here ships to both consoles.
   ========================================================================== */
.login-shell {
  --surface: rgba(255, 255, 255, 0.06);
  --surface-sunken: rgba(255, 255, 255, 0.10);
  --glass-surface-bg: rgba(255, 255, 255, 0.06);
  --glass-surface-border: rgba(255, 255, 255, 0.14);
  --border: rgba(255, 255, 255, 0.10);
  --border-strong: rgba(255, 255, 255, 0.20);
  --text: #f2f4fb;
  --text-secondary: rgba(242, 244, 251, 0.80);
  --text-muted: rgba(242, 244, 251, 0.58);
  --accent: #6cc6f5;
  --accent-grad: linear-gradient(135deg, #4cc3f7, #1a72c4);
  --accent-soft: rgba(76, 195, 247, 0.22);
  --st-expired: #ff8a80;

  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Block padding scales with viewport HEIGHT, inline with width (Clark, 2026-09-06: "the login
     page has internal scrolling when its not meant to be").

     It was one `clamp(1rem, 3.5vw, 3rem)` for all four sides, so the TOP and BOTTOM padding grew
     with how WIDE the window was. On a wide, short window -- every 1366x768 laptop -- 3.5vw
     resolved to 44.8px top and bottom while the viewport had no room to spare. Measured on the
     live page at 1280x575: card 578px + 89.6px of shell padding = 667px required against 575px
     available, so the page scrolled by 92px. Nothing was overflowing; there was simply more box
     than viewport, and the width-driven half of it was the part that had no business being
     there.

     Height-driven now, so a short window spends its padding budget on being short. The card's
     own padding below does the same. This does not promise zero scroll at every conceivable
     height -- the form itself has a floor of about 478px -- but it clears every realistic one. */
  padding-inline: clamp(1rem, 3.5vw, 3rem);
  padding-block: clamp(0.75rem, 3.5vh, 3rem);
  padding-bottom: max(clamp(0.75rem, 3.5vh, 3rem), env(safe-area-inset-bottom));
  position: relative;
  z-index: 1;
  overflow: hidden;
  color: var(--text);
}

/* The media slot. Empty by default — see LOGIN_BG in js/auth.js — and while it is empty this
   is a transparent pane, so the app's own wave canvas (js/lib/gradient-waves.js, 'login'
   colorway) reads straight through and the screen still has a moving background. The scrim is
   conditional for the same reason: dimming an empty stage would only dim the wave. */
.login-stage { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
/* One per slide, built lazily by login-hero.js. Two independent gates on opacity, and it needs
   both: .is-loaded means this clip actually has frames, .is-current means it's the slide being
   shown. Cross-fading between slides is the same property, so a slide change and a first load
   never fight each other. */
.login-stage-media {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0; transition: opacity 700ms ease;
}
.login-stage-media.is-loaded.is-current { opacity: 1; }
/* Two scrims, not one: a left-weighted wash so the headline holds over any footage, plus a
   floor gradient so the card's bottom edge never lands on a bright frame. Generated footage
   changes luminance shot to shot — the copy cannot depend on the frame behind it being dark. */
.login-stage.has-media::after {
  content: '';
  position: absolute; inset: 0;
  background:
    linear-gradient(100deg, rgba(5, 9, 22, 0.86) 0%, rgba(5, 9, 22, 0.62) 42%, rgba(5, 9, 22, 0.38) 100%),
    linear-gradient(to top, rgba(5, 9, 22, 0.65), rgba(5, 9, 22, 0) 45%);
}

.login-layout {
  position: relative;
  z-index: 1;
  /* Narrower than the old 1180px, and the copy column carries its own inset (Clark, 2026-09-04:
     "make the logo and the content close to the middle") — the two blocks used to sit pinned to
     opposite edges of a wide screen with a hole between them. */
  width: min(1080px, 100%);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(340px, 400px);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

/* Bottom-anchored, not centred (Clark, 2026-09-04: "move text and logo lower close to the
   progress bars on the bottom") — the logo/headline/sub group sits down at the foot of the
   panel with the bar under it, while the card opposite stays vertically centred. */
/* Bottom-anchored but lifted clear of the card's foot (Clark, 2026-09-04: "move it a bit
   higher") — align-self:end alone parked it flush with the bottom of the row. */
.login-copy {
  min-width: 0;
  padding-left: clamp(0rem, 3vw, 3rem);
  align-self: end;
  padding-bottom: clamp(1.5rem, 4.5vh, 3.5rem);
}
.login-card { align-self: center; }
/* align-self is load-bearing: .login-slide is a flex column, so without it the default
   `align-items: stretch` widens the <img> to the column and `width: auto` can no longer hold
   the mark's aspect ratio — it renders smeared across the panel. */
.login-copy-mark {
  display: block; align-self: flex-start;
  height: clamp(46px, 4.8vw, 62px); width: auto;
  margin: 0 0 clamp(0.9rem, 2vw, 1.4rem);
}
.login-copy-title {
  font-family: var(--font-display);
  font-weight: 800;
  /* Scaled back down and held to a narrower measure (Clark, 2026-09-04: "the description title
     is too big and wide"). max-width is what stops it running the full width of the column. */
  font-size: clamp(1.65rem, 2.9vw, 2.5rem);
  line-height: 1.14;
  letter-spacing: -0.02em;
  margin: 0 0 0.9rem;
  max-width: 20ch;
  color: #fff;
  text-wrap: balance;
}
.login-copy-sub {
  font-size: clamp(0.98rem, 1.15vw, 1.1rem);
  line-height: 1.72;
  color: var(--text-secondary);
  max-width: 44ch;
  margin: 0;
}

/* The slide box reserves the tallest slide's height so the logo above it and the progress bar
   below it don't hop on every change — a deliberate exception to the house "no min-height" rule,
   for the same reason .section-screen is one. The value is NOT set here: login-hero.js measures
   all three slides and writes it inline, because the title is clamp()-scaled by viewport width,
   so any number hardcoded in CSS is wrong at some width by construction. */
/* Text sits at the BOTTOM of that reserved box, so the sub always hugs the progress bar no
   matter how short the slide is. Reserving the height fixes something either way; this chooses
   which. A varying gap between the logo and the headline reads as breathing room — a varying
   gap between the copy and the bar that belongs to it reads as a bug. */
.login-slide { display: flex; flex-direction: column; justify-content: flex-end; }
.login-slide.is-entering .login-copy-title,
.login-slide.is-entering .login-copy-sub {
  animation: loginSlideIn 520ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
.login-slide.is-entering .login-copy-sub { animation-delay: 70ms; }
@keyframes loginSlideIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* One segment per slide: which clip is playing, and how far through it you are. */
/* Flush left, aligned with the copy above it (Clark, 2026-09-04 reverted the centred version). */
.login-progress {
  display: flex; gap: 0.45rem;
  margin-top: clamp(1.2rem, 2.4vw, 1.9rem);
}
/* Short and thick (Clark, 2026-09-04: "make the lines less wide and thicker"), with the active
   one POPPING OUT rather than just being brighter — it thickens, lifts and picks up a glow, so
   which slide you're on is legible from across the room and without relying on colour alone.
   One --line-h token drives the track and the fill together; setting it in two places is how
   the fill ends up half a pixel off the track it sits in. */
.login-progress-seg {
  --line-h: 5px;
  position: relative;
  flex: 0 0 clamp(20px, 2.6vw, 32px);
  /* Vertical padding IS the hit area — the line itself is a few px and unclickable on its own. */
  padding: 0.75rem 0;
  background: none; border: 0; cursor: var(--cur-hand); font: inherit;
  -webkit-appearance: none; appearance: none;
}
.login-progress-seg::before {
  content: ''; display: block;
  height: var(--line-h); border-radius: 999px;
  background: rgba(255, 255, 255, 0.20);
  transition: background-color 0.15s ease;
}
.login-progress-seg:hover::before { background: rgba(255, 255, 255, 0.34); }
.login-progress-seg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
/* The active segment is marked by the white fill and its glow ONLY — every line keeps the same
   thickness (Clark, 2026-09-04: "i dont like it getting thick like that just have the higlight
   and glow"). Nothing here may change --line-h. */
.login-progress-seg[aria-current="true"] .login-progress-fill {
  box-shadow: 0 0 12px rgba(255, 255, 255, 0.55), 0 1px 3px rgba(3, 7, 20, 0.45);
}
/* Swept by WIDTH, not transform: scaleX (Clark, 2026-09-04: "we loose the rounded edges of
   progress bar when it highlighted"). border-radius is resolved BEFORE the transform, so on a
   5px-tall bar the 999px radius clamps to 2.5px and scaleX(0.3) squashes that to 0.75px
   horizontally — the ends render square for most of the sweep and only round out at the end.
   Animating width leaves the radius alone at every frame. Layout cost is irrelevant on a 32px
   element. login-hero.js sets the transition inline per slide. */
.login-progress-fill {
  position: absolute; left: 0; top: 50%;
  width: 0;
  height: var(--line-h); margin-top: calc(var(--line-h) / -2);
  border-radius: 999px;
  background: #fff;
}
@media (pointer: coarse) { .login-progress-seg { padding: 1.3rem 0; } }
@media (prefers-reduced-motion: reduce) {
  .login-slide.is-entering .login-copy-title,
  .login-slide.is-entering .login-copy-sub { animation: none; }
  .login-stage-media { transition: none; }
  .login-back .icon { animation: none; }
}

/* Back to the marketing site. Translucent rather than a solid button on purpose: it sits over
   moving footage, and anything opaque up there reads as chrome bolted onto the video instead of
   as part of the same glass family as .login-card below. Same blur and the same white-alpha fill
   the shell's own --glass-surface-bg uses, just lighter, because this is the least important
   thing on the screen and must never compete with Sign in.

   Positioned to the shell's own padding so it lines up with the content rather than floating at
   an arbitrary inset, and given the left safe-area on top of that for a notched phone held
   sideways.

   SMALL AND OUT OF THE WAY (Clark, 2026-09-05: "too big just make it small and onubstructive",
   then "dont make it thin and long just scale it smaller", then "just put CEDA.ph instead of
   back"). The first attempt only took the HEIGHT down, which is what turned it into a lozenge:
   the width was never set by the padding, it was set by the words. "Back to ceda.ph" was 85px of
   text on its own, so at 26px tall the thing could only get longer relative to itself.

   The label is the destination alone -- "CEDA.ph", with the arrow carrying the "back" -- which is
   what keeps it short enough to scale as one object. The action stays in aria-label for anyone
   who cannot see the arrow. No title attribute: the visible text already names where it goes, and
   a tooltip repeating it is just a delay before nothing.

   Dimmed as well as small: it is the least important thing on this screen and should read as a
   quiet way out rather than an option being offered. Hover is what brings it to full strength.

   That is under the 44px tap minimum, deliberately and by the precedent already set for the
   sidebar rail: the size rule wins on a FINE pointer, and the full 44px target comes back under
   `pointer: coarse` below, where it is a finger rather than a cursor doing the aiming. */
.login-back {
  position: absolute;
  top: clamp(1rem, 3.5vw, 3rem);
  left: max(clamp(1rem, 3.5vw, 3rem), env(safe-area-inset-left));
  z-index: 2;
  display: inline-flex; align-items: center; gap: 0.3rem;
  min-height: 30px; padding: 0 0.7rem 0 0.55rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.11);
  backdrop-filter: blur(14px) saturate(1.15);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  color: var(--text-muted); text-decoration: none;
  font-size: 0.75rem; font-weight: 500; line-height: 1;
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.login-back[hidden] { display: none; }
.login-back .icon { width: 14px; height: 14px; transition: transform 0.18s ease; }
.login-back:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.26);
  color: var(--text);
}
/* The arrow moves, not the pill: a pill that slides left under the cursor pulls itself out from
   under the pointer, which reads as the target running away.
   It now moves on its own too (Clark, 2026-09-06: "for it yes make it animted too"), the same
   continuous glide the 404 board's back button uses, at a smaller amplitude to suit a 14px
   icon. The hover nudge is gone rather than kept: an animation wins the cascade on transform,
   so a :hover rule setting the same property would simply never take effect, and leaving it
   there would read as working code that does nothing. */
.login-back .icon { animation: beckonBack 1.6s ease-in-out infinite; --beckon: -3px; }
.login-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* The tap target, restored where a finger is doing the aiming rather than a cursor. One word is
   short enough to keep at every width now, so there is no icon-only phone case any more. */
@media (pointer: coarse) {
  .login-back { min-height: 44px; padding: 0 0.95rem 0 0.8rem; font-size: 0.8rem; }
  .login-back .icon { width: 15px; height: 15px; }
}

/* SHORT VIEWPORTS. Measured on the live page at 1280x631, a perfectly ordinary laptop window:
   a 600px card plus 44px of shell padding needs 644px against 631px available, so it scrolled by
   13px (Clark, 2026-09-06: "why is there still internal scrolling on that page when i said not
   to"). Making the block padding height-driven in v0.31.0 cut the overflow from 92px to 13px but
   did not close it, because vh-based padding grows again as the viewport does.

   A height breakpoint rather than more clamp maths, because that is what this actually is: below
   ~720px the screen has no spare height and the padding is the only thing that can give. Above
   it, nothing changes and the design keeps its air.

   There is a floor here and it is honest: the card's own content is about 560px of form, social
   buttons, helper lines and legal links, so a viewport under ~600px will still scroll. That is
   the right behaviour at that size -- the alternative is hiding controls. */
@media (max-height: 720px) {
  .login-shell {
    padding-block: clamp(0.5rem, 2vh, 1.4rem);
    padding-bottom: max(clamp(0.5rem, 2vh, 1.4rem), env(safe-area-inset-bottom));
  }
  .login-card { padding-block: clamp(0.75rem, 2vh, 1.3rem); }
  /* The copy column is bottom-aligned and its own padding is the last thing anyone looks at
     when the screen is this short. */
  .login-copy { padding-bottom: clamp(0.5rem, 2vh, 1.5rem); }
}

.login-card {
  position: relative;
  width: min(400px, 100%);
  justify-self: end;
  /* Not .glass — that resolves --glass-surface-bg, which the scope above already re-points at
     a flat translucent white. This needs a specific NAVY tint so the card reads as its own
     object over footage of any colour, which a white-alpha fill can't do. */
  background: rgba(9, 15, 35, 0.58);
  border: 1px solid rgba(255, 255, 255, 0.13);
  border-radius: 18px;
  /* Same reason as .login-shell's padding-block above: the vertical half was width-driven and
     wide-but-short windows paid for it twice, once here and once on the shell. */
  padding-inline: clamp(1.5rem, 2.4vw, 2.1rem);
  padding-block: clamp(1rem, 3vh, 2.1rem);
  backdrop-filter: blur(24px) saturate(1.15);
  -webkit-backdrop-filter: blur(24px) saturate(1.15);
  box-shadow: 0 30px 70px rgba(3, 7, 20, 0.50), 0 2px 8px rgba(3, 7, 20, 0.30);
}


/* Moved INSIDE the sentinels 2026-09-07. These three sat just outside the end
   marker below, so ops/css/login-shared.css never carried them and the ops console rendered
   .login-card-title, .login-card-sub and both .login-link-btn buttons ("Send a new code",
   "Sign in as someone else") as raw browser chrome on its own sign-in and two-factor cards.
   The rules were correct all along; only the copy boundary was wrong. Anything the ops login
   markup uses has to live above this line. */
.login-card-title { font-family: var(--font-display); font-weight: 700; font-size: 1.3rem; letter-spacing: -0.01em; margin: 0 0 0.25rem; }
.login-card-sub { color: var(--text-muted); font-size: 0.84rem; margin: 0 0 1.35rem; }

.login-link-btn {
  background: none; border: none; padding: 0;
  font: inherit; font-weight: 600;
  color: var(--accent); cursor: var(--cur-hand);
  text-decoration: underline; text-underline-offset: 2px;
}
.login-link-btn:hover { color: #fff; }

@media (pointer: coarse) { .login-link-btn { display: inline-block; padding: 0.62rem 0.3rem; } }

@media (max-width: 900px) {
  .login-layout { grid-template-columns: minmax(0, 1fr); justify-items: center; }
  .login-copy { display: none; }
}
