/* 로그인 화면 — 승인된 디자인 원본의 이식.
 *
 * 원본: reference/approved-login-baseline/static-login/styles.css (494줄, 무의존 정적본).
 * 이 화면은 "가능하면 개선"의 대상이 아니다. 값·타이밍·미디어 쿼리를 그대로 옮긴다.
 * 특히 다음 둘은 이 제품에서 유일하게 4K를 실제로 겨냥해 만든 부분이라 그대로 살아 있어야 한다:
 *   - @media (min-width: 2200px) and (min-height: 1200px) 블록 전체
 *   - clamp() 23개 (이 파일 기준: 본문 6 + 4K 블록 16 + 짧은 데스크톱 1 = 원본과 동수)
 *
 * 원본에서 이 파일로 오지 않은 것은 auth.css로 갔다(팔레트/모션 토큰, 리셋, .is-paused,
 * @keyframes). 페이지에 종속되지 않아 비밀번호 변경 화면과 공유하기 위해서다.
 * 색·형태·타이밍은 한 값도 바뀌지 않았다 — 어느 파일에 적혀 있는지만 다르다.
 *
 * 이 파일 끝에 "저장소 접합부"가 있다. 원본 정적본에는 없던 것들이다:
 *   #login-error(서버 렌더 + login.js가 채우는 실패 배너), #server-status(/readyz 프로브),
 *   세션 만료 안내, 인라인 SVG 워드마크의 면 색, aria-invalid ↔ .invalid 시각 신호.
 *   각각 그 자리에 이유를 적었다.
 */

:root {
  /* 로그인은 인증 전 화면이라 라이트 고정이다(히어로가 그 전제로 설계돼 있다).
     tests/regression/test_theme_on_all_authed_pages.py가 이 화면에 theme.js가 없어야 한다고
     못 박는다 — 그래서 색 구성표도 여기서 라이트로 선언한다. auth.css에 두면 비밀번호 변경
     화면(다크 지원)까지 라이트로 묶여 폼 컨트롤·스크롤바가 어긋난다. */
  color-scheme: light;
}

body {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 24px;
  color: var(--text);
  font-family: var(--font);
  background:
    radial-gradient(circle at 10% 0%, rgba(142,117,225,.17), transparent 32%),
    radial-gradient(circle at 100% 100%, rgba(83,108,214,.18), transparent 35%),
    #F3F6FF;
}

.login-shell {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(420px, .75fr);
  width: min(100%, clamp(1500px, 82vw, 1920px));
  height: min(calc(100svh - 48px), clamp(840px, 92svh, 1200px));
  min-height: 680px;
  max-width: 1920px;
  margin: 0 auto;
  overflow: hidden;
  border: 1px solid rgba(97,117,190,.16);
  border-radius: 28px;
  background: var(--surface);
  box-shadow: 0 28px 80px rgba(43,62,133,.16);
  animation: shell-enter 520ms var(--ease-out) both;
}

