/* ─────────────────────────────────────────────────────────────────────────────
   panels.css — the video panel and its layered canvases

   THE LAYER STACK — the important part of this file
   ─────────────────────────────────────────────────────────
   Three elements are stacked on the same rectangle, back to front:

     <video>           the raw webcam feed          (no z-index, painted first)
     #overlay-canvas   2D landmark lines + dots     z-index 2
     #ar-canvas        WebGL ring (js/ring/ring.js) z-index 3

   Only the video and the 2D overlay are CSS-mirrored with scaleX(-1). The AR
   canvas deliberately is NOT: mirroring a WebGL canvas in CSS would invert the
   mesh's triangle winding order, so back faces would cull as front ones and the
   ring would render inside-out. ring.js mirrors in world space instead, by
   negating x inside lmToVec().

   Sizing: the two canvases are positioned in JS, not here. getVideoRenderRect()
   measures where the video's visible picture actually sits (it is letterboxed
   whenever the panel and camera aspect ratios disagree) and writes explicit
   left/top/width/height. The 100% rules below are only the pre-JS fallback.
───────────────────────────────────────────────────────────────────────────── */

/* ── The bottom bar ─────────────────────────────────────────────────────────
   What is on the hand, and the way back to the configurator. Sits ON the video
   rather than above or below it, so the camera feed keeps the whole panel.

   The gradient, not a solid fill: a bar with a hard edge cuts the frame in two,
   where a gradient reads as a vignette and keeps the hand the subject. It is
   also what guarantees the text stays legible over an unknown camera image. */
.tryon-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 40px 20px 18px;

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;

  background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0));
  z-index: var(--z-panel-ui);
}

/* The bar is over video, so its type is light whatever the page theme does. */
.tryon-bar .summary {
  margin: 0;
  color: rgba(255, 255, 255, 0.92);
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-variant: small-caps;
  letter-spacing: 0.03em;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

/* Translucent rather than the light-page button fill, so it belongs to the
   video it sits on. */
.tryon-bar .btn-secondary {
  width: auto;
  padding: 9px 16px;
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.35);
  color: #fff;
  backdrop-filter: blur(6px);
}

.tryon-bar .btn-secondary:hover {
  background: rgba(255, 255, 255, 0.22);
  border-color: #fff;
  color: #fff;
}

/* On a phone the two stack: side by side, the summary is squeezed to a couple
   of words and the button ends up adrift on the left. Stacked, the line reads
   in full and the button spans the width, which is also where a thumb is. */
@media (max-width: 640px) {
  .tryon-bar {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 34px 14px 14px;
    text-align: center;
  }

  .tryon-bar .btn-secondary {
    width: 100%;
  }

  .panel-actions {
    top: 10px;
    left: 10px;
  }
}

/* ── The webcam ─────────────────────────────────────────────────────────── */
#video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;

  /* 'contain' preserves the camera's native aspect ratio, letterboxing rather
     than stretching. The JS canvas positioning assumes exactly this fit — if it
     changes to 'cover', getVideoRenderRect() must change to match. */
  object-fit: contain;
  object-position: center center;
  background: #000;

  transform: scaleX(-1);   /* selfie mirror, so the hand moves as expected */
}

/* 2D landmark overlay. Mirrored to match the video, so overlay.js can draw with
   raw landmark coordinates and no manual flip. */
#overlay-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  transform: scaleX(-1);
  pointer-events: none;
  z-index: var(--z-overlay-canvas);
}

/* ── The rear camera is not a mirror ────────────────────────────────────────
   Pointed away from you, the picture is already the world as everyone else
   sees it, and flipping it would make the hand move the wrong way. The class is
   set by setMirrored() in controls.js from what the browser ACTUALLY opened;
   lmToVec() and the handedness read in ring.js follow the same flag. All three
   have to agree or the ring lands on the wrong side of the hand. */
