* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden;
  font-family: 'JetBrains Mono', monospace;
}

/* Desk background — fills viewport. perspective makes translateZ on direct
   children look like they're rising out of the surface toward the viewer.
   No `transform-style: preserve-3d` here — that would 3D-sort siblings (canvas
   vs receipt) instead of honoring z-index, and would send the receipt behind
   the desk when the drop's bounce overshoots Z=0. */
.desk {
  position: relative;
  width: 100%;
  height: 100%;
  background: url('assets/deskbg.png') center/cover no-repeat;
  background-color: #8b7355;
  display: flex;
  justify-content: center;
  align-items: center;
  perspective: 1200px;
}

/* Cutting mat — centered, 18px radius, rotated -2deg */
.mat {
  width: 89%;
  max-width: 1546px;
  border-radius: 18px;
  transform: rotate(-2deg);
  box-shadow: 3px 4px 2.3px rgba(0, 0, 0, 0.44);
  display: block;
}

/* ── Decorative desk props (Figma frame 156:21, 1728×1117). Sizes are vw so
   they scale with the mat (which is 89% viewport width). MacBook bleeds off the
   top-right; Walkman + headphones sit on the right edge. ── */
.prop {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  user-select: none;
}
.prop-macbook {           /* Figma x1254 y-3 w478 → flush top-right */
  top: -0.3%;
  right: -0.2%;
  width: 27.7vw;
}
.prop-walkman {           /* Figma x1307 y459 w423 → right edge, ~41% down */
  top: 41%;
  right: 0;
  width: 24.5vw;
}
.prop-pencil {            /* Figma x21 y696 w414 → bottom-left, static both modes */
  left: 1.2%;
  top: 62.3%;
  width: 24vw;
}

/* Lamp — clickable toggle for light/dark ambiance. z-index above the overlay
   so the lit lamp shows in dark mode and stays clickable. */
.prop-lamp {              /* Figma x-98 y-61 w376 → top-left, bleeds off corner */
  left: -5.7%;
  top: -5.5%;
  width: 21.8vw;
  z-index: 60;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  pointer-events: auto;
}
.prop-lamp img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
}

/* ── Dark-mode spotlight overlay — fades in when the lamp is ON. Above the
   scene (keyboard z3 / receipt z2) but pointer-events:none so Cancel/Fax It
   stay clickable; below toast (100) and the debug panel (1000). ── */
.ambiance {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  opacity: 0;
  transition: opacity 450ms ease;
}
body.dark-mode .ambiance {
  opacity: 1;
}
.ambiance .darkarea {     /* full-scene dark vignette (Figma covers + bleeds past frame) */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ambiance .lightarea {    /* warm blurred light pool (Figma x178 y126 w1630) */
  position: absolute;
  left: 10.3%;
  top: 11.3%;
  width: 94.4%;
  height: auto;
}

/* ── Receipt — 414×808 Figma frame, pinned 58px from viewport top ── */
.receipt {
  position: absolute;
  width: 414px;
  height: 808px;
  left: 50%;
  top: 58px;
  /* translateZ(--lift) drives the rise-toward-camera sequence (top-down view).
     Default 0 = resting flat on desk; positive Z = closer to viewer. */
  transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(var(--receipt-scale, 0.85));
  transform-origin: top center;
  transition:
    transform  var(--lift-duration, 600ms) var(--lift-easing, cubic-bezier(0.22, 1, 0.36, 1)),
    box-shadow var(--lift-duration, 600ms) ease-out;
  background: #ebebeb;
  box-shadow: 4px 5px 2.5px rgba(0, 0, 0, 0.27);
  font-family: 'JetBrains Mono', monospace;
  color: #000;
  z-index: 2;
}

/* Lift state classes applied to .desk drive the receipt + canvas together.
   --lift-height is a positive Z-distance toward the camera. */
.desk.lifting  { --lift: var(--lift-height, 60px); }
.desk.dropping { --lift: 0px; }

/* rippleSlot wraps the dynamically-mounted canvas:
   - preserve-3d: lets the canvas's translateZ propagate up to .desk's perspective
     context (otherwise the grandchild canvas is 2D-flattened and translateZ has no
     visual effect — canvas stays small while receipt grows from perspective)
   - full-cover absolute overlay: preserve-3d also makes the slot a containing
     block for its abs-positioned canvas, so the slot must span .desk fully or
     the canvas's `top:58px / left:50%` resolves to the wrong spot
   - z-index: 2 + DOM order ensures the slot stacks above the receipt
   - pointer-events: none so the slot doesn't block clicks underneath */
#rippleSlot {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  transform-style: preserve-3d;
}
.desk.lifting .receipt {
  box-shadow: 6px 30px 32px rgba(0, 0, 0, 0.4);
}

@media (max-height: 900px) {
  .receipt { --receipt-scale: 0.78; }
}
@media (max-height: 800px) {
  .receipt { --receipt-scale: 0.70; }
}
@media (max-height: 720px) {
  .receipt { --receipt-scale: 0.62; }
}