.hero {
  position: relative;
  display: flex;
  min-height: 0;
  flex-direction: column;
  overflow: hidden;
  padding: 48px;
  color: #fff;
  background:
    radial-gradient(circle at 82% 24%, rgba(142,117,225,.58), transparent 32%),
    radial-gradient(circle at 18% 78%, rgba(98,199,189,.24), transparent 28%),
    linear-gradient(145deg, #17204D 0%, #293B8D 48%, #536CD6 100%);
}
.hero::before {
  position: absolute;
  inset: 0;
  content: "";
  opacity: .13;
  background-image:
    linear-gradient(rgba(255,255,255,.45) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.45) 1px, transparent 1px);
  background-size: 72px 72px;
  -webkit-mask-image: radial-gradient(circle at center, #000 15%, transparent 76%);
  mask-image: radial-gradient(circle at center, #000 15%, transparent 76%);
  pointer-events: none;
}
.ambient-orb {
  position: absolute;
  z-index: 0;
  width: 260px;
  aspect-ratio: 1;
  border-radius: 50%;
  opacity: .28;
  filter: blur(48px);
  pointer-events: none;
}
.ambient-orb-one { top: 12%; right: 5%; background: var(--purple); }
.ambient-orb-two { bottom: 2%; left: -8%; width: 320px; background: var(--mint); }
.hero-logo {
  position: relative;
  z-index: 3;
  /* 폭을 42% 로 두면 900~1100px 구간에서 로고가 146px 까지 줄고, 2줄 락업의 태그라인이
     3.8px 가 되어 글자가 아니라 얼룩으로 보인다(실측). 기준(.auth-logo)처럼 **높이를 고정**한다.
     폭을 auto 로 두면 안 된다 — SVG 는 그 경우 종횡비가 아니라 **컨테이너 폭을 채워서**
     로고가 887px 로 늘어났다(1920 실측). 비율(760/156 = 4.872)을 직접 곱해 준다. */
  --wm-h: clamp(46px, 5.4vw, 58px);
  height: var(--wm-h);
  width: calc(var(--wm-h) * 4.872);
  max-width: 100%;
  animation: content-enter 480ms 80ms var(--ease-out) both;
}
.hero-copy {
  position: relative;
  z-index: 3;
  max-width: 700px;
  margin-top: clamp(34px, 5.5vh, 58px);
  animation: content-enter 540ms 140ms var(--ease-out) both;
}
.hero-eyebrow { margin: 0 0 16px; color: #E3E7FF; font-size: 13px; font-weight: 800; letter-spacing: .12em; }
.hero h1 { margin: 0; color: #fff; max-width: 720px; font-size: clamp(38px, 3.7vw, 56px); font-weight: 820; line-height: 1.13; letter-spacing: -.055em; }
.hero h1 strong { color: #D8D0FF; font-weight: inherit; }
.hero-description { max-width: 610px; margin: 20px 0 0; color: rgba(255,255,255,.82); font-size: 15.5px; line-height: 1.7; }
.feature-list { display: grid; gap: 10px; margin: 20px 0 0; padding: 0; list-style: none; }
.feature-list li { display: flex; align-items: center; gap: 10px; color: rgba(255,255,255,.9); font-size: 14px; font-weight: 650; }
.feature-list span { display: grid; width: 32px; height: 32px; place-items: center; border: 1px solid rgba(255,255,255,.16); border-radius: 9px; background: rgba(255,255,255,.13); }

.hero-visual {
  position: relative;
  z-index: 2;
  flex: 1;
  min-height: 250px;
  isolation: isolate;
  animation: visual-enter 650ms 190ms var(--ease-out) both;
}

.clovi-stage {
  position: absolute;
  z-index: 2;
  /* pill 좌표를 이 이미지에 맞춰 되돌렸으므로 클로비 위치도 그에 맞춰 원래 값으로 둔다
     (기준 파일은 -4% 지만 그건 기준 쪽 pill 좌표와 한 벌이다). */
  right: -1%;
  bottom: -18%;
  width: min(64%, 700px);
  aspect-ratio: 1;
  --eye-x: 0px;
  --eye-y: 0px;
  cursor: default;
  user-select: none;
  -webkit-user-select: none;
  contain: layout paint;
}
.clovi-canonical-base,
.clovi-eyes-layer {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}
.clovi-canonical-base { z-index: 1; }
.clovi-eyes-layer {
  z-index: 2;
  opacity: 1;
  transform: translate3d(var(--eye-x), var(--eye-y), 0);
  transform-origin: 59.2% 23.3%;
  transition: transform 95ms linear, opacity 150ms ease, filter 150ms ease;
  will-change: transform;
}
.clovi-success-badge {
  position: absolute;
  z-index: 3;
  top: 12%;
  right: 10%;
  display: grid;
  width: 46px;
  height: 46px;
  place-items: center;
  color: #174F3C;
  border: 2px solid rgba(255,255,255,.82);
  border-radius: 50%;
  opacity: 0;
  background: #8FF4C8;
  box-shadow: 0 10px 30px rgba(9,37,31,.28);
  font-size: 24px;
  font-weight: 900;
  transform: scale(.72);
  transition: opacity 180ms ease, transform 260ms var(--ease-out);
  pointer-events: none;
}
.clovi-stage[data-state="welcome"] .clovi-eyes-layer,
.clovi-stage[data-state="greeting"] .clovi-eyes-layer { animation: eye-welcome 720ms ease both; }
.clovi-stage[data-state="privacy"] .clovi-eyes-layer { opacity: .7; filter: saturate(.82) brightness(.88); }
.clovi-stage[data-state="loading"] .clovi-eyes-layer { animation: eye-working 880ms ease-in-out infinite; }
.clovi-stage[data-state="success"] .clovi-eyes-layer { animation: eye-success 760ms ease both; filter: brightness(1.16) saturate(1.08); }
.clovi-stage[data-state="success"] .clovi-success-badge { opacity: 1; transform: scale(1); }
.clovi-stage[data-state="error"] .clovi-eyes-layer { animation: eye-error 340ms ease both; }

.activity-pill {
  position: absolute;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  color: rgba(255,255,255,.92);
  border: 1px solid rgba(255,255,255,.18);
  border-radius: 999px;
  background: rgba(18,31,85,.5);
  box-shadow: 0 12px 28px rgba(6,12,42,.16);
  backdrop-filter: blur(12px);
  font-size: 12px;
  font-weight: 700;
  pointer-events: none;
}
.activity-pill i { width: 7px; aspect-ratio: 1; border-radius: 50%; background: #8FF4E8; box-shadow: 0 0 10px rgba(143,244,232,.76); }
/* 기준 파일의 좌표(.auth-pill-docs{right:8%;top:15%} 등)를 그대로 옮겨 봤다가 되돌렸다.
   그 값은 기준 쪽 히어로 비율에 맞춰 잡힌 것이라, 우리 아트워크에 적용하면 '문서 연결'이
   **클로비 얼굴 한가운데**에 올라앉는다(1920 캡처로 확인). 좌표를 베끼는 것보다 의도를
   지키는 쪽이 맞다 — pill 은 클로비 주변을 떠다니되 얼굴과 노트북을 가리지 않는다.
   아래 값은 이 이미지에 맞춰 실제로 맞춰 본 위치다. */
.activity-pill-docs { right: 38%; bottom: 50%; }
.activity-pill-flow { right: 3%; bottom: 31%; }
.activity-pill-team { right: 30%; bottom: 5%; }

.clovi-card {
  position: absolute;
  z-index: 4;
  bottom: 8%;
  left: 4%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px 12px 12px;
  color: var(--ink);
  border: 1px solid rgba(255,255,255,.56);
  border-radius: 16px;
  background: rgba(255,255,255,.93);
  box-shadow: 0 18px 44px rgba(9,17,60,.2);
  backdrop-filter: blur(14px);
}
.clovi-card img { width: 46px; height: 46px; border-radius: 12px; object-fit: cover; }
.clovi-card small, .clovi-card strong { display: block; }
.clovi-card small { color: var(--muted); font-size: 12px; font-weight: 650; }
.clovi-card strong { margin-top: 3px; font-size: 14px; transition: opacity var(--motion-fast) ease; }
.hero-footer { position: relative; z-index: 3; margin: 16px 0 0; color: rgba(255,255,255,.68); font-size: 11px; }

/* 폼 패널 — 값은 기준 파일(preview-standalone.html)의 .auth-panel 계열과 항목별로 맞춘다.
   사용자 지적: "현재 로그인 화면의 폰트, 굵기와 문구 표현이 기준 파일과 다르다". 실제로
   달랐던 것은 프레임이 아니라 이 아래의 타이포·간격 값들이었다(프레임은 이미 일치). */
.form-panel {
  display: flex;
  min-width: 0;
  align-items: center;
  justify-content: center;
  /* 기준: 좌측 패딩이 더 넓다(60px) — 히어로 카드와의 이음매에서 폼이 숨 쉴 공간을 만든다. */
  padding: 40px 36px 40px 60px;
  background: radial-gradient(circle at 100% 0%, rgba(117,138,225,.1), transparent 30%), #fff;
}
/* top:-9px — 기준이 폼 덩어리를 광학 중심으로 살짝 올려 놓았다. 수학적 중앙에 두면
   아래 지원 안내·푸터 때문에 시각적으로 내려가 보인다. */
/* width:min(430px,100%) 로 쓰면 안 된다 — 4K 블록과 좁은 화면 블록이 max-width 로 이 값을
   덮는데, width 가 걸려 있으면 그 오버라이드가 통째로 무시된다(3840 에서 폼이 430px 에
   묶여 오른쪽 패널이 비어 보였다). width:100% + max-width 조합을 유지한다. */
.form-container { position: relative; top: -9px; width: 100%; max-width: 430px; animation: form-enter 520ms 160ms var(--ease-out) both; }
.mobile-brand-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 56px; }
.panel-logo { width: 230px; max-width: calc(100% - 68px); height: auto; }
.mobile-clovi { display: none; width: 54px; height: 54px; border-radius: 16px; object-fit: cover; box-shadow: 0 10px 24px rgba(83,108,214,.18); }
.eyebrow { margin: 0 0 8px; color: var(--primary); font-size: 12px; font-weight: 800; letter-spacing: .13em; }
.form-panel h2 { margin: 0; color: var(--ink); font-size: 32px; line-height: 1.2; letter-spacing: -.04em; }
.subtitle { margin: 10px 0 28px; color: var(--muted); font-size: 14px; line-height: 1.65; word-break: keep-all; text-wrap: pretty; }
/* 기준은 필드마다 margin-bottom:17px 로 간격을 준다. form 의 gap 으로 주면 오류 메시지가
   나타났다 사라질 때 간격이 같이 흔들린다(메시지도 격자 항목이라). */
form { display: grid; gap: 0; margin-top: 0; }
.field { min-width: 0; margin-bottom: 17px; }
.field label { display: block; margin-bottom: 8px; color: var(--ink); font-size: 13px; font-weight: 700; }
/* 기준은 flex 가 아니라 3열 격자다(아이콘 24 / 입력 / 토글). flex + margin-left 로 두면
   비밀번호 필드(토글 있음)와 이메일 필드(토글 없음)의 입력 시작 위치가 미세하게 어긋난다. */
.input-wrap { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; gap: 8px; align-items: center; min-height: 54px; padding: 0 13px; overflow: hidden; border: 1px solid var(--border); border-radius: 12px; background: var(--surface-soft); transition: border-color var(--motion-fast) ease, box-shadow var(--motion-fast) ease, background var(--motion-fast) ease; }
.input-wrap:hover { background: #fff; border-color: #B9C5EA; }
/* 기준에는 뜨는 동작(translateY)이 없다. 입력창이 포커스마다 1px 움직이면 그 아래 폼 전체가
   같이 떨린다 — 원본에서 뺀 이유가 있다. */
.input-wrap:focus-within { border-color: var(--primary-soft); background: #fff; box-shadow: 0 0 0 4px rgba(117,138,225,.14); }
.input-wrap.invalid { border-color: var(--error); }
.input-wrap > svg { width: 20px; height: 20px; fill: none; stroke: var(--muted); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.input-wrap input { min-width: 0; height: 52px; padding: 0; color: var(--ink); border: 0; outline: 0; background: transparent; font-size: 15px; }
.input-wrap input::placeholder { color: #64708F; opacity: 1; }
.password-toggle { align-self: stretch; padding: 0; color: var(--primary); border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 750; }
.password-toggle:hover { color: var(--primary-dark); }
.password-toggle:disabled { color: #9AA3BD; cursor: wait; }
.password-toggle:focus-visible, .login-button:focus-visible, a:focus-visible { outline: 3px solid rgba(83,108,214,.25); outline-offset: 2px; }
.field-message, .caps-warning { min-height: 18px; margin: 5px 0 0; color: var(--error); font-size: 12px; line-height: 1.4; }
/* Caps Lock 경고는 자리를 미리 비워 두지 않는다.
   예전에는 visibility:hidden 으로 18px 을 항상 잡아 뒀는데(뜰 때 레이아웃이 안 흔들리게),
   그 결과 비밀번호 칸과 로그인 버튼 사이에 **늘** 죽은 여백 18px 이 남았다. 오류 메시지가
   이미 같은 자리를 18px 잡고 있어 두 겹이 된다. Caps Lock 경고는 사용자가 키를 누른
   그 순간에만 뜨는 것이라, 그때 한 줄 밀리는 편이 매 화면에 빈 띠를 두는 것보다 낫다.
   (게다가 밀리는 움직임 자체가 시선을 끌어 경고를 알아채게 한다.) */
.caps-warning { min-height: 0; }
.caps-warning[hidden] { display: none; }
/* 기준의 .auth-status 는 테두리가 없다 — 채운 면 하나로 상태를 말한다. 테두리를 더하면
   그 아래 입력창 테두리와 두 겹으로 겹쳐 보인다. */
.status { margin: 0 0 14px; padding: 11px 13px; color: #3167C9; border: 0; border-radius: 12px; background: #EDF3FF; font-size: 13px; line-height: 1.55; }
.status.error { color: var(--error); background: #FFF0F2; }
.status.success { color: var(--success); background: #F0FBF5; }

/* 자동완성 노란 배경 제거 (사용자 지적 §1).
 *
 * Chrome/Edge 는 자동완성된 입력에 사용자 스타일시트로는 못 바꾸는 배경(#FAFFBD 계열)을
 * 칠한다. background 를 아무리 지정해도 안 먹는다 — 유일하게 통하는 것이 **inset box-shadow**로
 * 그 위를 덮는 방법이다. 입력 자체는 투명하고 배경은 .input-wrap 이 갖고 있으므로, 덮는 색도
 * .input-wrap 의 상태별 배경과 같아야 한다(기본 연한 면, hover/focus 흰 면).
 *
 * transition 5000s: 자동완성 직후 노란색이 한 번 번쩍이는 것을 막는다. 값을 되돌리는 전이가
 * 사실상 끝나지 않게 해서 첫 프레임의 색이 유지되게 하는, 널리 쓰이는 관용구다.
 */
.input-wrap input:-webkit-autofill,
.input-wrap input:-webkit-autofill:hover,
.input-wrap input:-webkit-autofill:focus,
.input-wrap input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px var(--surface-soft) inset;
  box-shadow: 0 0 0 1000px var(--surface-soft) inset;
  -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink);
  transition: background-color 5000s ease-in-out 0s;
}
.input-wrap:hover input:-webkit-autofill,
.input-wrap:focus-within input:-webkit-autofill {
  -webkit-box-shadow: 0 0 0 1000px #fff inset;
  box-shadow: 0 0 0 1000px #fff inset;
}

.login-button {
  display: flex;
  min-height: 52px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
  color: #fff;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(135deg, #758AE1 0%, #536CD6 42%, #435CBE 100%);
  box-shadow: 0 12px 28px rgba(83,108,214,.28);
  cursor: pointer;
  font-size: 16px;
  font-weight: 800;
  transition: transform 140ms ease, box-shadow var(--motion-fast) ease, opacity var(--motion-fast) ease;
}
.login-button:hover { transform: translateY(-1px); box-shadow: 0 14px 32px rgba(83,108,214,.36); }
.login-button:active { transform: translateY(0) scale(.995); }
.login-button:disabled { opacity: .68; cursor: wait; transform: none; }
.login-button svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.button-spinner { width: 20px; aspect-ratio: 1; border: 2px solid rgba(255,255,255,.35); border-top-color: #fff; border-radius: 50%; animation: spin 720ms linear infinite; }
.button-spinner[hidden] { display: none; }
.support-note { margin-top: 22px; padding: 14px; color: var(--muted); border: 1px solid #E5EAF7; border-radius: 12px; background: var(--surface-soft); font-size: 12px; line-height: 1.55; }
.support-note a { color: var(--primary); font-weight: 700; text-decoration: none; }
.support-note a:hover { text-decoration: underline; }
.panel-footer { margin: 24px 0 0; color: #8790AA; text-align: center; font-size: 11px; }


@media (max-width: 1100px) {
  body { padding: 20px; }
  .login-shell { grid-template-columns: minmax(0, 1fr) minmax(380px, 430px); height: calc(100svh - 40px); min-height: 680px; }
  .hero { padding: 40px; }
  .hero h1 { font-size: clamp(38px, 4.2vw, 52px); }
  .clovi-stage { right: -4%; width: min(64%, 600px); }
  .clovi-card { left: 0; }
  .activity-pill-docs { right: 41%; }
  .form-panel { padding-inline: 36px; }
}

@media (max-width: 899px) {
  body { display: block; padding: 20px; }
  .login-shell { display: block; min-height: calc(100svh - 40px); height: auto; overflow: visible; }
  .hero { display: none; }
  .form-panel { min-height: calc(100svh - 40px); padding: 44px 56px; }
  .mobile-clovi { display: block; }
}

@media (max-width: 599px) {
  body { display: block; padding: 0; background: #fff; }
  .login-shell { min-height: 100svh; border: 0; border-radius: 0; box-shadow: none; animation: none; }
  .form-panel { min-height: 100svh; padding: max(28px, env(safe-area-inset-top)) max(24px, env(safe-area-inset-right)) max(28px, env(safe-area-inset-bottom)) max(24px, env(safe-area-inset-left)); }
  .mobile-brand-row { margin-bottom: 48px; }
  .panel-logo { width: 205px; }
  .mobile-clovi { width: 50px; height: 50px; }
  .form-panel h2 { font-size: 30px; }
}

@media (max-height: 840px) and (min-width: 900px) {
  .hero { padding-block: 30px; }
  .hero-copy { margin-top: 22px; }
  .hero h1 { font-size: clamp(34px, 4vw, 46px); }
  .hero-description { margin-top: 12px; font-size: 14px; }
  .feature-list { margin-top: 14px; gap: 7px; }
  .feature-list span { width: 28px; height: 28px; }
  .hero-visual { min-height: 135px; }
  .clovi-stage { right: -4%; width: min(58%, 430px); bottom: -8%; }
  .clovi-card, .activity-pill { display: none; }
  .hero-footer { display: none; }
  .mobile-brand-row { margin-bottom: 28px; }
  .form-panel { padding-block: 24px; }
  form { margin-top: 24px; }
  .support-note { margin-top: 18px; }
  .panel-footer { margin-top: 16px; }
}

@media (min-width: 2200px) and (min-height: 1200px) {
  body { padding: 48px; }
  .login-shell {
    width: min(calc(100vw - 96px), clamp(1920px, 68vw, 3000px));
    height: min(calc(100svh - 96px), clamp(1200px, 70svh, 1700px));
    min-height: 900px;
    max-width: 3000px;
  }
  .hero { padding: clamp(64px, 3vw, 96px); }
  /* 폭이 아니라 높이(--wm-h)를 키운다 — 기본 규칙이 폭을 높이×4.872 로 계산하므로,
     여기서 폭만 덮으면 높이는 58px 에 묶여 로고가 좌우로 letterbox 된다(실측 320x58). */
  .hero-logo { --wm-h: clamp(66px, 1.85vw, 80px); }
  .hero-copy { max-width: 920px; margin-top: clamp(64px, 5vh, 88px); }
  .hero-eyebrow { font-size: clamp(14px, .45vw, 16px); }
  .hero h1 { max-width: 920px; font-size: clamp(64px, 1.8vw, 76px); }
  .hero-description { max-width: 760px; font-size: clamp(17px, .52vw, 19px); }
  .feature-list { gap: 13px; margin-top: 26px; }
  .feature-list li { font-size: clamp(15px, .44vw, 17px); }
  .feature-list span { width: 38px; height: 38px; }
  .hero-visual { min-height: 300px; }
  .clovi-stage { width: min(64%, clamp(780px, 25vw, 1040px)); max-width: none; }
  .activity-pill { padding: 11px 16px; font-size: 14px; }
  .clovi-card { left: 5%; bottom: 8%; padding: 15px 22px 15px 15px; }
  .clovi-card img { width: 56px; height: 56px; }
  .clovi-card small { font-size: 13px; }
  .clovi-card strong { font-size: 16px; }
  .hero-footer { font-size: 12px; }
  .form-panel { padding: clamp(64px, 3vw, 96px); }
  .form-container { max-width: clamp(480px, 14vw, 540px); }
  .mobile-brand-row { margin-bottom: clamp(72px, 6vh, 96px); }
  .panel-logo { width: clamp(255px, 7vw, 300px); }
  .eyebrow { font-size: 14px; }
  .form-panel h2 { font-size: clamp(38px, 1.1vw, 46px); }
  .subtitle { font-size: clamp(15.5px, .48vw, 17px); }
  form { margin-top: 38px; }
  .field label { font-size: 14px; }
  .input-wrap { min-height: 60px; }
  .input-wrap input { height: 58px; font-size: 16px; }
  .login-button { min-height: 60px; font-size: 17px; }
  .support-note { font-size: 14px; }
  .panel-footer { font-size: 12px; }
}

/* Compact desktop heights: keep the complete login flow visible at 1280x720-class screens. */
@media (max-height: 760px) and (min-width: 900px) {
  body { padding: 12px; }
  .login-shell { height: calc(100svh - 24px); min-height: 0; }
  .hero { padding: 24px 36px; }
  /* 세로가 짧은 화면 — 로고도 같이 낮춘다(폭이 아니라 높이로). */
  .hero-logo { --wm-h: 42px; }
  .hero-copy { margin-top: 14px; }
  .hero-eyebrow { margin-bottom: 10px; font-size: 12px; }
  .hero h1 { font-size: clamp(32px, 3.5vw, 40px); }
  .hero-description { margin-top: 9px; font-size: 13.5px; line-height: 1.55; }
  .feature-list { margin-top: 10px; gap: 5px; }
  .feature-list li { font-size: 13px; }
  .feature-list span { width: 26px; height: 26px; border-radius: 7px; }
  .hero-visual { min-height: 90px; }
  .clovi-stage { right: -4%; width: min(58%, 420px); bottom: -8%; }
  .hero-footer { margin-top: 4px; }
  .form-panel { padding: 14px 36px; }
  .mobile-brand-row { margin-bottom: 16px; }
  .panel-logo { width: 205px; }
  .eyebrow { font-size: 12px; }
  .form-panel h2 { margin-top: 4px; font-size: 28px; }
  .subtitle { margin-top: 5px; font-size: 13px; line-height: 1.5; }
  form { margin-top: 15px; }
  .field label { margin-bottom: 5px; }
  .input-wrap { min-height: 48px; }
  .input-wrap input { height: 46px; }
  .field-message, .caps-warning { min-height: 15px; margin: 2px 0 0; font-size: 11px; }
  .login-button { min-height: 48px; }
  .support-note { margin-top: 10px; padding: 11px 13px; font-size: 12.5px; line-height: 1.5; }
  .panel-footer { margin-top: 8px; font-size: 10.5px; }
}

@media (max-width: 599px) and (max-height: 700px) {
  .form-panel { height: auto; padding-block: 20px; }
  .mobile-brand-row { margin-bottom: 32px; }
  form { margin-top: 24px; }
  .support-note { margin-top: 16px; }
  .panel-footer { margin-top: 16px; }
}

@media (min-width: 600px) and (max-width: 899px) {
  .form-panel { min-height: calc(100svh - 42px); }
}


/* Short tablet landscape: keep the primary action in the first viewport. */
@media (min-width: 600px) and (max-width: 899px) and (max-height: 500px) {
  body { padding: 0; background: #fff; }
  .login-shell { min-height: 100svh; border: 0; border-radius: 0; box-shadow: none; }
  .form-panel { min-height: 100svh; justify-content: flex-start; padding: 14px 24px; }
  .form-container { max-width: 760px; }
  .mobile-brand-row { margin-bottom: 8px; }
  .panel-logo { width: 190px; }
  .mobile-clovi { width: 44px; height: 44px; border-radius: 12px; }
  .eyebrow { font-size: 11px; }
  .form-panel h2 { margin-top: 2px; font-size: 24px; }
  .subtitle { margin-top: 3px; font-size: 12.5px; line-height: 1.4; }
  form { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 14px; row-gap: 2px; margin-top: 10px; }
  .field label { margin-bottom: 4px; }
  .input-wrap { min-height: 46px; }
  .input-wrap input { height: 44px; }
  .field-message, .caps-warning { min-height: 15px; margin: 2px 0 0; font-size: 11px; }
  .status, .login-button { grid-column: 1 / -1; }
  .status { margin: 2px 0 6px; padding: 8px 11px; }
  .login-button { min-height: 46px; margin-top: 0; }
  .support-note { margin-top: 8px; padding: 8px 11px; font-size: 12px; line-height: 1.4; }
  .panel-footer { margin-top: 5px; font-size: 10.5px; }
}

@media (forced-colors: active) {
  .input-wrap, .support-note, .status { border: 1px solid CanvasText; }
  .login-button { border: 1px solid ButtonText; background: ButtonFace; color: ButtonText; }
}

/* Mobile with an open software keyboard: keep the submit action visible. */
@media (max-width: 599px) and (max-height: 520px) {
  .form-panel {
    min-height: 100svh;
    justify-content: flex-start;
    padding: max(12px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right)) max(14px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
  }
  .mobile-brand-row { margin-bottom: 10px; }
  .panel-logo { width: 170px; }
  .mobile-clovi { width: 40px; height: 40px; border-radius: 12px; }
  .eyebrow { font-size: 11px; }
  .form-panel h2 { margin-top: 2px; font-size: 25px; }
  .subtitle { display: none; }
  form { gap: 0; margin-top: 12px; }
  .field label { margin-bottom: 4px; }
  .input-wrap { min-height: 46px; }
  .input-wrap input { height: 44px; }
  .field-message, .caps-warning { min-height: 14px; margin: 2px 0 1px; font-size: 11px; }
  .login-button { min-height: 48px; margin-top: 0; }
  .support-note { margin-top: 8px; padding: 8px 11px; font-size: 12px; line-height: 1.4; }
  .panel-footer { margin-top: 5px; font-size: 10.5px; }
}


/* ══════════════════════════════════════════════════════════════════════════
   저장소 접합부 — 원본 정적본에 없던 것들.
   원본은 데모라 서버가 없었다. 이 화면은 실제 /login을 쓴다: no-JS POST 폴백, 서버 렌더
   오류 배너, 계정 잠금 분기, /readyz 프로브. 그 요소들의 스타일만 여기 있다.
   위쪽 원본 영역의 값은 하나도 바꾸지 않았다.
   ══════════════════════════════════════════════════════════════════════════ */

/* #login-error는 서버 렌더(login_error)와 login.js가 함께 쓰는 하나의 실패 배너다.
   원본의 #login-status(.status)와 같은 상자이며, 이 화면에서는 실패에만 쓰이므로
   class="status error"로 고정한다(login.js는 textContent만 쓰고 클래스는 건드리지 않는다).
   비었을 때 상자가 남으면 실패한 적 없는 화면에 빈 붉은 칸이 뜬다 — 그때만 감춘다.
   :empty는 자식 노드가 하나도 없을 때만 맞으므로 Jinja가 빈 문자열을 렌더한 상태와 정확히 일치한다. */
#login-error:empty { display: none; }

/* 세션 만료 안내는 폼 위에 뜨는 페이지 수준 알림이라 폼 안(.status)의 위/아래 여백이 맞지 않는다.
   상자의 생김새는 원본 .status(정보 톤) 그대로 두고 여백만 이 자리로 맞춘다. */
.session-notice { margin: 18px 0 0; }

/* login.js가 /readyz 프로브 결과를 여기에 쓴다(정상이면 빈 문자열이라 아무것도 안 보인다).
   .is-error는 login.js가 토글한다 — 규칙이 없으면 오류가 평상시와 같은 회색으로 나가 묻힌다. */
.server-status { min-height: 16px; margin: 10px 0 0; color: var(--muted); text-align: center; font-size: 11.5px; }
.server-status.is-error { color: var(--error); font-weight: 600; }
/* 정상일 때 login.js는 이 칸을 빈 문자열로 둔다. 원본에 없던 요소이므로 그때는 높이를
   0으로 접어, 짧은 데스크톱(≤840px)에서 저작권 줄을 셸 밖으로 밀어내지 않게 한다.
   display:none으로 감추지는 않는다 — aria-live 영역이 접근성 트리에서 빠지면 나중에
   문구가 들어와도 스크린리더가 읽지 않을 수 있다. 렌더는 유지하고 크기만 없앤다. */
.server-status:empty { min-height: 0; margin: 0; }

/* login.js는 실패 시 이메일/비밀번호 칸에 aria-invalid를 건다(스크린리더용). 시각 신호가 없으면
   같은 사실이 눈에는 안 보인다 — 원본의 .invalid와 같은 붉은 테두리로 맞춘다. clovi-login.js도
   .invalid를 함께 토글하므로 두 신호가 항상 같이 움직인다(base.css의 같은 :has() 규칙은 이
   화면에서 로드되지 않으므로 여기 다시 둔다). */
.input-wrap:has(input[aria-invalid="true"]) { border-color: var(--error); }

/* 워드마크는 인라인 SVG다(_wordmark.html) — 글자가 currentColor를 따르므로 놓인 면의 색을
   여기서 정한다. 원본은 밝은 면/어두운 면용 SVG 파일을 두 벌 두고 골라 끼웠는데, 이 저장소는
   바로 그 방식으로 두 번 틀렸다(흰 카드에 흰 글자 → 잉크 카드에 어두운 글자).
   tests/regression/test_wordmark_follows_its_surface.py가 그 구조를 지킨다. */
/* --wm-accent: 락업의 'Assist' 글자 색. 공식 자산은 흰 면에서 #536CD6 을 쓰는데, 그 값을
   딥 인디고 히어로에 그대로 쓰면 배경과 붙어 안 읽힌다(대비 1.6:1). 면마다 다른 값을 준다.
   변수를 안 주는 면에서는 _wordmark.html 의 fallback 이 currentColor 로 떨어져 최소한 읽힌다. */
.hero-logo { color: #fff; --wm-accent: #BCC8FF; }
.hero-logo .wordmark-sub { fill: rgba(255,255,255,.72); }
.panel-logo { color: var(--ink); --wm-accent: var(--primary); }
.panel-logo .wordmark-sub { fill: var(--muted); }
.wordmark-accent { fill: var(--wm-accent, currentColor); }

/* JS가 없는 브라우저에는 클로비가 표정을 바꾸지 못한다 — 그때는 말풍선이 틀린 말을 하느니
   로드 시점 문구 그대로 두는 편이 낫다(카드 자체는 브랜드 장식이라 남긴다).
   폼은 여전히 동작한다: <form action="/login" method="post">가 네이티브로 제출된다. */
noscript .status { display: block; }
