/* ================================================================
   SETC&amp;S ControlRoom — Cinematic Layer
   New visual primitives (3D depth, custom cursor, signature page
   transitions, grain texture, magnetic buttons, WebGL ambient mesh
   canvas) layered ON TOP of theme.css's existing tokens — nothing
   here replaces the base design system, it extends it. Auto-injected
   by api.js on any page carrying a .theme-toggle (the same signal
   already used to exclude public/print/kiosk pages from the
   notification bell), so public forms never load this file at all.
   Every animation here follows theme.css's own convention: fully
   inert under prefers-reduced-motion, never a functional requirement.
   ================================================================ */

:root {
  --cine-persp:     1200px;
  /* How far a card rotates as the pointer moves across it. This was 10deg,
     which on a large card (the Pending Across All Sites read-out especially)
     swings the whole panel enough to be distracting while you are trying to
     read a table inside it. Kept as a whisper of depth, not a motion effect —
     the shadow and border shift are what actually signal "hovered". */
  --cine-tilt-max:  2.5deg;
  /* How far a card's edge may travel under the cursor, in pixels. The tilt
     angle is derived per card from this so a large panel and a small tile
     move by the same visible amount — see mountCardTilt in cinematic.js. */
  --cine-tilt-travel: 5;
  --cine-wipe-t:    560ms;
  --cine-cursor-t:  180ms;

  /* Per-tenant tint — PT.applyBranding() in api.js overwrites these three
     from company.primaryColor at login; default here is the base copper
     accent so the cinematic layer looks correct even before branding
     loads (first paint, or a session with no company yet). */
  --brand-tint:     var(--accent);
  --brand-tint-rgb: 222,152,98;
  --brand-tint-glow:var(--accent-glow);
}
@media (prefers-reduced-motion: reduce) {
  :root { --cine-wipe-t: 1ms; --cine-cursor-t: 1ms; }
}

/* ── WebGL ambient mesh canvas ─────────────────────────────────
   Sits behind everything, above the plain background colour. Only
   created when PT.mountAmbientMesh() succeeds (WebGL available, motion
   not reduced) — otherwise api.js's existing #pt-ambient-canvas 2D
   fallback keeps running untouched, so there's always something. */
#pt-cine-mesh {
  /* z-index MUST be 0 or higher, never negative. Confirmed empirically on
     the deployed site (screenshots + pixel reads, bisected live): with
     z-index:-1 or -2 this canvas rendered nothing visible at all, while
     0 made it render correctly, with -1 in between confirmed still
     invisible — the boundary is exactly between -1 and 0. Every normal
     in-flow element on the page (every <section>, all z-index:auto)
     stacks at the "0" level, which sits ABOVE anything with a negative
     z-index regardless of DOM order — so any negative value here loses
     to literally all page content, painting fully hidden behind it. 0
     is safe: DOM order (this canvas is inserted as body's first child,
     before every section) still puts it behind all real content at the
     same stacking level, and pointer-events:none below means it can
     never block clicks either way. */
  /* width/height:100% needed alongside inset:0 — a <canvas> is a replaced
     element, and replaced elements don't stretch to fill via inset alone;
     without this the canvas falls back to its pixel-ratio-scaled width/
     height attributes as its intrinsic CSS size, overflowing the viewport
     on any device pixel ratio above 1 (confirmed live at 375px mobile
     width: canvas rendered at 562px CSS width, exactly attrWidth/dpr). */
  position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none;
  opacity: .85;
}

/* ── Custom cursor (desktop/mouse only — never touch devices) ─── */
body.pt-cursor-active,
body.pt-cursor-active a, body.pt-cursor-active button,
body.pt-cursor-active .btn, body.pt-cursor-active input,
body.pt-cursor-active textarea, body.pt-cursor-active select {
  cursor: none;
}
.pt-cursor-dot, .pt-cursor-ring {
  /* Above EVERYTHING. The custom cursor replaces the real one (body gets
     cursor:none), so anything painted over it leaves the person with no
     pointer at all — which is what happened over the guided tour's cards
     at z-index 10050. It takes no pointer events, so sitting on top costs
     nothing; any overlay added later must still stay below this. */
  position: fixed; top: 0; left: 0; pointer-events: none; z-index: 2147483000;
  border-radius: 50%; transform: translate3d(-50%,-50%,0);
  /* Promote both to their own compositor layer. Without this the browser
     re-rasterises them against the page on every move, which is what
     makes a custom cursor feel heavier than the real OS one. */
  will-change: transform;
}
/* NO transform transition on the dot. The dot IS the pointer (the real
   system cursor is hidden via `cursor:none` above), so any easing on its
   position is felt directly as input lag — a `transform 90ms linear`
   here meant the dot was always ~90ms behind the actual pointer, which
   is precisely why the app's cursor felt less responsive than the
   system's. Only the ring, which is decorative, is allowed to trail. */