.masthead {
  position: absolute;
  left: 99px;
  top: 53px;
  width: 214.354px;
  height: 56.463px;
}

.tagline {
  position: absolute;
  top: 120.29px;
  left: 50%;
  transform: translateX(-50%);
  font-family: 'Adelphe Germinal', 'Adelphe', Georgia, serif;
  font-size: 13.082px;
  letter-spacing: 1.3082px;
  text-transform: uppercase;
  white-space: nowrap;
  line-height: 1.17;
}

/* Dashed dividers */
.divider {
  position: absolute;
  height: 0;
  border-top: 1.5px dashed #000;
}
.divider-long {
  left: 17px;
  width: 380px;
}
.divider-short {
  left: 86px;
  width: 242px;
}

/* Date + time row — 17px line-box matches Figma h-[17px] so baselines align */
.meta {
  position: absolute;
  top: 169px;
  height: 17px;
  font-size: 12px;
  font-family: 'JetBrains Mono', monospace;
  line-height: 17px;
}
.meta-date { left: 12px; }
.meta-time { right: 17px; }

/* Sender row — top:197 matches Figma's updated baseline; line-height:17 keeps 12px + 15px text on shared baseline */
.sender-label {
  position: absolute;
  top: 197px;
  left: 13px;
  height: 17px;
  font-size: 12px;
  line-height: 17px;
}
.sender-input {
  position: absolute;
  top: 197px;
  left: 70px;
  width: 220px;
  height: 17px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 15px;
  letter-spacing: -1.2px;
  line-height: 17px;
  border: none;
  background: transparent;
  outline: none;
  color: #000;
  padding: 0;
  vertical-align: baseline;
}
.sender-input::placeholder {
  color: #000;
  opacity: 0.16;
  transition: opacity 500ms ease-out;
}
.receipt.placeholders-hidden .sender-input::placeholder {
  opacity: 0;
  transition: none;
}
.type-label {
  position: absolute;
  top: 197px;
  right: 17px;
  height: 17px;
  font-size: 12px;
  line-height: 17px;
}

/* NEW MESSAGE / MESSAGE END headings */
.heading-new {
  position: absolute;
  left: 128px;
  top: 229px;
  width: 151px;
  height: 23px;
}
.heading-end {
  position: absolute;
  left: 131px;
  top: 474px;
  width: 146px;
  height: 23px;
}

/* Message body */
.message-input {
  position: absolute;
  top: 271px;
  left: 17px;
  width: 380px;
  height: 190px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 15px;
  letter-spacing: -1.2px;
  line-height: 1.3;
  border: none;
  background: transparent;
  outline: none;
  resize: none;
  color: #000;
  padding: 0;
}
.message-input::placeholder {
  color: #000;
  opacity: 0.16;
  transition: opacity 500ms ease-out;
}
.receipt.placeholders-hidden .message-input::placeholder {
  opacity: 0;
  transition: none;
}

.char-counter {
  position: absolute;
  top: 448px;
  right: 17px;
  font-size: 10px;
  font-family: 'JetBrains Mono', monospace;
  color: #888;
  line-height: 1;
}
.char-counter.warning {
  color: #ff8c00;
}

/* Signoff + footer */
.signoff {
  position: absolute;
  top: 561px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 15px;
  letter-spacing: -1.2px;
  white-space: nowrap;
  line-height: 1;
}

.ascii-footer {
  position: absolute;
  top: 624px;
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  font-family: 'JetBrains Mono', monospace;
  font-size: 9.905px;
  line-height: 1.46;
  white-space: pre;
  text-align: left;
  margin: 0;
}

/* ── Keyboard — 381×174 vector base (native SVG size) with two keys, pinned to
   viewport bottom and scaled via --receipt-scale. ── */
.keyboard {
  position: absolute;
  width: 381px;
  height: 174px;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%) scale(var(--receipt-scale, 0.85));
  transform-origin: bottom center;
  z-index: 3;
}

.keyb-base {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  filter: drop-shadow(2px 4px 4px rgba(0, 0, 0, 0.55));
}

.key {
  position: absolute;
  top: 11px;
  width: 175px;
  height: 79px;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  transition: transform 140ms cubic-bezier(0.34, 1.3, 0.64, 1), filter 140ms ease;
}
.cancel-key { left: 15px; }
.fax-key    { left: 192px; }

.key img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  user-select: none;
  pointer-events: none;
}

.key:hover {
  transform: scale(0.95);
}

.key:active {
  transform: scale(0.85);
  filter: brightness(0.85);
  transition-duration: 60ms;
}

/* ── Cast shadow — sits on the mat under the receipt's footprint. Hidden at
   rest (the receipt's own box-shadow handles contact); during levitation JS
   drives --shadow-grow / blur / opacity so it grows + softens + fades like a
   real shadow as the paper rises toward the light. ── */
