/**
 * @file Live surveillance cameras (js/camera/camera.js): picture, overlay canvas, HUD, infrared fallback, the
 * Replaced's own lagging figure, and the veil of a room's micro-cut (js/camera/flicker.js, which animates it).
 */
.cam {
  position: relative;
  overflow: hidden;
  background: #000;
  aspect-ratio: 9 / 16;
  width: 100%;
}
.cam.wide,
.cam.media {
  aspect-ratio: 16 / 9;
}
.cam canvas,
.cam video,
.cam img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.cam img,
.cam video {
  filter: saturate(0.75) contrast(1.05);
}
/* Fallback infrared when the dark still is missing. */
.cam .dim {
  filter: grayscale(1) brightness(0.5) contrast(1.5) sepia(0.35) hue-rotate(55deg) saturate(1.4);
}
.cam .hud {
  position: absolute;
  left: 8px;
  right: 8px;
  top: 6px;
  display: flex;
  justify-content: space-between;
  font-size: 0.7rem;
  color: #ddd;
  text-shadow: 0 0 3px #000;
}
.cam .hud.bottom {
  top: auto;
  bottom: 6px;
}
.cam .rec {
  color: var(--red);
}
.cam .rec::before {
  content: '● ';
  animation: blink 0.8s infinite;
}
/* Source-sized scene: still and cropped alpha layers share the same cover crop. */
.cam .cam-scene {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.cam .cam-background,
.cam .cam-layers {
  position: absolute;
  inset: 0;
}
.cam .cam-layer {
  inset: auto;
  object-fit: fill;
}
/* A Silent (server/game/silents.js): the healthy crew member's picture, drained to a wet grey and darker, as if
   the camera could barely hold them. */
.cam .cam-layer.silent {
  filter: grayscale(0.92) sepia(0.18) hue-rotate(150deg) saturate(0.65) brightness(0.82) contrast(1.15);
}
/* The Replaced's own figure on their lying phone (js/camera/self-lag.js): a beat late, and now and then gone for
   an instant. */
.cam .cam-layer.lagging,
.cam .cam-layer.blink {
  visibility: hidden;
}

/* --- A room's micro-cut (js/camera/flicker.js): snow, a torn line and black, under the HUD --- */

.cam .cam-flicker,
.cam .cam-flicker > i {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.cam .cam-flicker > i {
  opacity: 0;
}
/* Snow over scan lines; flicker.js makes its grain jump. */
.cam .cam-flicker .snow {
  background:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.07) 0 1px, transparent 1px 3px),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 2.4 0 0 0 -1.1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* A torn line: a bright band of the picture slipping sideways. */
.cam .cam-flicker .tear {
  bottom: auto;
  height: 7%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.3) 20%,
    rgba(255, 255, 255, 0.08) 65%,
    transparent
  );
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.25);
}
.cam .cam-flicker .black {
  background: #000;
}

