/* ============================================================================
   Camera grid (AEY "Cameras" tab)
   ----------------------------------------------------------------------------
   Rendered by components/camera_grid.rs. A responsive grid of 16:9 camera
   tiles. A camera that holds an RTSP feed (`rtsp`/`stream_url`) is a live
   camera: its tile streams the feed via the platform's RTSP->MJPEG relay —
   click the centred "Watch live" control to start, Stop to hang up, with a
   LIVE pill while streaming (start/stop driven by static/app/camera-grid.js).
   Cameras without a feed are stills: the last stored thumbnail with the mode
   glyph, online/offline state and mode badge. The whole tile is one <a> to
   the camera's record page.

   The glass PILLS here are the deliberate local exception (mirroring the
   legacy site-cameras wall): chrome over moving footage must not look like a
   rectangle cut out of it, so pills are fully rounded. Kept to this surface.
   ========================================================================== */

.camera-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (min-width: 640px) {
  .camera-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1200px) {
  .camera-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.camera-tile {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  border-radius: var(--ta-r-lg);
  border: 1px solid var(--ta-border-strong);
  background: #000;
  color: inherit;
  text-decoration: none;
  outline-offset: 2px;
  isolation: isolate;
  transition: transform 0.14s ease-out, box-shadow 0.14s ease-out,
    border-color 0.14s ease-out;
}

.camera-tile:hover {
  text-decoration: none;
  transform: translateY(-2px);
  border-color: color-mix(in oklab, var(--ta-accent) 55%, var(--ta-border-strong));
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}

.camera-tile:focus-visible {
  outline: 2px solid var(--ta-accent);
}

.camera-tile-stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: #000;
}

/* Live MJPEG frame — carries no `src` until the operator clicks "Watch
   live" (the JS copies `data-live-src` into `src`), so it renders nothing
   while idle and sits above the still once streaming. */
.camera-tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  z-index: 2;
}

/* Still fallback: thumbnail (dimmed) behind the centred mode glyph. The
   stage is always a dark monitoring frame (footage is dark), so the still
   uses fixed dark shades in both themes rather than the light surface.
   Hidden only once a stream starts (`.is-live` toggled in JS). */
.camera-tile-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 120% at 20% 0%, rgba(120, 130, 150, 0.14), transparent 55%),
    linear-gradient(160deg, #1b202a, #0b0d11 85%);
  /* Fixed light — the stage is always dark footage, so the glyph must be
     light in light mode too (--ta-muted would go dark and vanish). */
  color: rgba(232, 235, 241, 0.78);
}

.camera-tile-thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.55;
  filter: saturate(0.85);
}

.camera-tile-glyph {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--ta-r-lg);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(4px);
}

.camera-tile-glyph svg {
  width: 1.5rem;
  height: 1.5rem;
}

/* ── Glass chrome (floats over the frame) ───────────────────────────────── */

.camera-tile-top,
.camera-tile-bottom {
  position: absolute;
  left: 0.75rem;
  right: 0.75rem;
  /* Above the still/stream (z-index 2) and the play control (z-index 3) so
     name/mode stay crisp over the dimmed idle state; pointer-events stay
     none so clicks reach the play button (except the stop control, which
     opts back in). */
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  pointer-events: none;
}

.camera-tile-top {
  top: 0.75rem;
  justify-content: space-between;
}

.camera-tile-bottom {
  bottom: 0.75rem;
  justify-content: space-between;
  gap: 0.75rem;
}

.camera-tile-top-right {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
}

/* Shared glass-pill recipe (chrome over footage is rounded by design). */
.camera-tile-status,
.camera-tile-live,
.camera-tile-mode {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.16rem 0.55rem;
  border-radius: 999px;
  background: rgba(10, 12, 16, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #e8ebf1;
  font-family: var(--ta-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.camera-tile-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--ta-err);
}

.camera-tile-status.online .camera-tile-dot {
  background: var(--ta-ok);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ta-ok) 28%, transparent);
}

.camera-tile-mode {
  text-transform: none;
}

.camera-tile-mode.mode-continuous {
  color: #d9c4ff;
}

.camera-tile-mode.mode-alarm {
  color: #ffd9a8;
}

.camera-tile-mode.mode-upload {
  color: #a9c8ff;
}

.camera-tile-mode.mode-neutral {
  color: #cdd3de;
}

