/*
  Cinematic login screen — DA.MOTORS calculator.

  Layer stack (back to front):
    .hero-stage        fixed full-bleed container
      .hero-media-slot   swappable background (HeroMedia mounts here; empty today)
      .hero-fx           smoke / volumetric light / body-sweep — generic atmosphere,
                          designed to sit over whatever real photo/video is mounted
      .hero-overlay      flat dimming layer, strength set from config
                          (imageOverlayOpacity) — the safety margin for text
                          contrast against a photo we haven't seen yet
      .hero-scrim        left→right darkening gradient (desktop) so the form stays
                          readable no matter what the photo looks like
    .login-panel        in-flow content (logo, title, benefits, Telegram button)

  Effect layers (smoke / light rays / body-shine / Ken Burns / parallax) are
  toggled by JS via classes on <body> (see login.js: applyQualityMode()) —
  one function decides the quality mode (desktopFull / tabletReduced /
  mobileLite / reducedMotion) and one config decides which effects the author
  wants at all (hero-media-config.js). CSS below only reacts to the resulting
  classes; it does not decide anything on its own except the hard
  prefers-reduced-motion override at the bottom, which stays CSS-only on
  purpose so it can never depend on JS having run.
*/

html, body {
  height: 100%;
}

body.login-page {
  margin: 0;
  min-height: 100dvh;
  overflow-x: hidden;
  background-color: var(--color-bg); /* shows instantly, before any media loads */
  font-family: var(--font-sans);
  color: var(--color-text);
}

/* ---------- Hero stage (background) ---------- */

.hero-stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background-color: var(--color-bg);
}

.hero-media-slot {
  position: absolute;
  inset: 0;
}

/*
  Placeholder state: no real photo/video yet. This is a plain, honest dark
  gradient — not an illustrated showroom or a stand-in car. It exists so the
  atmosphere system (smoke/light/sweep below) has something to sit on top of.
  TODO(DA.MOTORS): once real showroom/car photography or video is supplied,
  it mounts INSIDE this element via hero-media.js — nothing here needs to change.
*/
.hero-media {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 100% at 68% 32%, var(--color-surface) 0%, var(--color-bg) 55%, var(--color-bg-deep) 100%);
}

.hero-media img,
.hero-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Mobile-first position; desktop override below. Both custom properties
     are set from hero-media-config.js (imagePositionMobile/Desktop) — no
     CSS edit needed to reframe a photo. */
  object-position: var(--hero-pos-mobile, 50% 30%);
}

@media (min-width: 1024px) {
  .hero-media img,
  .hero-media video {
    object-position: var(--hero-pos-desktop, 68% 40%);
  }
}

/* Photos fade in once loaded instead of popping in; the placeholder
   gradient (or poster, for video) shows underneath in the meantime.
   A well-exposed photo (like the current BMW mockup) needs only a light
   cinematic grade here, not a brightness rescue — the car should already
   read clearly straight out of the source image. */
.hero-media img {
  opacity: 0;
  filter: brightness(1.03) contrast(1.04) saturate(0.98);
  transition: opacity 0.6s var(--ease-standard);
}

.hero-media img.is-loaded {
  opacity: 1;
}

.hero-media--kenburns img,
.hero-media--kenburns video {
  animation: kenburns 34s ease-in-out infinite alternate;
  transform-origin: 62% 38%;
}

@keyframes kenburns {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.045) translate3d(-1%, -1%, 0); }
}

/* ---------- Atmosphere (smoke / light / body sweep) ---------- */