/* --- An incident's live effect over its still (js/camera/incident-fx.js): positioned in the scene's frame --- */
.cam-incident {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  --x: 50%;
  --y: 50%;
  --stage: 1;
}
.cam-incident.ir {
  filter: grayscale(1) brightness(1.15);
}
.cam-incident > div {
  position: absolute;
}
/* The fire's light, flickering, wider at each stage; a nearer, faster one on the flames themselves. */
.cam-incident .fx-glow {
  inset: 0;
  background: radial-gradient(
    circle at var(--x) var(--y),
    rgba(255, 150, 50, calc(0.18 + 0.06 * var(--stage))) 0,
    rgba(255, 90, 20, calc(0.08 + 0.03 * var(--stage))) calc(10% + 7% * var(--stage)),
    transparent calc(22% + 12% * var(--stage))
  );
  mix-blend-mode: screen;
  animation: fx-flicker 1.7s infinite;
}
.cam-incident .fx-glow-near {
  background: radial-gradient(circle at var(--x) var(--y), rgba(255, 200, 120, 0.35) 0, transparent 9%);
  animation-duration: 0.53s;
}
@keyframes fx-flicker {
  0%,
  100% {
    opacity: 0.85;
  }
  12% {
    opacity: 1;
  }
  21% {
    opacity: 0.62;
  }
  34% {
    opacity: 0.95;
  }
  49% {
    opacity: 0.7;
  }
  63% {
    opacity: 1;
  }
  78% {
    opacity: 0.76;
  }
}
/* Embers rising from the flames and fading. */
.cam-incident .fx-ember {
  left: calc(var(--x) + var(--dx));
  top: var(--y);
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #ffb347;
  box-shadow: 0 0 6px 2px rgba(255, 120, 30, 0.8);
  animation: fx-rise var(--dur) var(--delay) infinite ease-out;
}
@keyframes fx-rise {
  0% {
    opacity: 0;
    transform: translate(0, 0) scale(1);
  }
  15% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    top: calc(var(--y) - var(--rise));
    transform: translate(calc(var(--dx) * 2), 0) scale(0.4);
  }
}
/* Smoke, steam, the fire's own smoke: big soft clouds drifting slowly across the room (no blur filter: phones). */
.cam-incident .fx-haze {
  inset: -25%;
  animation: fx-drift var(--dur) var(--delay) infinite alternate ease-in-out;
}
.cam-incident .fx-haze-smoke {
  background: radial-gradient(
    ellipse at calc(50% + var(--ox)) calc(45% + var(--oy)),
    rgba(110, 110, 110, 0.38),
    transparent 45%
  );
}
.cam-incident .fx-haze-steam {
  background: radial-gradient(
    ellipse at calc(var(--x) + var(--ox) / 2) calc(var(--y) + var(--oy) / 2),
    rgba(235, 240, 245, 0.42),
    transparent 40%
  );
  animation-duration: calc(var(--dur) / 3);
}
.cam-incident .fx-haze-fire {
  background: radial-gradient(
    ellipse at calc(var(--x) + var(--ox) / 3) 18%,
    rgba(20, 16, 14, calc(0.15 * var(--stage))),
    transparent 55%
  );
}
@keyframes fx-drift {
  from {
    transform: translate(-6%, 2%) scale(1);
  }
  to {
    transform: translate(6%, -3%) scale(1.12);
  }
}
/* The leak: a white burst pulsing at the hole, debris sucked towards it from the room. */
.cam-incident .fx-burst {
  left: var(--x);
  top: var(--y);
  width: 9%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 255, 255, 0.7),
    rgba(220, 235, 255, 0.25) 35%,
    transparent 70%
  );
  animation: fx-pulse 0.9s infinite;
}
@keyframes fx-pulse {
  0%,
  100% {
    opacity: 0.55;
    transform: translate(-50%, -50%) scale(0.85);
  }
  50% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.15);
  }
}
.cam-incident .fx-debris {
  width: 5px;
  height: 3px;
  background: rgba(230, 230, 220, 0.85);
  animation: fx-suck var(--dur) var(--delay) infinite ease-in;
}
@keyframes fx-suck {
  0% {
    left: var(--sx);
    top: var(--sy);
    opacity: 0;
    transform: rotate(0);
  }
  12% {
    opacity: 0.9;
  }
  100% {
    left: var(--x);
    top: var(--y);
    opacity: 0;
    transform: rotate(var(--spin)) scale(0.3);
  }
}
/* The short circuit: irregular blue-white flashes at the arc, sparks falling and bouncing out. */
.cam-incident .fx-flash {
  inset: 0;
  background: radial-gradient(
    circle at var(--x) var(--y),
    rgba(200, 225, 255, 0.85) 0,
    rgba(140, 180, 255, 0.25) 12%,
    transparent 30%
  );
  mix-blend-mode: screen;
  animation: fx-arc 2.3s infinite;
}
.cam-incident .fx-flash-late {
  animation-duration: 3.7s;
  animation-delay: -1.1s;
}
@keyframes fx-arc {
  0%,
  9%,
  44%,
  100% {
    opacity: 0;
  }
  3% {
    opacity: 1;
  }
  6% {
    opacity: 0.2;
  }
  7% {
    opacity: 0.9;
  }
  41% {
    opacity: 0;
  }
  42% {
    opacity: 0.7;
  }
}
.cam-incident .fx-spark {
  left: var(--x);
  top: var(--y);
  width: 2px;
  height: 6px;
  background: #e8f2ff;
  box-shadow: 0 0 5px 1px rgba(170, 200, 255, 0.9);
  animation: fx-fall var(--dur) var(--delay) infinite cubic-bezier(0.4, 0, 1, 1);
}
@keyframes fx-fall {
  0% {
    opacity: 1;
    transform: translate(0, 0);
  }
  100% {
    opacity: 0;
    top: calc(var(--y) + var(--fall));
    transform: translate(calc(var(--dx) * 6), 0);
  }
}

/* A burnt room while it smokes: cinders glowing in the ash, fading in and out. */
.cam-incident .fx-cinder {
  left: calc(var(--x) + var(--dx));
  top: calc(var(--y) + var(--dy));
  width: 4px;
  height: 3px;
  border-radius: 50%;
  background: #ff7a1a;
  box-shadow: 0 0 6px 2px rgba(255, 90, 20, 0.7);
  animation: fx-glow-ember var(--dur) var(--delay) infinite ease-in-out;
}
@keyframes fx-glow-ember {
  0%,
  100% {
    opacity: 0.15;
  }
  50% {
    opacity: 0.9;
  }
}

/* Corridor CCTV slots stay above the picture and leave the timestamp visible. */
.cam .cam-slots {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: max(28px, 8%); /* above the timestamp, and the TV's status line on a wall screen */
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 3px;
}
.cam .cam-slots[hidden] {
  display: none;
}
.cam .cam-slots button {
  min-width: 0;
  min-height: 30px;
  padding: 2px 0;
  border: 1px solid #61716d;
  border-radius: 2px;
  background: #07110ee6;
  color: #b8c9c3;
  font: 11px monospace;
  cursor: pointer;
}
.cam .cam-slots button[aria-pressed='true'] {
  background: #b8e9d6;
  color: #07110e;
  border-color: #e0fff3;
}
.cam .cam-slots button:disabled {
  opacity: 1;
  cursor: default;
}

/* The corridor's sector panel (camera.js, lib/signs.js): amber letters on the blank glass above the door, grey
   and soft in the infrared of the dark. */
.cam .cam-signs {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.cam .sign {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  padding: 0 12px;
  box-sizing: border-box;
  color: #ffb000;
  font-family: monospace;
  text-shadow: 0 0 6px #ff9a0099;
  filter: blur(0.4px);
  opacity: 0.85;
  white-space: nowrap;
  overflow: hidden;
}
.cam .sign .main {
  font-size: 30px;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.cam .sign .sub {
  font-size: 18px;
  letter-spacing: 0.12em;
  color: #ffcf66cc;
}
.cam .cam-signs.dark .sign {
  color: #c8c8c8;
  text-shadow: none;
  opacity: 0.55;
}
.cam .cam-signs.dark .sign .sub {
  color: #b0b0b0;
}