.panel.is-unmirrored #video,
.panel.is-unmirrored #overlay-canvas {
  transform: none;
}

/* AR canvas — the 3D ring composited over the video.
   NOT mirrored here; see the winding-order note in the file header. */
#ar-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: var(--z-ar-canvas);
}

/* ── Loading overlay ────────────────────────────────────────────────────── */
/* Covers the panel until the camera is live, and carries the start button.
   Hidden by adding .hidden from js/ui/controls.js — it fades via opacity rather
   than display:none so the transition is visible.

   Dark, not --bg: this sits inside the black camera panel, and a light sheet
   here would flash white before the feed appears. */
.loading-overlay {
  position: absolute;
  inset: 0;
  background: #0d0d0c;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  z-index: var(--z-loading);
  transition: opacity var(--transition-slow);
}

/* pointer-events:none matters as much as the opacity — the element still
   occupies the panel after fading, and would otherwise swallow clicks. */
.loading-overlay.hidden {
  opacity: 0;
  pointer-events: none;
}

/* ── The hand guidance ──────────────────────────────────────────────────────
   Shown while the camera opens, which is exactly when it is useful: the
   customer has a second or two to get their hand into position before the feed
   arrives, and nothing else on screen tells them which way round it goes. It
   leaves with the overlay once tracking starts. */
/* ── The hand guide ─────────────────────────────────────────────────────────
   A TARGET, not an icon: it sits at the size and position the customer's real
   hand should occupy, which is the part the sentence cannot say. "Show your
   hand" gets fingers and a cropped palm; a shape this size gets a whole hand,
   and the tracker needs the whole hand — P13 and P17 are what the orientation
   basis is built from.

   Taken out of the overlay's flex column and positioned against the panel, so
   it sits in the middle of the frame while the prompt stays where it was. The
   hint state pushes that prompt to the bottom precisely to leave this clear.
──────────────────────────────────────────────────────────────────────────── */
.hand-guide {
  /* IN THE FLOW, not absolutely positioned. The prompt has to sit BELOW the
     hand, and the way to guarantee that is to let the column stack them —
     every version of this that positioned the guide freely and then nudged the
     text with an offset ended up with the words across the palm on some panel
     shape or other. Now they cannot overlap by construction. */
  flex: 0 0 70%;
  min-height: 0;
  width: 100%;
  object-fit: contain;

  /* 70% of the frame's height, letterboxed inside the full width — so on a
     laptop it is 70% of the height and on a narrow phone panel the width
     limits it first, and it is never cropped either way. */

  pointer-events: none;
  animation: hand-blink 3.4s ease-in-out infinite;
}

/* Alpha only — nothing moves and nothing shifts the layout. */
@keyframes hand-blink {
  0%, 100% { opacity: 0.22; }
  50%      { opacity: 0.85; }
}

/* Smaller before the camera starts, because that screen also carries the
   prompt, the permission line and the Start button, and 70% plus all of that
   does not fit a short window. It still blinks: this is the screen the customer
   sits on while deciding, so it is where the guide has the most to say. */
.loading-overlay:not(.is-hint) .hand-guide {
  flex-basis: 46%;
}

/* THE BLINK SURVIVES prefers-reduced-motion, and that is a deliberate reversal
   of base.css — the same one VIEWER.respectReducedMotion makes for the
   turntable, for the same reason. base.css collapses every animation to 0.01ms
   with !important, and Windows' Accessibility ▸ Visual effects ▸ Animation
   effects toggle is enough to trigger it, so the guide simply sat still and
   read as a broken image rather than a declined animation.

   Kept because of WHAT this animation is: alpha only, on one element, with
   nothing moving and no layout shift. The reduced-motion setting exists for
   vestibular triggers — movement, parallax, spin — and a slow fade is none of
   those. Anything here that actually moved would be given up without argument.

   Slower and shallower under the setting even so. */
