:root{
  --bg0:#ffffff;
  --bg1:#f8f9fa;
  --perspective: 120vh;
  --rotate-x: 4deg;
  --rotate-y: 0deg;
  --skew-x: 0deg;
  --scale: 1.0;
  --vertical-shift: 0%;
  --vertical-scale: 1.0;
}

*{ box-sizing:border-box; margin:0; padding:0; }
html{ scroll-behavior: smooth; }
html, body {
  width: 100%;
  height: auto;
  margin: 0;
  padding: 0;
  overflow-x: hidden;
}
body {
  position: relative;
  font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  color: #eaf0ff;
  background: var(--bg0);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

.frame-site{
  font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}

/* ─── Galaxy Canvas ──────────────────────────────────────────────────────── */
#galaxyCanvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 0;          /* behind .scene (z-index 1) */
  pointer-events: none;
  display: block;
  /* slight overscan so mouse parallax translate doesn't show edges */
  transform-origin: center center;
}

/* Scene container — make bg transparent so galaxy shows through */
.scene{
  position: fixed;
  inset: 0;
  z-index: 1;
  background: transparent;  /* was #000 — galaxy canvas is behind this */
  pointer-events: none;
  overflow: hidden;
}

/* ─── Blurred backdrop (ambilight fill for letterbox bars) ───────────────
   Same frame as #frameImg but cover-fit + heavy blur. Sits between the
   galaxy canvas and the sharp frame. Opacity is set per-frame in JS —
   0 during wide shots (galaxy shows in bars), 1 during the zoom section
   (~frames 140–214) where the hard image edge would otherwise be visible.
   NO transition — the per-frame opacity ramp in JS handles the fade. */
.scene-frame-bg {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  object-fit: cover;
  object-position: center center;
  display: block;
  pointer-events: none;
  z-index: 0;               /* below .scene-frame (z-index 1) */
  opacity: 0;
  transform: scale(1.12);   /* overscan so blur fringing never shows at edges */
  filter: blur(40px) saturate(1.15) brightness(0.9);
  will-change: opacity;
  transition: none !important;
}

/* ─── Mobile frame mode ──────────────────────────────────────────────────
   On phones the animation shows at FULL HEIGHT, centered — the sides of
   the wide frame crop off (object-fit: cover). No letterbox gaps exist in
   this mode, so JS disables the blurred backdrop + edge feather. */
body.mobile-frame .scene-frame {
  object-fit: cover;
  object-position: center center;
}
body.mobile-frame .scene-frame-bg {
  display: none;
}

/* Frame image — use contain so the full laptop is always visible on any screen size */
.scene-frame {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  object-fit: contain;
  object-position: center center;
  display: block;
  pointer-events: none;
  z-index: 1;
  background: transparent;  /* was #000 — let galaxy show in letterbox bars */
  /* NO transition here — frame swaps are instant by design */
}

.scroll-spacer{
  height: 6400px;
}

/* ─── Scroll hint — first frame only ──────────────────────────────────────
   Same visual language as portfolio.html's own .hero__hint (text + a thin
   line that drops and redraws on a loop), reimplemented here under its own
   names since this page doesn't load portfolio.css. Sits above the scene
   (z-index 1) but below the laptop-screen overlay and contact card (2/3/4),
   so it never competes with them once either takes over. script.js fades
   it out the moment scrolling starts and never brings it back. */
.scrollHint{
  position: fixed;
  left: 50%; bottom: 9vh;
  transform: translateX(-50%);
  z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  font-size: 11.5px; font-weight: 500; letter-spacing: 0.3em; text-transform: uppercase;
  color: rgba(234, 240, 255, 0.72);
  white-space: nowrap;
  pointer-events: none;
  opacity: 1;
  transition: opacity 450ms ease;
}
.scrollHint.is-hidden{ opacity: 0; }
.scrollHintLine{
  width: 1px; height: 44px;
  background: linear-gradient(180deg, transparent, rgba(255, 106, 213, 0.9));
  animation: scrollHintDrop 1.8s ease-in-out infinite;
}
@keyframes scrollHintDrop{
  0%{ transform: scaleY(0); transform-origin: top; }
  55%{ transform: scaleY(1); transform-origin: top; }
  100%{ transform: scaleY(0); transform-origin: bottom; }
}
@media (max-width: 768px){
  .scrollHint{ bottom: 6vh; font-size: 10.5px; letter-spacing: 0.24em; }
}
@media (prefers-reduced-motion: reduce){
  .scrollHintLine{ animation: none; }
}

/* ─── Laptop screen overlay ───────────────────────────────────────────────
   This wrapper sits over the whole viewport and controls visibility.
   ONLY opacity is ever animated — nothing else.
   The viewport inside is always pre-positioned; it never moves on screen. */