.pt-cursor-dot {
  width: 6px; height: 6px; background: var(--cursor-tint, var(--brand-tint));
  transition: background var(--cine-cursor-t) ease;
}
.pt-cursor-ring {
  width: 34px; height: 34px; border: 1px solid rgba(var(--cursor-tint-rgb, var(--brand-tint-rgb)),.5);
  transition: width var(--t-med) var(--ease-settle), height var(--t-med) var(--ease-settle),
              border-color var(--cine-cursor-t) ease, background var(--cine-cursor-t) ease;
}
.pt-cursor-ring.pt-cursor-hover {
  width: 56px; height: 56px; border-color: var(--cursor-tint, var(--brand-tint));
  background: rgba(var(--cursor-tint-rgb, var(--brand-tint-rgb)),.10);
}
@media (prefers-reduced-motion: reduce), (pointer: coarse) {
  .pt-cursor-dot, .pt-cursor-ring { display: none !important; }
  body.pt-cursor-active,
  body.pt-cursor-active a, body.pt-cursor-active button,
  body.pt-cursor-active .btn { cursor: auto; }
}

/* ── 3D card tilt + cursor light-sweep ─────────────────────────
   Apply .pt-tilt to a card's container and .pt-tilt-inner to the
   element that should actually rotate — cinematic.js applies these
   classes automatically to .card/.instrument-panel/.app-card, no
   markup changes needed on existing pages. */
.pt-tilt { perspective: var(--cine-persp); }
.pt-tilt-inner {
  transform-style: preserve-3d;
  transition: transform var(--t-med) var(--ease-settle);
  will-change: transform;
}
/* Set by cinematic.js on a card bigger than the viewport — the unit register
   on a site with thousands of extinguishers. It does not rotate (see the
   note in mountCardTilt), so promoting it to its own compositor layer buys
   nothing and costs memory in proportion to its size. */
.pt-tilt-inner.pt-tilt-static { will-change: auto; transition: none; }
.pt-tilt-inner::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(240px circle at var(--mx,50%) var(--my,50%),
              rgba(var(--brand-tint-rgb),.24), transparent 60%);
  opacity: 0; transition: opacity var(--t-fast) var(--ease-engineered);
  pointer-events: none;
}
.pt-tilt-inner.pt-tilt-hover::after { opacity: 1; }
@media (prefers-reduced-motion: reduce), (pointer: coarse) {
  .pt-tilt-inner { transform: none !important; transition: none; }
  .pt-tilt-inner::after { display: none; }
}

/* ── Film-grain texture — subtle, cheap, animated by toggling
   background-position rather than repainting ─────────────────── */
.pt-grain {
  position: fixed; inset: -50px; z-index: 9997; pointer-events: none;
  opacity: .035; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 180px 180px;
  animation: pt-grain-shift 1.4s steps(4) infinite;
}
@keyframes pt-grain-shift {
  0%   { transform: translate(0,0); }
  25%  { transform: translate(-2%,1%); }
  50%  { transform: translate(1%,-2%); }
  75%  { transform: translate(-1%,-1%); }
  100% { transform: translate(0,0); }
}
@media (prefers-reduced-motion: reduce) { .pt-grain { display: none; } }

/* ── Magnetic button pull ───────────────────────────────────────
   cinematic.js applies a live transform on mousemove within a radius;
   this just sets up the settle-back transition for when the cursor
   leaves the radius. */
.pt-magnetic {
  display: inline-block; will-change: transform;
  transition: transform var(--t-med) var(--ease-settle);
}
@media (prefers-reduced-motion: reduce), (pointer: coarse) {
  .pt-magnetic { transition: none !important; transform: none !important; }
}

/* ── Count-up numerals — no motion of their own, just a hook class
   cinematic.js targets to animate textContent on scroll-into-view ── */
.pt-countup { font-variant-numeric: tabular-nums; }

/* ── Scroll-reveal base state — cinematic.js adds/removes
   .pt-reveal-in via IntersectionObserver; theme.css's existing
   .stagger-in/cardRise stays untouched for load-time entrances, this
   is the separate scroll-into-view mechanism ─────────────────── */
.pt-reveal {
  opacity: 0; transform: translateY(18px);
  transition: opacity var(--t-slow) var(--ease-engineered),
              transform var(--t-slow) var(--ease-engineered);
}
.pt-reveal.pt-reveal-in { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .pt-reveal { opacity: 1; transform: none; transition: none; }
}
