/* Portal login page (public-spec section 4.1: "same visual pattern as
   Admin login, plus a language dropdown placed under the title"). Ported
   from asportal-admin's own static/css/login/login.css -- same card
   proportions/spacing (this app has no competing design export of its
   own for this screen yet, and the spec explicitly says to match admin's
   pattern), as its own independent copy per this app's established
   convention (nothing shared at import time between the two apps' static
   trees).

   Background image + animation landed 2026-07-24 (design-handoffs/
   asportal-public/login/). Deliberately simple after a long chase of exact
   pixel-continuity behind Safari's own status bar/toolbar chrome
   (`<html>`-level background, dual-layer crop syncing, edge masks, an
   inverted zoom direction, a hand-tuned crop position -- all tried, all
   real, all reverted): none of it held up to the user's own final call
   -- match the design guide's own plain crop, and let the safe-area/
   chrome strips be a solid color-match instead of a literal continuation
   of the image. `--color-login-bg` (below, tokens.css) is that color,
   sampled directly as this photo's single most common pixel value (62%
   of the image), not eyeballed. */
/* Same color on `html` itself, not just `body` below -- real user report:
   the status bar sometimes shows gray instead of navy, inconsistently,
   on a refresh. `html` had no background-color of its own at all before
   this, only `body.login-page` did; the browser's own default (not this
   photo's navy) is whatever briefly shows if the root element's own box
   is what gets sampled/painted for even one frame before body's own
   background is applied -- exactly the kind of transient-paint
   inconsistency this project has already hit more than once with
   Safari's own status-bar/toolbar tinting specifically (see DEV_LOG
   2026-07-24's `.sidebar` `<dialog>`-conversion entry for the other
   real instance of it). Cheap, low-risk fix: give `html` the identical
   color too, so there's no unstyled moment for anything else to show
   through regardless of which element Safari happens to sample. */
html {
  background-color: var(--color-login-bg);
  /* Deliberately not overriding `--public-nav-drawer-bg` to match this --
     see public-top.css's own `.public-nav-drawer` rule for why (accepted
     status-bar seam vs. an off-spec drawer color). */
}

body.login-page {
  font-family: var(--font-family);
  /* Solid color-match for the status bar/Safari-toolbar strips -- not
     an attempt at literal image continuity there anymore, see this
     file's own history above for why. */
  background-color: var(--color-login-bg);
}

.login-bg {
  position: relative;
  width: 100%;
  /* 100dvh, not the plain 100vh this rule used to match asportal-admin's own
     login.css with -- real user report: plain 100vh (the *static*,
     chrome-collapsed-sized value) is taller than the *currently visible*
     dynamic viewport whenever Safari's own chrome is showing, which
     made this box, and the whole page along with it, genuinely taller
     than one screen -- an unwanted vertical scrollbar with nothing real
     to scroll to, just more of this same fixed-position page. Explicit
     user call: no scroll here ever matters more than fully extending
     the background behind Safari's chrome (the thing 100vh was for in
     the first place) -- 100dvh tracks the *current* visible size
     exactly, so this box is never taller than what's actually on
     screen. */
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  /* Reserves the pre-login top bar's own height (public-top.css) so the
     centered card can never render *underneath* it -- .public-top is
     `position: fixed`, invisible to this element's own flex-centering
     math otherwise, which on a short (landscape phone) viewport could
     otherwise center the card right through the bar. If the card plus
     this padding is taller than the current viewport, this box (min-
     height, not height) simply grows to fit it and the *page* scrolls
     to reach the rest -- explicit user call for the landscape case:
     never overlap the bar, scroll instead. */
  padding-top: calc(60px + env(safe-area-inset-top));
  /* env(safe-area-inset-bottom), not a flat 16px -- real user report: the
     status bar (top) and Safari's own bottom toolbar didn't match colors
     on iPhone. Root cause was here, not the top/theme-color side (already
     correct): with only a flat 16px reserved below the card, the card's
     own light `--color-surface` background could sit inside -- or right
     up against -- the home-indicator safe area on notch/Face-ID phones,
     so that's the color Safari sampled for its bottom chrome tint instead
     of the dark `--color-login-bg` the top status bar already matches.
     Matches padding-top's own safe-area handling above -- that side was
     never the problem since it already accounts for env(safe-area-inset-top). */
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  /* Clips .login-bg-image's own 120% end state (see that rule's own
     comment) to this box's own bounds -- without this the zoomed image
     would overflow past it. Growing this box (above) to fit a too-tall
     card, rather than growing the *viewport*, is what keeps this from
     ever needing to clip the card itself, only the background image's
     own animation overshoot. */
  overflow: hidden;
}

