/* ================================================================
 * Preview mode — a fullscreen rendering of the user's shader on the
 * dossier-style character card. All card selectors are scoped to
 * `.preview-root` so they don't leak into the editor's styles.
 *
 * Most of the visual rules are ported from index.html's card; the only
 * JS change from that original is that the face shader source comes from
 * the live graph compiler instead of being hard-coded.
 * ================================================================ */

/* @property declarations MUST live at global scope — they register the
   animated custom properties with the CSS typed-OM so transitions work. */
@property --angle { syntax:'<angle>';  initial-value:0deg;  inherits:false; }
@property --hue   { syntax:'<angle>';  initial-value:0deg;  inherits:false; }
@property --flip  { syntax:'<angle>';  initial-value:0deg;  inherits:true; }
@property --lift   { syntax:'<length>'; initial-value:0px;  inherits:true; }
@property --lift-x { syntax:'<length>'; initial-value:0px;  inherits:true; }
@property --lift-y { syntax:'<length>'; initial-value:0px;  inherits:true; }

/* ---- fade-to-black transition overlay ---- */
.fade-layer{
  position:fixed;
  inset:0;
  background:#000;
  z-index:1000;
  opacity:0;
  pointer-events:none;
  transition:opacity .5s ease;
}
.fade-layer.visible{
  opacity:1;
  pointer-events:auto;
}

/* ---- hide editor chrome while preview is active ----
   `.save-video-modal` and `#saveVideoBack` are exempt: the preview reuses
   the editor's recording modal. The toast is also kept visible so the
   "saved … video" confirmation surfaces after a preview recording. */
body.preview-mode #bgShader,
body.preview-mode .chrome,
body.preview-mode .modal,
body.preview-mode .save-fab,
body.preview-mode .ctx,
body.preview-mode .picker:not(.save-video-modal),
body.preview-mode .picker-backdrop:not(#saveVideoBack){
  display:none !important;
}
/* lift the modal + backdrop above the preview-root (z-index:900) and the
   fade-layer (z-index:1000) so the modal sits on top while preview is up */
body.preview-mode .picker.save-video-modal{ z-index:1010; }
body.preview-mode #saveVideoBack{ z-index:1005; }
body.preview-mode .toast{ z-index:1015; }