.receipt-shadow {
  position: absolute;
  width: 414px;
  height: 808px;
  left: 50%;
  top: 58px;
  transform: translateX(-50%) scale(calc(var(--receipt-scale, 0.85) * var(--shadow-grow, 1)));
  transform-origin: top center;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 6px;
  filter: blur(10px);
  opacity: 0;
  z-index: 1;
  pointer-events: none;
}

/* ── Ripple canvas — overlays the receipt during send animation ── */
.ripple-canvas {
  position: absolute;
  width: 414px;
  height: 808px;
  left: 50%;
  top: 58px;
  transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(var(--receipt-scale, 0.85));
  transform-origin: top center;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity   100ms linear,
    transform var(--lift-duration, 600ms) var(--lift-easing, cubic-bezier(0.22, 1, 0.36, 1));
}
.ripple-canvas.active {
  opacity: 1;
}

/* ── Wobble3d canvas — full-viewport, transparent Three.js render. Only the
   receipt plane paints; the rest is see-through so desk/mat/keyboard show.
   z-index 2 = above the receipt (also 2, but receipt is hidden during the
   animation); the keyboard at z-index 3 stays in front. ── */
#wobble3dCanvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  transition: opacity 100ms linear;
}
#wobble3dCanvas.active {
  opacity: 1;
}

/* ── Ripple tuning panel ── */
.ripple-debug {
  position: fixed;
  top: 16px;
  left: 16px;
  width: 280px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  background: rgba(18, 18, 22, 0.92);
  color: #eee;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
  z-index: 1000;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
}
.ripple-debug[hidden] { display: none; }

.debug-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  font-size: 10px;
  color: #aaa;
}
.debug-header button {
  background: none;
  border: none;
  color: #999;
  font-size: 18px;
  cursor: pointer;
  line-height: 1;
  padding: 0 4px;
}
.debug-header button:hover { color: #fff; }

.debug-body {
  padding: 10px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.debug-body label {
  display: grid;
  grid-template-columns: 1fr 50px;
  gap: 4px 8px;
  align-items: center;
  font-size: 10px;
  color: #bbb;
}
.debug-body input[type="range"] {
  grid-column: 1 / 3;
  width: 100%;
  accent-color: #ff8c00;
}
.debug-body .debug-val {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: #fff;
  font-size: 10px;
}
/* Color-picker rows: label text left, swatch right */
.debug-body label.debug-color {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.debug-body label.debug-color input[type="color"] {
  width: 46px;
  height: 22px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  background: none;
  cursor: pointer;
}

.debug-actions {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}
.debug-btn {
  flex: 1;
  background: rgba(255, 255, 255, 0.06);
  color: #eee;
  border: 1px solid rgba(255, 255, 255, 0.08);
  padding: 6px 8px;
  border-radius: 6px;
  font-family: inherit;
  font-size: 10px;
  cursor: pointer;
  transition: background 120ms;
}
.debug-btn:hover { background: rgba(255, 255, 255, 0.12); }
.debug-btn-primary {
  background: #ff8c00;
  color: #111;
  border-color: #ff8c00;
}
.debug-btn-primary:hover { background: #ffa030; }

.debug-pill {
  position: fixed;
  top: 16px;
  left: 16px;
  background: rgba(18, 18, 22, 0.92);
  color: #eee;
  border: 1px solid rgba(255, 255, 255, 0.08);
  padding: 8px 14px;
  border-radius: 999px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
  cursor: pointer;
  z-index: 1000;
  backdrop-filter: blur(10px);
}

/* ── CSS fallback when WebGL isn't available — quick flash + dissolve ── */
@keyframes cssRippleFlash {
  0%   { filter: brightness(1) blur(0);     opacity: 1;   transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(var(--receipt-scale, 0.85)); }
  35%  { filter: brightness(1.6) blur(2px); opacity: 1;   transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(calc(var(--receipt-scale, 0.85) * 1.03)); }
  60%  { filter: brightness(2.2) blur(6px); opacity: 0.4; transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(calc(var(--receipt-scale, 0.85) * 1.05)); }
  85%  { filter: brightness(1.2) blur(1px); opacity: 1;   transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(var(--receipt-scale, 0.85)); }
  100% { filter: brightness(1) blur(0);     opacity: 1;   transform: translateX(-50%) translateZ(var(--lift, 0px)) scale(var(--receipt-scale, 0.85)); }
}
.receipt.css-ripple {
  animation: cssRippleFlash 1200ms ease-in-out forwards;
}

/* ── Toast ── */
.toast {
  position: fixed;
  bottom: 32px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  padding: 12px 20px;
  background: rgba(20, 20, 20, 0.92);
  color: #fff;
  font-family: 'JetBrains Mono', monospace;
  font-size: 14px;
  border-radius: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms ease, transform 220ms ease;
  z-index: 100;
}
.toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.toast.error {
  background: rgba(180, 40, 40, 0.94);
}