@media (prefers-reduced-motion: reduce) {
  .hand-guide {
    animation: hand-blink 5s ease-in-out infinite !important;
    animation-iteration-count: infinite !important;
  }
}

.hand-prompt {
  max-width: 22ch;
  font-size: var(--text-md);
  line-height: 1.35;
  text-align: center;
  color: #fff;
}

/* In the error state the message is the point, not the pose. */
.loading-overlay.is-error .hand-guide,
.loading-overlay.is-error .hand-prompt {
  display: none;
}

/* ── The hint state ─────────────────────────────────────────────────────────
   Once the camera is live the same overlay stays for a few seconds carrying
   only the hand guidance, now over the picture instead of instead of it. The
   customer needs the instruction exactly when they can act on it, and the
   loading screen disappears at that moment.

   It is the SAME element as the loading screen rather than a second copy of the
   icon and the sentence: one thing to keep in step, and the change from "wait"
   to "do this" is a state, not a new component.

   pointer-events: none matters — it covers the whole panel, and the settings
   drawer and the design bar live underneath. */
.loading-overlay.is-hint {
  background: transparent;
  pointer-events: none;

  /* Centred as a GROUP — the hand and then the prompt under it — where this
     used to push the prompt to the bottom with padding to keep it off a
     free-floating guide. The guide is a flex item now, so the column does that
     on its own and there is no offset left to get wrong.

     Worth remembering if an offset is ever needed here again: percentage
     padding resolves against the containing block's WIDTH even on
     padding-bottom, so a 30% value moved the prompt by 30% of a phone's 420px width
     rather than of its height and barely shifted it. dvh was the fix. */
  padding-bottom: 6dvh;
}

.loading-overlay.is-hint .loader-ring,
.loading-overlay.is-hint .load-text,
.loading-overlay.is-hint .btn {
  display: none;
}

/* Legible over an unknown camera image, without a panel to sit in. */
.loading-overlay.is-hint .hand-prompt {
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.85);
}

/* ── Actions on the video ───────────────────────────────────────────────────
   Top-left, over the feed. Left rather than right because the header's gear is
   already top-right and the two clusters would read as one crowded group; and
   over the video rather than in the bottom bar because these act on the picture
   itself, not on the design underneath it. */
.panel-actions {
  position: absolute;
  top: 12px;
  left: 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: var(--z-panel-ui);
}

/* The icon button from layout.css, re-toned for a dark, unknown background. */
.icon-btn--onvideo {
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  backdrop-filter: blur(6px);
}

.icon-btn--onvideo:hover {
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
}

.icon-btn--onvideo[hidden] {
  display: none;
}

/* The spinner and its caption are shared (components.css). Here they sit on a
   near-black sheet rather than on the light page, so both are re-toned. */
.loading-overlay .loader-ring {
  border-color: rgba(255, 255, 255, 0.18);
  border-top-color: var(--accent);
}

.loading-overlay .load-text {
  color: rgba(255, 255, 255, 0.65);
}

/* The overlay is a column, so the button would otherwise stretch across the
   whole panel.

   And it is INVERTED here: --btn-primary is near-black ink, which is the right
   weight on the light configurator rail and all but invisible on this dark
   sheet. Same component, opposite ground. */
.loading-overlay .btn {
  width: auto;
  min-width: 200px;
}

.loading-overlay .btn-primary {
  background: #fff;
  color: var(--ink);
}

.loading-overlay .btn-primary:hover:not(:disabled) {
  background: var(--accent);
  color: #fff;
}

/* Arriving from "Try it on": the camera is already being requested, so there is
   nothing here to press. Hidden rather than disabled — a greyed-out button next
   to a spinner reads as something the customer failed to do. */
.loading-overlay.is-starting .btn {
  display: none;
}

.loading-overlay .btn-primary:disabled {
  background: rgba(255, 255, 255, 0.35);
  color: rgba(255, 255, 255, 0.7);
}