#screenOverlay{
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s ease;
  /* Force a compositing layer from page load so the iframe is already on the
     GPU when the user scrolls into range. Without this the browser creates
     the layer at the moment the opacity transition starts — right when the
     user arrives — causing the cold-render flash. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: opacity;
}

/* ─── Inner viewport: mapped exactly onto the laptop screen in the frame ──
   Position/size/transform are set ONCE on page load and on resize.
   They NEVER animate or transition — they are always pre-calculated.
   This is the key: when the overlay fades in, the frame is already
   perfectly placed underneath, so there is zero "sliding into position". */
#screenViewport{
  position: fixed;
  overflow: hidden;
  background: #000;
  border-radius: 5px;
  /* Initial values — JS overwrites these immediately on load */
  left: 0;
  top: 0;
  width: 100px;
  height: 100px;
  transform-origin: 50% 50%;
  transform:
    perspective(var(--perspective))
    rotateX(var(--rotate-x))
    rotateY(var(--rotate-y));
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transform-style: preserve-3d;
  /* CRITICAL: absolutely no transition on this element */
  transition: none !important;
  border: 2px solid rgba(0, 0, 0, 0.3);
  box-shadow:
    0 20px 60px rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}

/* Content container */
#screenInner{
  width: 100%;
  height: 100%;
  border-radius: 5px;
  overflow: hidden;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

#iframeContainer {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}

#siteFrame{
  width: 100%;
  height: 100%;
  border: none;
  display: block;
  transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  pointer-events: none;
  /* Isolate the iframe's render from the outer page. Without this, every
     scroll event or animation on the outer page can trigger a re-composite
     of the iframe even while it's invisible. */
  contain: layout paint;
  isolation: isolate;
}

/* Contact overlay — centered over the final animation frames */
#contactOverlay{
  position: fixed;
  inset: 0;
  z-index: 4;
  display: flex;
  align-items: center;       /* center of the animation, not bottom */
  justify-content: center;
  padding: 24px 18px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 250ms ease;
}

/* Glass card */
.contactCard{
  width: min(720px, 94vw);
  text-align: center;
  background: linear-gradient(
    135deg,
    rgba(255, 255, 255, 0.12),
    rgba(255, 255, 255, 0.04)
  );
  border: 1px solid rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(18px) saturate(1.5);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  border-radius: 28px;
  padding: 56px 48px;
  color: #eaf0ff;
  box-shadow:
    0 24px 70px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.contactKicker{
  font-size: 14px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(234, 240, 255, 0.65);
  margin-bottom: 12px;
}

.contactCard h2{
  font-size: clamp(34px, 6vw, 56px);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin-bottom: 14px;
  background: linear-gradient(90deg, #ffffff, #ffb8ea, #cfa9ff);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.contactSub{
  font-size: clamp(16px, 2vw, 18px);
  line-height: 1.65;
  color: rgba(234, 240, 255, 0.78);
  margin-bottom: 30px;
}

.contactLinks{
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

.contactLinks a{
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 13px 22px;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 500;
  color: #dce8ff;
  text-decoration: none;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background 180ms ease, border-color 180ms ease, transform 180ms ease;
}

.contactLinks a:hover{
  background: rgba(255, 106, 213, 0.16);
  border-color: rgba(255, 106, 213, 0.45);
  transform: translateY(-2px);
  text-decoration: none;
}

.contactIcon{
  font-size: 14px;
  opacity: 0.85;
}

/* =========================================== */
/* SMALL SCREEN MODE (below 1050x600)          */
/* =========================================== */

body.small-screen-mode .scene-frame {
  opacity: 0 !important;
  transition: opacity 0.5s ease !important;
}

body.small-screen-mode .scene-frame-bg {
  opacity: 0 !important;
}

/* No camera-flight story is told in this mode — the portfolio takes over
   fullscreen immediately and scrolls natively, so a hint about scrolling
   the (invisible) flight animation would be meaningless here. */
body.small-screen-mode .scrollHint {
  opacity: 0 !important;
  pointer-events: none !important;
}

body.small-screen-mode #screenOverlay {
  opacity: 1 !important;
  pointer-events: auto !important;
  z-index: 999 !important;
  /* Override the normal transition for instant switch in mobile mode */
  transition: none !important;
}

body.small-screen-mode #screenViewport {
  position: fixed !important;
  left: 0 !important;
  top: 0 !important;
  width: 100vw !important;
  height: 100vh !important;
  height: 100dvh !important;  /* tracks iOS/Android collapsing browser chrome */
  transform: none !important;
  border-radius: 0 !important;
  border: none !important;
  box-shadow: none !important;
  transition: none !important;
}

body.small-screen-mode #screenInner {
  border-radius: 0 !important;
}

body.small-screen-mode #siteFrame {
  pointer-events: auto !important;
}

body.small-screen-mode #contactOverlay {
  opacity: 0 !important;
  pointer-events: none !important;
}

body.small-screen-mode .scroll-spacer {
  display: block !important;
}

/* Mobile responsiveness */
@media (max-width: 768px) {
  #contactOverlay { padding: 24px 12px; }
  .contactCard { padding: 18px 16px; }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}