/* LIVE pill — hidden until a stream starts (`.is-live` toggled in JS). */
.camera-tile-live {
  display: none;
  background: rgba(214, 69, 69, 0.92);
  border-color: rgba(255, 255, 255, 0.25);
  color: #fff;
  letter-spacing: 0.08em;
}

.camera-tile-pulse {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: #fff;
  animation: camera-tile-ping 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;
}

@keyframes camera-tile-ping {
  0% {
    box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.55);
  }

  70%,
  100% {
    box-shadow: 0 0 0 6px rgba(255, 255, 255, 0);
  }
}

.camera-tile-stop {
  display: none;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(10, 12, 16, 0.6);
  color: #e8ebf1;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  cursor: pointer;
  pointer-events: auto;
  transition: background 0.15s ease, transform 0.15s ease;
}

.camera-tile-stop svg {
  width: 0.85rem;
  height: 0.85rem;
}

.camera-tile-stop:hover {
  background: rgba(214, 69, 69, 0.85);
  transform: scale(1.06);
}

.camera-tile-stop:focus-visible {
  outline: 2px solid var(--ta-accent);
  outline-offset: 2px;
}

/* Stream started: hide the still + play control, reveal LIVE + Stop. The
   `.is-live` ancestor is either a grid `.camera-tile` or the camera page's
   `.camera-page-stage` — both are wired by static/app/camera-grid.js. */
.is-live .camera-tile-live,
.is-live .camera-tile-stop {
  display: inline-flex;
}

.is-live .camera-tile-play,
.is-live .camera-tile-fallback {
  display: none;
}

.camera-tile-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: #fff;
  font-size: 0.82rem;
  font-weight: 600;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

.camera-tile-mode {
  flex: none;
}

/* ── Watch-live control (streamable + idle) ───────────────────────────────
   Centred (not full-bleed): the tile is also the camera's record link, so
   the play control must leave the surrounding frame clickable for that. */

.camera-tile-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: #fff;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.camera-tile-play:focus-visible {
  outline: 2px solid var(--ta-accent);
  outline-offset: 2px;
}

.camera-tile-play-ring {
  display: grid;
  place-items: center;
  width: 3.4rem;
  height: 3.4rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.28);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  transition: transform 0.18s ease, background 0.18s ease;
}

.camera-tile-play-ring svg {
  width: 1.4rem;
  height: 1.4rem;
  margin-left: 0.15rem;
  fill: #fff;
}

.camera-tile-play:hover .camera-tile-play-ring {
  transform: scale(1.06);
  background: rgba(255, 255, 255, 0.22);
}

.camera-tile-play-label {
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
}

@media (prefers-reduced-motion: reduce) {
  .camera-tile-pulse {
    animation: none;
  }
}

/* ── Hikvision alarm-email state on a tile ────────────────────────────────
   Each tile carries a status pill (a `tone-*` hue over the glass chrome:
   "Sending alarms" green, "Needs attention" amber, "Setup failed" red,
   "Not set up" muted) and — when the camera has an address the one-click
   auto-calibrate can use — a small refresh button that POSTs the ISAPI
   setup and fills the grid panel's shared result area (hik-calibrate.js).
   The bottom bar is pointer-events:none (it floats over the footage), so
   the button opts back in. */

.camera-tile-bottom-right {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
  min-width: 0;
  flex: none;
}

.camera-tile-alarm {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: rgba(10, 12, 16, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-family: var(--ta-mono);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  /* The tone colour lightened so it reads on dark glass (the pill sits over
     a video frame, not a light panel — `--tone-ink` is tuned for light
     washes and would vanish here). */
  color: color-mix(in oklab, var(--tone) 88%, #fff);
}

.camera-tile-alarm svg {
  width: 0.72rem;
  height: 0.72rem;
  flex: none;
  color: color-mix(in oklab, var(--tone) 80%, #fff);
}

.camera-tile-calib {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border-radius: 999px;
  background: rgba(10, 12, 16, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: rgba(232, 235, 241, 0.92);
  cursor: pointer;
  pointer-events: auto;
  transition: color 0.14s ease, background 0.14s ease, border-color 0.14s ease,
    transform 0.14s ease;
}

.camera-tile-calib:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.3);
  transform: translateY(-1px);
}

.camera-tile-calib:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

.camera-tile-calib svg {
  width: 0.9rem;
  height: 0.9rem;
}

.camera-tile-calib.is-busy svg {
  animation: camera-tile-calib-spin 0.9s linear infinite;
}

@keyframes camera-tile-calib-spin {
  to {
    transform: rotate(360deg);
  }
}