/* ---- preview root ---- */
.preview-root{
  position:fixed;
  inset:0;
  display:none;
  z-index:900;
  background:radial-gradient(1200px 800px at 50% 55%, #14141b 0%, #0a0a0e 55%, #050507 100%);
  color:var(--ink);
  overflow:hidden;
  /* preview-local defaults for the animated CSS vars; JS overwrites these
     each frame with the smoothed tilt/lift values */
  --rx:0deg; --ry:0deg; --tx:0px; --ty:0px;
  --flip:0deg; --lift:0px; --lift-x:0px; --lift-y:0px;
  --shine-top:.35; --shine-right:.35; --shine-bottom:.35; --shine-left:.35;
  --sx:50%; --sy:50%;
  --shade-top-a:0; --shade-bot-a:0; --shade-left-a:0; --shade-right-a:0;
}
.preview-root.active{ display:block; }

/* grain overlay so the card feels cinematic */
.preview-root::after{
  content:"";
  position:fixed; inset:0;
  pointer-events:none;
  opacity:.05;
  mix-blend-mode:overlay;
  z-index:20;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 .6 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* top-left/right editorial text during preview */
.preview-chrome{
  position:fixed; inset:0;
  pointer-events:none;
  padding:22px 28px;
  display:grid;
  grid-template-columns:1fr 1fr;
  grid-template-rows:auto 1fr auto;
  font-size:10.5px;
  letter-spacing:.24em;
  text-transform:uppercase;
  color:var(--ink-faint);
  z-index:10;
}
.preview-chrome strong{ color:var(--ink); font-weight:500; }
.preview-chrome .pc-tl{ grid-column:1; grid-row:1; }
.preview-chrome .pc-tr{ grid-column:2; grid-row:1; text-align:right; }

/* ---- centered stage ---- */
.preview-root .arena{
  position:absolute; inset:0;
  display:grid;
  place-items:center;
  perspective:1800px;
  perspective-origin:50% 50%;
}
.preview-root .stage{
  position:relative;
  width:min(420px, 86vw);
  aspect-ratio: 5 / 7.2;
}

/* ---- pastel aura ----
   Blur radii were 34 px / 14 px; the filter's per-frame cost scales roughly
   with radius², so dropping to 16 / 8 is ≈4× / 3× cheaper for a visually
   very similar glow. The trailing `translateZ(0)` promotes the glow to its
   own compositor layer so its filter re-rasterization doesn't invalidate
   the shared document composite every frame. */
.preview-root .glow{
  position:absolute;
  inset:-7%;
  border-radius:42px;
  background:conic-gradient(from var(--angle),
    #ffc4d6, #ffd9b8, #fff0b8, #c9ffd4, #b8e1ff, #d8c4ff, #ffc4d6);
  filter:blur(16px) saturate(1.15) hue-rotate(var(--hue));
  opacity:.82;
  animation:previewSpin 9.3s linear infinite, previewHueshift 14.7s ease-in-out infinite;
  z-index:0;
  pointer-events:none;
  transform:rotateX(var(--rx)) rotateY(var(--ry)) translateZ(0);
  will-change:transform, filter;
}
.preview-root .glow::after{
  content:"";
  position:absolute;
  inset:4%;
  border-radius:34px;
  background:conic-gradient(from calc(var(--angle) + 180deg),
    #c9ffd4, #b8e1ff, #d8c4ff, #ffc4d6, #ffd9b8, #fff0b8, #c9ffd4);
  filter:blur(8px) saturate(1.25) hue-rotate(calc(var(--hue) * -0.7));
  opacity:.55;
  mix-blend-mode:screen;
}
@keyframes previewSpin     { to { --angle: 360deg; } }
@keyframes previewHueshift { 0%, 100% { --hue: -28deg; } 50% { --hue: 36deg; } }

/* ---- contact shadow ---- */
.preview-root .card-shadow{
  position:absolute; inset:-4px;
  border-radius:30px;
  z-index:1;
  pointer-events:none;
  background:rgba(0,0,0,.85);
  filter:blur(28px);
  transform:translate3d(var(--tx), var(--ty), 0)
            rotateX(var(--rx))
            rotateY(calc(var(--ry) + var(--flip)));
  opacity:1;
  will-change:transform, opacity;
}

/* ---- card itself ---- */
.preview-root .card{
  position:relative; z-index:2;
  width:100%; height:100%;
  transform-style:preserve-3d;
  transform:
    translate3d(
      calc(var(--tx) + var(--lift-x)),
      calc(var(--ty) + var(--lift-y)),
      var(--lift))
    rotateX(var(--rx))
    rotateY(calc(var(--ry) + var(--flip)));
  will-change:transform;
}

.preview-root .face{
  position:absolute; inset:0;
  border-radius:26px;
  overflow:hidden;
  background:#000;
  border:1px solid rgba(255,255,255,.12);
  box-shadow:
    0 38px 80px -28px rgba(0,0,0,.75),
    0 4px 18px -6px rgba(0,0,0,.55),
    inset  0  1.5px 0 rgba(255,255,255, calc(var(--shine-top)    * 0.55)),
    inset  0 -1.5px 0 rgba(255,255,255, calc(var(--shine-bottom) * 0.55)),
    inset  1.5px 0 0 rgba(255,255,255, calc(var(--shine-left)    * 0.55)),
    inset -1.5px 0 0 rgba(255,255,255, calc(var(--shine-right)   * 0.55)),
    inset  0  5px 7px -3px rgba(0,0,0, calc((1.0 - var(--shine-top))    * 0.60)),
    inset  0 -5px 7px -3px rgba(0,0,0, calc((1.0 - var(--shine-bottom)) * 0.60)),
    inset  5px 0 7px -3px rgba(0,0,0, calc((1.0 - var(--shine-left))    * 0.60)),
    inset -5px 0 7px -3px rgba(0,0,0, calc((1.0 - var(--shine-right))   * 0.60));
}
.preview-root .face > .shader-canvas{
  position:absolute; inset:0;
  width:100%; height:100%;
  display:block;
  z-index:1;
}

.preview-root .face > .sss{
  position:absolute; inset:0;
  z-index:1;
  pointer-events:none;
  background:radial-gradient(130% 160% at var(--sx) var(--sy),
    rgba(255, 200, 168, .50) 0%,
    rgba(255, 168, 190, .32) 22%,
    rgba(255, 198, 170, .14) 48%,
    transparent 72%);
  mix-blend-mode:screen;
  opacity:.9;
  mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.011' numOctaves='2' seed='5' stitchTiles='stitch'/></filter><rect width='400' height='400' filter='url(%23n)'/></svg>");
  mask-mode:luminance;
  mask-size:100% 100%;
  mask-repeat:no-repeat;
  -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.011' numOctaves='2' seed='5' stitchTiles='stitch'/></filter><rect width='400' height='400' filter='url(%23n)'/></svg>");
  -webkit-mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;
}

.preview-root .face::after{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(140deg,
    rgba(255,255,255,.10) 0%,
    rgba(255,255,255,.04) 42%,
    rgba(255,255,255,.01) 100%);
  pointer-events:none;
  z-index:2;
}

.preview-root .face::before{
  content:"";
  position:absolute; inset:0;
  pointer-events:none;
  background:
    linear-gradient(to bottom, rgba(0,0,0, var(--shade-top-a))   0%, transparent 55%),
    linear-gradient(to top,    rgba(0,0,0, var(--shade-bot-a))   0%, transparent 55%),
    linear-gradient(to right,  rgba(0,0,0, var(--shade-left-a))  0%, transparent 55%),
    linear-gradient(to left,   rgba(0,0,0, var(--shade-right-a)) 0%, transparent 55%);
  z-index:3;
}

.preview-root .sheen{
  position:absolute; inset:0;
  border-radius:26px;
  pointer-events:none;
  background:radial-gradient(110% 130% at var(--sx) var(--sy),
    rgba(255,255,255,.18) 0%,
    rgba(255,255,255,.09) 20%,
    rgba(255,255,255,.03) 45%,
    transparent 72%);
  mix-blend-mode:screen;
  z-index:4;
}

.preview-root .rim-svg{
  position:absolute;
  inset:0;
  width:100%; height:100%;
  pointer-events:none;
  overflow:visible;
  z-index:6;
}

/* ---- content column ---- */
.preview-root .content{
  position:relative;
  height:100%;
  padding:26px 28px 22px;
  display:flex;
  flex-direction:column;
  gap:14px;
  z-index:3;
  transform-style:preserve-3d;
  backface-visibility:hidden;
  -webkit-backface-visibility:hidden;
}
.preview-root .content > *{ backface-visibility:hidden; -webkit-backface-visibility:hidden; }
.preview-root .content > .row:first-of-type{ transform:translateZ(6px); }
.preview-root .content > .avatar-wrap       { transform:translateZ(20px); }
.preview-root .content > .name              { transform:translateZ(14px); }
.preview-root .content > .tagline           { transform:translateZ(10px); }
.preview-root .content > .divider           { transform:translateZ(4px); }
.preview-root .content > .bio               { transform:translateZ(8px); }
.preview-root .content > .meta-row          { transform:translateZ(4px); }

.preview-root .row{
  display:flex;
  align-items:center;
  justify-content:space-between;
  font-size:9.5px;
  letter-spacing:.28em;
  text-transform:uppercase;
  color:var(--ink-faint);
}
.preview-root .row .dot{
  display:inline-block;
  width:5px; height:5px; border-radius:50%;
  background:#9cff9a;
  margin-right:7px; vertical-align:middle;
  box-shadow:0 0 10px #9cff9a;
  animation:previewDotPulse 2.6s ease-in-out infinite;
}
@keyframes previewDotPulse{
  0%,100%{ opacity:1;   box-shadow:0 0 10px #9cff9a; }
  50%    { opacity:.25; box-shadow:0 0 2px rgba(156,255,154,.4); }
}

.preview-root .avatar-wrap{
  position:relative;
  width:118px; height:118px;
  align-self:center;
  margin:2px 0 2px;
}
.preview-root .avatar-ring{
  position:absolute; inset:-6px;
  border-radius:50%;
  background:conic-gradient(from 90deg, #ffd68a, #ff9ec1, #b8a5ff, #8fd6ff, #a3f1c3, #ffd68a);
  filter:blur(10px);
  opacity:.55;
  pointer-events:none;
}
.preview-root .avatar{
  position:relative;
  width:100%; height:100%;
  display:block;
  border-radius:50%;
  object-fit:cover;
  background:linear-gradient(160deg,#1c1c24,#0c0c11);
  border:1px solid rgba(255,255,255,.22);
  box-shadow:
    0 12px 40px -10px rgba(0,0,0,.7),
    inset 0 0 0 1px rgba(255,255,255,.06),
    inset 0 -10px 28px rgba(0,0,0,.35);
}

.preview-root .name{
  font-family:'Fraunces', serif;
  font-weight:400;
  font-size:34px;
  line-height:1;
  text-align:center;
  letter-spacing:-.01em;
  font-variation-settings:"opsz" 144;
  margin:6px 0 0;
}
.preview-root .tagline{
  font-family:'Fraunces', serif;
  font-style:italic;
  font-weight:300;
  font-size:16.5px;
  text-align:center;
  color:var(--ink-dim);
  margin:0;
}
.preview-root .divider{
  height:1px;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.22), transparent);
  margin:2px 0;
}
.preview-root .bio{
  font-size:16px;
  line-height:1.58;
  color:rgba(255,255,255,.8);
  font-weight:300;
  flex:1;
  overflow:hidden;
  padding-right:2px;
}
.preview-root .meta-row{
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  padding-top:6px;
  border-top:1px solid var(--hairline);
  font-size:9px;
  letter-spacing:.32em;
  text-transform:uppercase;
  color:var(--ink-faint);
}
.preview-root .meta-row .serial{
  font-family:'Fraunces', serif;
  font-style:italic;
  text-transform:none;
  letter-spacing:.03em;
  font-size:10.5px;
  color:rgba(255,255,255,.55);
}

/* ---- back-to-editor button ---- */
.preview-back-btn{
  position:fixed;
  left:28px; bottom:28px;
  z-index:950;
  padding:11px 18px;
  font-family:inherit;
  font-size:10.5px;
  letter-spacing:.26em;
  text-transform:uppercase;
  color:var(--ink);
  background:
    linear-gradient(135deg, rgba(255,255,255,.12), rgba(255,255,255,.04)),
    rgba(22,22,28,.55);
  border:1px solid rgba(255,255,255,.2);
  border-radius:999px;
  cursor:pointer;
  backdrop-filter:blur(18px) saturate(1.4);
  -webkit-backdrop-filter:blur(18px) saturate(1.4);
  box-shadow:
    0 14px 34px -14px rgba(0,0,0,.7),
    inset 0 1px 0 rgba(255,255,255,.24);
  display:inline-flex;
  align-items:center;
  gap:8px;
  transition:transform .2s, box-shadow .2s, background .2s;
}
.preview-back-btn:hover{
  transform:translateY(-1px);
  background:
    linear-gradient(135deg, rgba(255,255,255,.18), rgba(255,255,255,.06)),
    rgba(28,28,36,.6);
  box-shadow:
    0 20px 40px -14px rgba(0,0,0,.8),
    inset 0 1px 0 rgba(255,255,255,.32);
}
.preview-back-btn:active{ transform:scale(.98); }

/* ---- preview-only fab group (Save Video + Hide) ----
   Mirrors the editor's `.fab-group` placement at bottom-right. Lives inside
   `.preview-root` so the buttons only appear while preview mode is active. */
.preview-root .preview-fab-group{
  position:fixed;
  right:clamp(12px, 1.4vw, 28px);
  bottom:clamp(12px, 1.4vh, 28px);
  z-index:950;
  display:flex;
  gap:clamp(6px, 0.6vw, 12px);
  align-items:center;
  flex-wrap:wrap;
  justify-content:flex-end;
  max-width:calc(100vw - 24px);
}
.preview-root .preview-fab{
  padding:clamp(8px, 0.85vh, 14px) clamp(12px, 1.3vw, 24px);
  font-family:inherit;
  font-size:clamp(9px, 0.7vw, 12px);
  letter-spacing:.26em;
  text-transform:uppercase;
  color:var(--ink);
  background:
    linear-gradient(135deg, rgba(255,255,255,.12), rgba(255,255,255,.04)),
    rgba(22,22,28,.55);
  border:1px solid rgba(255,255,255,.2);
  border-radius:999px;
  cursor:pointer;
  backdrop-filter:blur(18px) saturate(1.4);
  -webkit-backdrop-filter:blur(18px) saturate(1.4);
  box-shadow:
    0 14px 34px -14px rgba(0,0,0,.7),
    inset 0 1px 0 rgba(255,255,255,.24);
  display:inline-flex;
  align-items:center;
  gap:clamp(6px, 0.6vw, 12px);
  transition:transform .2s, box-shadow .2s, background .2s;
}
.preview-root .preview-fab:hover{
  transform:translateY(-1px);
  background:
    linear-gradient(135deg, rgba(255,255,255,.18), rgba(255,255,255,.06)),
    rgba(28,28,36,.6);
  box-shadow:
    0 20px 40px -14px rgba(0,0,0,.8),
    inset 0 1px 0 rgba(255,255,255,.32);
}
.preview-root .preview-fab:active{ transform:scale(.98); }
.preview-root .preview-fab svg{
  width:clamp(11px, 0.95vw, 16px);
  height:clamp(11px, 0.95vw, 16px);
}
/* recording-state glow — same red treatment as `.save-fab.recording` */
.preview-root .preview-fab.recording{
  border-color:rgba(255,100,100,.85);
  color:#ffc4c4;
  box-shadow:0 0 0 1px rgba(255,100,100,.4) inset, 0 0 22px rgba(255,80,80,.35);
  animation:save-rec-pulse 1.5s ease-in-out infinite;
}

/* When the toggle is on, hide the card's text + avatar layer so only the
   shader face shows through. */
.preview-root.content-hidden .card .content{ display:none; }