@media (max-width: 767px) {
  .login-bg {
    /* Matches public-top.css's own Mobile bar height (56px, not 60px). */
    padding-top: calc(56px + env(safe-area-inset-top));
  }
}

/* Real design export landed 2026-07-24 (design-handoffs/asportal-public/login/)
   -- background photo + a one-shot entrance animation, both explicit user
   instructions: object-fit:cover (maintain ratio, centered, fill), 10s
   ease-out scale from 100% to 120%. position:absolute (not the Figma
   export's own literal oversized-image + rotation-matrix hack, which was
   just that tool's own way of achieving the same crop) is what makes
   object-fit:cover apply correctly here; needs .login-card to also be
   `position: relative` (below) so it still paints above this despite
   coming first in visual stacking order otherwise -- positioned elements
   always paint above non-positioned in-flow siblings regardless of DOM
   order, so without that, this absolutely-positioned image would cover
   the card entirely rather than sit behind it. srcset (not a single src)
   picks the right density variant automatically -- 1x/2x/3x all provided
   in the export, this asset is a real photo, not an icon, so density
   matters for file size.

   Genuinely position:absolute against .login-bg, not position:fixed
   against the viewport -- two real wrong turns chasing the same real
   "doesn't paint behind the status bar/address bar" bug, both reverted:
   `position:fixed` + plain 100%, then `position:fixed` + 100lvw/100lvh,
   neither fixed it on a real device despite passing every Simulator
   check. The image deliberately follows `.login-bg`'s current dynamic
   viewport sizing (`min-height: 100dvh`): this keeps the animated crop
   aligned with the visible viewport while the browser chrome is shown.
   Matches the design guide's own plain crop -- object-fit:cover,
   object-position:center, no hand-tuned offset -- after several rounds
   of trying to fix a perceived off-center crop and a status-bar/toolbar
   seam ended up reverted; see this file's own top-of-file note for why
   both were abandoned in favor of this simpler version. */
.login-bg-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  animation: login-bg-zoom 10s ease-out forwards;
}

@keyframes login-bg-zoom {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.2);
  }
}

.login-logo {
  width: 127px;
  height: 30px;
}

.login-card {
  /* Positioned (not the default static) purely for stacking -- see
     .login-bg-image's own comment for why this has to paint above it. */
  position: relative;
  width: 400px;
  padding: 48px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
  background: var(--color-surface);
  /* 0 4px 32px @ 0.3 opacity, not 16px @ 0.25 -- the real export's own
     values (design-handoffs/asportal-public/login/), confirmed 2026-07-24;
     previously guessed from a plain screenshot, not a CSS export. */
  box-shadow: 0px 4px 32px rgba(0, 0, 0, 0.3);
  border-radius: var(--radius-lg);
  box-sizing: border-box;
}

.login-title {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.login-title h1 {
  margin: 0;
  font-size: 24px;
  font-weight: 700;
  line-height: 140%;
  letter-spacing: var(--letter-spacing-tight);
  color: var(--color-text-primary);
}

/* public-spec 4.1's own addition over admin's login -- EN/KO switcher,
   placed under the title. Hidden as of the real design export landing
   2026-07-24 (design-handoffs/asportal-public/login/) -- that export's own
   "Title" node is just logo+heading, no slot for a locale control
   anywhere on PC/Tablet/Mobile. Explicit user decision: hide, don't
   delete -- the translation infrastructure this depends on (locales/
   {en,ko}.json, auth.set_locale_route(), current_locale()) stays fully
   wired up untouched, so this is a one-line `display: none` removal
   away from being visible again whenever a real design slot exists for
   it, not a rebuild. */
.login-locale {
  display: none;
  margin: 0;
}

.login-locale-select {
  box-sizing: border-box;
  height: 32px;
  /* Right padding clears the custom chevron icon below -- matches
     .select's own 12px right inset (base/forms.css), not the plain 10px
     this row's other side keeps. */
  padding: 0 28px 0 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  /* Custom chevron, not the browser's own native select arrow -- this
     never had .select's own `appearance:none` + background-image
     treatment (base/forms.css), so it was showing a visibly different,
     OS-default up/down spinner icon instead of the app's real dropdown
     design everywhere else uses (caught from a real screenshot
     comparison, side by side with the 전체/사용 중 filter select). */
  background:
    var(--color-surface)
    url('../../images/base/icons/ic-down-stroke-12-md.svg') no-repeat right 12px center;
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font-family);
  font-size: 13px;
  color: var(--color-text-faint);
  cursor: pointer;
}