.hero-fx {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/*
  Real smoke, filmed on solid black, composited with mix-blend-mode:screen —
  black contributes nothing to a screen blend, so the black background
  disappears on its own and only the bright smoke shows through. One clip:
  dense ground fog at the bottom of the frame. (A second, higher/backlit
  layer was tried and removed — Pavel found it added nothing, and its
  container's bottom edge showed as a hard horizontal seam across the whole
  frame while the video was still loading, before screen-blend evened it
  out. Don't re-add a second full-bleed-height smoke layer without solving
  that load-time seam first.)

  IMPORTANT: opacity/mask/filter must live on the <video> itself, never on
  its wrapper. Any of those on an ANCESTOR creates a new stacking context,
  which isolates mix-blend-mode from the photo behind it — the video would
  then blend against its own (black) backdrop instead, rendering as a
  visible dark rectangle instead of disappearing. Wrapper divs below only
  carry position/inset/overflow.
*/
.hero-fx__smoke-video video {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
  mix-blend-mode: screen;
  pointer-events: none;
}

/* Belt-and-suspenders: some Chromium builds (Opera included) show a
   floating Picture-in-Picture / seek overlay on hover regardless of the
   disablepictureinpicture attribute. Hide any native video chrome outright
   — this is decorative background, never meant to be interacted with. */
.hero-fx__smoke-video video::-webkit-media-controls,
.hero-fx__smoke-video video::-webkit-media-controls-panel,
.hero-fx__smoke-video video::-webkit-media-controls-overlay-play-button,
.hero-fx__smoke-video video::-webkit-media-controls-picture-in-picture-button {
  display: none !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

/* Dense ground fog, boxed to the BOTTOM ~40% of the frame only (not
   full-bleed) so it stays at wheel/floor height and never climbs over the
   grille, headlights, or roofline. */
.hero-fx__smoke-video--front {
  position: absolute;
  inset: auto 0 0 0;
  height: 38%;
  overflow: hidden;
}

.hero-fx__smoke-video--front video {
  opacity: 0.62;
  object-position: center 100%;
  -webkit-mask-image: linear-gradient(to top, #000 55%, transparent 100%);
  mask-image: linear-gradient(to top, #000 55%, transparent 100%);
}

/* reducedMotion: video doesn't play (still frame shows via the video's own
   poster attribute — see login.js). */
.fx-smoke-off .hero-fx__smoke-video { display: none; }

.hero-fx__light {
  position: absolute;
  inset: 0;
  /* Two layers: a soft ambient wash (as before) plus a set of narrow
     parallel bands standing in for visible beams cutting through the
     smoke. The mask fades both toward the light's own origin (top-right)
     so the bands read as shafts radiating from a source, not blinds
     covering the whole frame evenly. Cool blue-steel, no warm/neon tones. */
  background: conic-gradient(from 200deg at 76% 18%, rgba(140, 175, 255, 0.18), transparent 30%, transparent 70%, rgba(140, 175, 255, 0.1)),
    repeating-linear-gradient(100deg, transparent 0 46px, rgba(180, 205, 255, 0.045) 46px 54px, transparent 54px 120px);
  -webkit-mask-image: radial-gradient(65% 65% at 76% 18%, #000 0%, rgba(0, 0, 0, 0.6) 45%, transparent 78%);
  mask-image: radial-gradient(65% 65% at 76% 18%, #000 0%, rgba(0, 0, 0, 0.6) 45%, transparent 78%);
  mix-blend-mode: screen;
  animation: lightBreathe 24s ease-in-out infinite;
}

@keyframes lightBreathe {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 0.7; }
}

/* Rare, very soft flicker — a light rig settling, not a strobe */
.hero-fx::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  animation: rareFlicker 29s ease-in-out infinite;
}

@keyframes rareFlicker {
  0%, 96%, 100% { opacity: 0; }
  97%           { opacity: 0.02; }
  98%           { opacity: 0; }
}

.hero-fx__sweep {
  position: absolute;
  inset: 0;
  /* Wide, gradual falloff instead of a tight band — reads as a slow sheen,
     not a hard-edged stripe crossing the frame. */
  background: linear-gradient(115deg, transparent 25%, rgba(255, 255, 255, 0.035) 50%, transparent 75%);
  background-size: 260% 260%;
  mix-blend-mode: soft-light;
  animation: sweepPass 13s linear infinite;
}

@keyframes sweepPass {
  from { background-position: 120% 0; }
  to   { background-position: -20% 0; }
}

/* Quality-mode toggles, set on <body> by login.js (applyQualityMode).
   One function resolves the mode; these classes are the only thing CSS
   reacts to — no separate per-breakpoint effect rules elsewhere. Smoke
   video visibility rules live with the smoke video block above. */
.fx-lightrays-off .hero-fx__light { display: none; }
.fx-bodyshine-off .hero-fx__sweep { display: none; }

/* ---------- Overlay (config-driven safety margin for text contrast) ---------- */

.hero-overlay {
  position: absolute;
  inset: 0;
  background: #000;
  opacity: var(--hero-overlay-opacity, 0.15);
  pointer-events: none;
}

/* ---------- Scrim (keeps the form readable over any photo) ---------- */

.hero-scrim {
  position: absolute;
  inset: 0;
  /* Lighter than before through the middle band — the BMW's grille/lights
     need to stay recognizable behind the text on mobile, not just present
     as vague atmosphere. Still dense enough at the bottom for the button. */
  background: linear-gradient(
    180deg,
    rgba(15, 17, 23, 0.42) 0%,
    rgba(15, 17, 23, 0.72) 55%,
    rgba(15, 17, 23, 0.93) 100%
  );
}

@media (min-width: 1024px) {
  .hero-scrim {
    /* More stops, slightly off-horizontal (100deg) — a gradual fade rather
       than a band with a couple of steep steps in it. */
    background: linear-gradient(
      100deg,
      rgba(15, 17, 23, 0.96) 0%,
      rgba(15, 17, 23, 0.85) 26%,
      rgba(15, 17, 23, 0.64) 44%,
      rgba(15, 17, 23, 0.4) 60%,
      rgba(15, 17, 23, 0.18) 76%,
      rgba(15, 17, 23, 0.05) 88%,
      rgba(15, 17, 23, 0) 98%
    );
  }
}

/* ---------- Content ---------- */

.login-panel {
  position: relative;
  z-index: 1;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  padding: 32px 20px;
}

@media (min-width: 640px) {
  .login-panel { padding: 48px 40px; }
}

@media (min-width: 1024px) {
  .login-panel { padding: 0 0 0 96px; }
}

@media (min-width: 1440px) {
  .login-panel { padding-left: 140px; }
}

.login-content {
  width: 100%;
  max-width: 460px;
}

.login-brand {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-accent-soft);
  margin: 0 0 16px;
}

.login-title {
  /* Fluid scale, tuned at ~375/640/1024/1440/1920px rather than a fixed
     handful of breakpoints: ~30px on a phone, ~40px at 1024px, ~46px at
     1440px, capping at 53px beyond ~1920px. */
  font-size: clamp(1.75rem, 1.571rem + 1.451vw, 3.3125rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0 0 16px;
  text-wrap: balance;
}

.login-subtitle {
  font-size: var(--fs-md);
  line-height: 1.6;
  color: var(--color-text-muted);
  margin: 0 0 32px;
  max-width: 42ch;
}

.login-benefits {
  list-style: none;
  margin: 0 0 32px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.login-benefits li {
  position: relative;
  padding-left: 28px;
  font-size: var(--fs-base);
  color: var(--color-text);
}

.login-benefits li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1px;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-xs);
  background: rgba(5, 150, 105, 0.16);
  border: 1px solid var(--color-success-border);
}

.login-benefits li::after {
  content: "";
  position: absolute;
  left: 4.5px;
  top: 6px;
  width: 9px;
  height: 5px;
  border-left: 2px solid var(--color-success-text);
  border-bottom: 2px solid var(--color-success-text);
  transform: rotate(-45deg);
}

.login-cta {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.telegram-widget-holder {
  min-height: 52px;
  display: flex;
  align-items: center;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  padding: 8px 10px;
  width: fit-content;
  transition: border-color var(--dur-base) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard),
    transform var(--dur-fast) var(--ease-standard);
}

/* Soft diffuse glow (not a hard ring) makes this slot read as the page's
   primary action. */
.telegram-widget-holder:hover,
.telegram-widget-holder:focus-within {
  border-color: rgba(59, 130, 246, 0.55);
  box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.3), 0 0 22px rgba(37, 99, 235, 0.22),
    0 14px 30px -12px rgba(37, 99, 235, 0.35);
}

.telegram-widget-holder:active {
  /* scale() scales children too, so the button presses down with the wrapper. */
  transform: scale(0.97);
}

/* Own button, not Telegram's iframe (see login.js for why) — plain link to
   oauth.telegram.org, styled to match Telegram's own widget colors so the
   switch is invisible to the user. */
.telegram-login-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 22px;
  background: #29a9eb;
  color: #fff;
  font-size: var(--fs-base);
  font-weight: 600;
  text-decoration: none;
  border-radius: 8px;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.telegram-login-btn:hover { background: #229ed9; }
.telegram-login-btn__icon { width: 20px; height: 20px; flex-shrink: 0; }

@media (max-width: 639px) {
  .telegram-login-btn { width: 100%; justify-content: center; }
}

@media (max-width: 639px) {
  .telegram-widget-holder {
    width: 100%;
    justify-content: center;
  }
}

/* iOS/Telegram quirk: after confirming the login inside the Telegram app,
   the app doesn't reliably hand control back to Safari on its own — found
   2026-07-28 watching a real failed attempt live in the server logs (button
   opens the Telegram app, confirms fine, but the browser tab just sits on
   /login and the person reloads it instead of switching back manually).
   Desktop's popup flow returns on its own, so this hint is mobile-only. */
.login-mobile-hint {
  display: none;
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  margin: 0;
  line-height: 1.4;
}
@media (max-width: 639px) {
  .login-mobile-hint {
    display: block;
  }
}

.login-error {
  font-size: var(--fs-sm);
  color: var(--color-danger-text);
  margin: 0;
}

.login-error[hidden] {
  display: none;
}

/* ---------- Entrance choreography ---------- */

.login-content > * {
  opacity: 0;
  transform: translateY(14px);
}

.login-content.is-ready > * {
  animation: contentIn 0.8s var(--ease-out-strong) forwards;
}

.login-content.is-ready > *:nth-child(1) { animation-delay: 0.05s; }
.login-content.is-ready > *:nth-child(2) { animation-delay: 0.18s; }
.login-content.is-ready > *:nth-child(3) { animation-delay: 0.3s; }
.login-content.is-ready > *:nth-child(4) { animation-delay: 0.42s; }
.login-content.is-ready > *:nth-child(5) { animation-delay: 0.54s; }

@keyframes contentIn {
  to { opacity: 1; transform: translateY(0); }
}

/* ---------- Reduced motion: freeze everything, keep the still frame ---------- */

@media (prefers-reduced-motion: reduce) {
  .hero-media--kenburns img,
  .hero-media--kenburns video,
  .hero-fx__light,
  .hero-fx__sweep,
  .hero-fx::after,
  .login-content > * {
    animation: none !important;
  }

  .login-content > * {
    opacity: 1 !important;
    transform: none !important;
  }
}