/* Same mechanism as .login-input:focus/.field:focus/.select:focus --
   admin's own <select> dropdowns use this exact pattern too (checked
   directly, no separate bold/black-outline treatment exists anywhere
   in either app): outline:none removes the browser's default blue
   ring, border-color does the actual highlighting, subtly, not a heavy
   2px outline. */
.login-locale-select:focus {
  outline: none;
  border-color: var(--color-text-muted);
}

.login-fields {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  width: 320px;
}

.login-input-group {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
}

.login-input {
  box-sizing: border-box;
  width: 100%;
  height: 48px;
  padding: 0 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-family: var(--font-family);
  /* 16px, not 15px -- iOS Safari auto-zooms the whole page in when a
     focused input's own font-size is under 16px (its heuristic for
     "this text might be too small to read"), regardless of whether the
     page already reads fine at the current zoom level. Real reported
     bug on both this input and .search-box-input/.field (same fix
     applied there too) -- confirmed the 16px floor is what Safari
     actually checks, not a cosmetic preference. */
  font-size: 16px;
  font-weight: 400;
  letter-spacing: var(--letter-spacing-tight);
  color: var(--color-text-primary);
}

.login-input::placeholder {
  color: var(--color-text-placeholder);
}

/* Matches base/forms.css's own .field:focus/.field.field-error pattern
   (admin's established input-focus/error design language) -- .login-input
   never had it, just the browser's own default blue focus ring and no
   error-state border at all (error only ever showed as text below).
   outline:none + a subtle border-color darken instead of the default
   ring; border-color goes to the accent-error red on a field the server
   flagged, not just the text underneath it (login.html adds
   .login-input-error when errors_for() found something for that field). */
.login-input:focus {
  outline: none;
  border-color: var(--color-text-muted);
}

.login-input.login-input-error {
  border-color: var(--color-accent-error);
}

.login-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}

.login-remember label {
  font-size: 15px;
  font-weight: 400;
  letter-spacing: var(--letter-spacing-tight);
  color: var(--color-text-faint);
}

.login-submit {
  width: 100%;
  height: 48px;
  padding: 16px;
  background: var(--color-button-primary-bg);
  color: var(--color-text-inverse);
  border: none;
  border-radius: var(--radius-md);
  font-family: var(--font-family);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: var(--letter-spacing-tight);
  cursor: pointer;
}

/* Same Hover/Pressed pattern as .btn-secondary's own identical
   background (base/forms.css) -- .login-submit isn't built on top of
   that shared class, but its color IS the same --color-button-primary-bg
   token, so it gets the same --color-button-secondary-bg fill on
   hover/press rather than inventing a separate treatment. */
.login-submit:active {
  background: var(--color-button-secondary-bg);
}

@media (hover: hover) {
  .login-submit:hover {
    background: var(--color-button-secondary-bg);
  }
}

.login-error {
  display: block;
  margin-top: 8px;
  width: 100%;
  font-size: 14px;
  color: var(--color-accent-error);
}

/* Forced first-login reset uses the same card hierarchy as the admin reset
   screen while retaining the Portal login photograph behind it. */
.login-reset-id {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  font-size: 15px;
}

.login-reset-id span:first-child {
  color: var(--color-text-faint);
}

.login-reset-id span[aria-hidden] {
  color: var(--color-border);
}

.login-reset-id strong {
  color: var(--color-text-primary);
  font-weight: 700;
}

.login-reset-description,
.login-reset-hint {
  width: 100%;
  margin: 0;
  color: var(--color-text-muted);
}

.login-reset-description {
  text-align: center;
}

/* Mobile keeps 16px side gutters while the card grows fluidly up to the
   400px component maximum. The 32px internal gap is unchanged. */
@media (max-width: 767px) {
  .login-card {
    width: min(400px, calc(100vw - 32px));
    padding: 40px 24px;
    gap: 32px;
  }

  .login-fields {
    width: 100%;
  }

  /* Homepage/Mobile/H2 (22px), not PC/Tablet's own H3 (24px) -- a real
     size step confirmed in the export, not a font-size the two other
     tiers happen to share. */
  .login-title h1 {
    font-size: 22px;
  }
}
