/* ═══════════════════════════════════════════════════════════════════════
   Ranbir Gill — home page.

   The page has two halves:
     1. THE DESK  — a CSS 3D scene (the hero).
     2. THE WALL  — a paper-coloured page with the project grid.

   It also has two modes, chosen in index.html before the first paint:
     • default     the desk is a plain hero and the wall sits under it.
                   Phones, touch screens, reduced motion, and no JavaScript.
     • .is-live    the desk stays pinned while you scroll, the papers lift
                   off it, and the wall rises to catch them. js/main.js
                   drives that; this file only sets up the starting state.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  --paper: #f6efe3;
  --paper-sheet: linear-gradient(170deg, #fdfaf4 0%, #f8f2e7 60%, #f3ebdd 100%);
  --footer: #efe6d7;
  --ink: #1f2230;
  --ink-2: #4a4d5c;
  --muted: #6b6258;
  --button: #3e322e;
  --room: #2e2832;
  --nav-h: 64px;

  /* The desk camera's focal length, as a share of the window width.
     Smaller is a wider lens: more room in view, everything a little smaller. */
  --focal: 42vw;

  --display: 'Barlow Condensed', sans-serif;
  --mono: 'IBM Plex Mono', monospace;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--room);
  overflow-x: clip;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar { display: none; width: 0; height: 0; }


/* ── Motion ─────────────────────────────────────────────────────────────
   All of it ambient: things that move on their own, whatever the scroll
   position. The scroll transition itself lives in js/main.js.

   These only ever animate `transform` and `opacity`, with plain numbers.
   That is what lets the browser run them on the graphics card without
   redrawing anything. A var() inside a keyframe quietly breaks that, which
   is why the dust (different drift per speck) is animated from js/main.js. */

@keyframes cueBob    { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(7px) } }
@keyframes twinkle   { 0%, 100% { opacity: 1 } 50% { opacity: 0.35 } }
@keyframes shoot     { 0% { transform: translateX(0); opacity: 0 } 0.6% { opacity: 1 } 3.2% { transform: translateX(220px); opacity: 0 } 100% { transform: translateX(220px); opacity: 0 } }
@keyframes moonGlow  { 0%, 100% { opacity: 0.34 } 50% { opacity: 0.44 } }
@keyframes smoke     { 0% { transform: translate(0, 0) scale(0.6); opacity: 0 } 15% { opacity: 0.55 } 100% { transform: translate(14px, -46px) scale(2.2); opacity: 0 } }
@keyframes sunBreath { 0%, 100% { opacity: 0.15 } 50% { opacity: 0.6 } }
@keyframes flock     { 0% { transform: translate(0, 0); opacity: 0 } 3% { opacity: 1 } 30% { transform: translate(-1900px, -50px); opacity: 1 } 33% { opacity: 0 } 100% { transform: translate(-1900px, -50px); opacity: 0 } }
@keyframes flap      { 0%, 100% { transform: scaleY(1) } 50% { transform: scaleY(-0.5) } }
@keyframes glint     { 0%, 100% { opacity: 0.75 } 50% { opacity: 1 } }
@keyframes auraPulse { 0%, 100% { opacity: 0.55; transform: scale(0.92) } 50% { opacity: 0.9; transform: scale(1.08) } }
@keyframes spark     { 0% { transform: translateY(0) scale(0.4); opacity: 0 } 20% { opacity: 1 } 100% { transform: translateY(-46px) scale(1); opacity: 0 } }

/* The floating-logo sway. --ph is an angle that runs 0 → 360° on a loop and
   --amp is how strong the sway is (0 = still). Registering them as typed
   properties is what lets the browser animate them. */
@property --ph  { syntax: "<angle>";  initial-value: 0deg; inherits: false }
@property --amp { syntax: "<number>"; initial-value: 0;    inherits: false }
@keyframes phase { to { --ph: 360deg } }


/* ══════════════════════════════════════════════════════════════════════
   NAVBAR
   Light text over the desk, dark text on paper once the wall has landed.
   Slides away when you scroll down and comes back when you scroll up.
   ══════════════════════════════════════════════════════════════════════ */

.nav {
  position: fixed;
  left: 0; right: 0; top: 0;
  z-index: 10;
  height: var(--nav-h);
  box-sizing: border-box;
  padding: 0 4vw;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  color: var(--paper);
  background-color: rgba(246, 239, 227, 0);
  transition: transform 450ms cubic-bezier(.3, .7, .3, 1), background-color 400ms ease, color 400ms ease, box-shadow 400ms ease;
}
/* The dark fade behind the light text. A separate layer so it can cross-fade
   with the paper background; a gradient can't be transitioned to a colour. */
.nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgba(20, 16, 30, 0.55) 0%, rgba(20, 16, 30, 0) 100%);
  transition: opacity 400ms ease;
}
.nav.is-paper {
  color: var(--ink);
  background-color: rgba(246, 239, 227, 0.94);
  box-shadow: 0 1px 0 rgba(31, 34, 48, 0.1);
}
.nav.is-paper::before { opacity: 0; }
/* 2px further than its own height, so the hairline shadow leaves too. */
.nav.is-hidden { transform: translateY(calc(-100% - 2px)); }

.nav a,
.footer a {
  font-family: var(--display);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: inherit;
  opacity: 0.78;
  transition: opacity 200ms ease;
}
.nav a:hover,
.footer a:hover { opacity: 1; }

.nav .nav__name {
  font-weight: 700;
  font-size: 20px;
  letter-spacing: 0.06em;
  opacity: 1;
}
.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 36px);
}


/* ══════════════════════════════════════════════════════════════════════
   THE DESK
   ══════════════════════════════════════════════════════════════════════ */

.page { position: relative; }

.scene {
  position: relative;
  z-index: 0;
  width: 100%;
  /* Out of live mode the scene is a hero, not a full-screen stage. It keeps
     roughly a laptop's shape so the composition matches the design. */
  height: min(100svh, max(62.5vw, 380px));
  overflow: hidden;
  background: var(--room);
  /* The camera. Its focal length follows the window width, so the desk
     always fills the same share of the screen. */
  perspective: var(--focal);
  perspective-origin: 50% 50%;
}
.is-live .scene {
  position: sticky;
  top: 0;
  height: 100vh;
}

/* Everything in the room hangs off this point at the centre of the screen.
   rotateX(-20deg) is the camera looking slightly down at the desk. */
.world {
  position: absolute;
  left: 50%; top: 50%;
  width: 0; height: 0;
  transform-style: preserve-3d;
  transform: translate3d(0, 0, var(--focal)) rotateX(-20deg);
}

.scene__vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 40%, rgba(20, 16, 30, 0) 45%, rgba(20, 16, 30, 0.45) 100%);
}

/* Shorthand for the scene markup: a flat plane placed in 3D (.pl), one that
   hides when seen from behind (.bf), a layer of the sky painting (.sk), a
   layer painted on the desk top (.dk), a 3D group (.g3), and an anchor that
   js/main.js draws effects into (.fx). */
.pl { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.bf { backface-visibility: hidden; }
.sk { position: absolute; left: 0; top: 0; width: 4000px; height: 1800px; }
.dk { position: absolute; left: 0; top: 0; width: 800px; height: 350px; }
.g3 { position: absolute; left: 0; top: 0; transform-style: preserve-3d; }
.fx { position: absolute; left: 0; top: 0; }

/* One flat picture of the sky. The outer box is the only part of the sky
   the window can ever show (worked out from the window's size and depth),
   so nothing outside it is drawn or kept in memory. The inner box puts the
   sky's own 4000 × 1800 coordinates back. */
.skg {
  position: absolute;
  left: 1200px; top: 400px;
  width: 1600px; height: 900px;
  overflow: hidden;
  contain: strict;
}
.skg > div {
  position: absolute;
  left: -1200px; top: -400px;
  width: 4000px; height: 1800px;
}

/* Laid out at four times its size and scaled back to a quarter. Nothing
   changes on screen at rest, but while something is mid-animation in 3D the
   browser draws it at its laid-out size and stretches that. Without this a
   logo lifting off the desk is a 24px picture blown up to five times its
   size, sharpening only once it stops. */
.x4 {
  position: absolute;
  left: 0; top: 0;
  transform: scale(0.25);
  transform-origin: 0 0;
  will-change: transform;
}

/* Once the wall has covered the desk (or the hero has scrolled away),
   everything on it stops moving. */
.scene.is-asleep,
.scene.is-asleep * { animation-play-state: paused !important; }

/* Laptop keyboard: rows of keys. */
.kr { display: flex; gap: 1.5px; flex: none; }
.kr i { flex: 1 1 0; height: 100%; border-radius: 1.5px; background: #4a4643; }

.dust {
  position: absolute;
  left: 0; top: 0;
  transform-style: preserve-3d;
  pointer-events: none;
}


/* ── The three things you can pick up: two papers and the laptop ──────── */

.obj {
  position: absolute;
  left: 0; top: 0;
  display: block;
  text-decoration: none;
  cursor: pointer;
  transform-origin: 0 0;
  transform-style: preserve-3d;
  outline: none;
}
/* Once scrolling starts they belong to the transition, not the mouse. */
.scene.is-away .obj { pointer-events: none; }
/* ...and their surfaces are kept as finished pictures that the graphics
   card tilts, instead of being redrawn at every angle on the way. */
.scene.is-away .paper__cast,
.scene.is-away .paper__band,
.scene.is-away .lid > div:not(.emblem) { will-change: transform; }

/* Papers lie flat on the desk (rotateX 90°), turned 8° toward the viewer.
   --seat is where a paper sits; js/main.js takes over from there on scroll. */
.paper {
  width: 120px;
  height: 127px;
  transform: var(--seat);
}
.paper.is-hover { transform: var(--seat) translateZ(1.5px); }
#paper-ss { --seat: translate3d(200px, 229.4px, -300px) rotateX(90deg) rotateZ(-8deg) translate(-50%, -50%); }
#paper-ws { --seat: translate3d(-200px, 229.4px, -300px) rotateX(90deg) rotateZ(8deg) translate(-50%, -50%); }

/* --fx is how much of the desk's light and shadow is still on the paper:
   1 lying on the desk, 0 once it has lifted clear. */
.paper__cast {
  position: absolute;
  left: -2.5px; top: 3.5px;
  width: 120px; height: 127px;
  border-radius: 1px;
  background: rgba(30, 18, 20, 0.5);
  filter: blur(1.2px);
  opacity: var(--fx, 1);
}
/* The sheet uses the same 4× trick as .x4, so the print on it stays sharp
   as the paper turns to face you. The inner box is the actual paper. */
.paper__sheet {
  position: absolute;
  left: 0; top: 0;
  width: 480px; height: 508px;
  transform: scale(0.25);
  transform-origin: 0 0;
  will-change: transform;
}
.paper__sheet > div {
  position: absolute;
  left: 0; top: 0;
  width: 120px; height: 127px;
  transform: scale(4);
  transform-origin: 0 0;
  border-radius: 1px;
  background: var(--paper-sheet);
  box-shadow: inset 0 0 0 0.5px rgba(150, 120, 90, 0.25);
}
.paper__sun {
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(255, 188, 120, 0.34) 0%, rgba(255, 196, 130, 0.22) 100%);
  opacity: var(--fx, 1);
}
/* The window frame's shadow, falling straight across the paper. */
.paper__band {
  position: absolute;
  left: 0; top: 0;
  width: 120px; height: 127px;
  overflow: hidden;
  transform: translateZ(0.5px);
  opacity: var(--fx, 1);
}
.paper__band > div {
  position: absolute;
  left: -90px; top: -86.5px;
  width: 300px; height: 300px;
  background: linear-gradient(180deg, rgba(38, 24, 32, 0.58) 0px, rgba(38, 24, 32, 0.58) 106px, rgba(38, 24, 32, 0) 122px);
}
/* The floating logo's own shadow on the paper below it. */
.paper__shadow {
  position: absolute;
  border-radius: 50%;
  filter: blur(2px);
  opacity: 0;
  transition: opacity 700ms ease;
}
.is-hover .paper__shadow { opacity: 1; }

.lid {
  width: 176px;
  height: 116px;
  transform: translate3d(0px, 222px, -395.5px) rotateX(15deg) translate(-50%, -100%);
}
/* Where the snowball touches the screen. Gone once it lifts off. */
.lid__contact {
  position: absolute;
  left: 62px; top: 26px;
  width: 37px; height: 5px;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(3, 30, 42, 0.55), rgba(3, 30, 42, 0) 70%);
  transition: opacity 700ms ease;
}
.is-hover .lid__contact { opacity: 0; }


/* ── Hover: the logo peels off and floats ─────────────────────────────
   .is-hover is set by js/main.js on mouse-over and keyboard focus.
   .is-anim outlives it by a moment so the sway and sparkles keep moving
   while they fade, instead of freezing mid-air. */

.emblem {
  position: absolute;
  transform-style: preserve-3d;
  transform: var(--rest, translateZ(0.8px));
  transition: transform 1100ms cubic-bezier(.22, .9, .3, 1.12);
}
.is-hover .emblem { transform: var(--lift); }

.sway {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  --amp: 0;
  transition: --amp 0.5s ease-out;
  animation: phase 4.4s linear infinite paused;
}
.is-hover .sway {
  --amp: 1;
  transition: --amp 1.6s ease-in-out 0.7s;
}
.is-anim .sway { animation-play-state: running; }

.aura {
  position: absolute;
  opacity: 0;
  transition: opacity 800ms ease;
}
.is-hover .aura { opacity: 1; }
.obj:not(.is-anim) .aura * { animation-play-state: paused !important; }

.glow {
  filter: var(--g0);
  transition: filter 800ms ease;
}
.is-hover .glow { filter: var(--g1); }

/* The darker, shadowed copy of a logo. Lifts with the desk light (--fx)
   and clears completely when the logo floats up out of the shadow. */
.shade {
  opacity: var(--fx, 1);
  transition: opacity 500ms ease;
}
.is-hover .shade { opacity: 0; }

.obj:focus-visible .paper__sheet > div { box-shadow: 0 0 0 1px rgba(255, 238, 200, 0.9); }


/* ── Scroll cue ─────────────────────────────────────────────────────── */

.cue { display: none; }
.is-live .cue {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  height: 16vh;
  box-sizing: border-box;
  padding-bottom: 2.6vh;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(246, 239, 227, 0) 0%, rgba(246, 239, 227, 0.5) 50%, rgba(246, 239, 227, 0.95) 100%);
  will-change: opacity;
}
.cue svg {
  overflow: visible;
  animation: cueBob 2.4s ease-in-out infinite;
}

.runway { display: none; }
.is-live .runway {
  display: block;
  /* 320vh plays the transition. The extra 50vh is a rest once the wall has
     landed (HOLD in js/main.js). */
  height: 370vh;
  pointer-events: none;
}


/* ══════════════════════════════════════════════════════════════════════
   THE WALL
   ══════════════════════════════════════════════════════════════════════ */

.wall-hold {
  position: relative;
  z-index: 2;
}
.wall {
  position: relative;
  z-index: 2;
  min-height: 100vh;
  box-sizing: border-box;
  padding: calc(var(--nav-h) + 7vh) 4vw 0;
  background: var(--paper);
  box-shadow: 0 -30px 60px rgba(20, 14, 24, 0.35);
}
/* Live: the wall waits below the screen and js/main.js slides it up with a
   transform, which moves the finished picture of the wall rather than
   laying the page out again every frame. .is-settled is added once it has
   fully risen, and hands it back to the normal document flow. */
.is-live .wall {
  position: fixed;
  left: 0; right: 0; top: 0;
  transform: translate3d(0, 140vh, 0);
  will-change: transform;
}
.is-live .wall.is-settled {
  position: relative;
  transform: none;
  will-change: auto;
}

.wall__head {
  max-width: 1040px;
  margin: 0 auto 5vh;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.eyebrow {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.wall h1 {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(40px, 6vw, 76px);
  line-height: 0.95;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-wrap: balance;
}


/* ── Project grid ───────────────────────────────────────────────────── */

.grid {
  max-width: 1040px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 3.2vw, 44px);
}

/* .slot is the card's fixed place in the grid. .fly is the layer that
   js/main.js moves, so a card can start where its paper was on the desk
   and travel to its slot. .card is the paper itself. */
.slot {
  position: relative;
  aspect-ratio: 120 / 127;
}
.fly {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
}
.is-live .fly {
  opacity: 0;
  pointer-events: none;
  /* Travels as a finished picture; never redrawn on the way. */
  will-change: transform, opacity;
}

/* Cards are the same colour as the wall. Only the shadow sets them apart. */
.card {
  position: absolute;
  inset: 0;
  display: block;
  overflow: hidden;
  text-decoration: none;
  border-radius: 3px;
  background: var(--paper-sheet);
  box-shadow: 0 1px 2px rgba(70, 50, 35, 0.12), 0 8px 24px rgba(70, 50, 35, 0.14);
  transition: transform 300ms ease, box-shadow 300ms ease;
}
.card:hover,
.card:focus-visible {
  transform: translateY(-6px);
  box-shadow: 0 2px 4px rgba(70, 50, 35, 0.14), 0 20px 40px rgba(70, 50, 35, 0.22);
}
.card:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

/* A card's contents are laid out at the desk paper's size (120 × 127) and
   scaled up as one piece. --k is card width ÷ 120, set by js/main.js. */
.card__art {
  position: absolute;
  left: 0; top: 0;
  width: 120px; height: 127px;
  transform-origin: 0 0;
  transform: scale(var(--k, 2.6));
}
/* Carries the dim of the room across with the card, then fades out. */
.card__dim {
  position: absolute;
  inset: 0;
  background: rgb(20, 16, 30);
  pointer-events: none;
  opacity: 0;
  will-change: opacity;
}

/* Snowball PvP's mini browser: a loading bar, then the game fades in.
   .is-loaded is added when the card arrives. */
.sb-bar {
  position: absolute;
  left: 0; top: 7px;
  width: 0;
  height: 0.8px;
  background: #4aa3c4;
}
.sb-screen { opacity: 0; }
.is-loaded .sb-bar {
  width: 100%;
  opacity: 0;
  transition: width 900ms cubic-bezier(.3, .7, .4, 1), opacity 300ms ease 950ms;
}
.is-loaded .sb-screen {
  opacity: 1;
  transition: opacity 500ms ease 850ms;
}
html:not(.js) .sb-screen { opacity: 1; }


/* ── About line, back button, footer ────────────────────────────────── */

.about {
  max-width: 1040px;
  margin: 0 auto;
  padding: 16vh 0 12vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7vh;
  text-align: center;
}
.about p {
  margin: 0;
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(28px, 3.6vw, 44px);
  line-height: 1.1;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-wrap: balance;
}

.back {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--button);
  opacity: 0.85;
  transition: opacity 200ms ease;
}
.back:hover { opacity: 1; }
.back svg {
  overflow: visible;
  animation: cueBob 2.4s ease-in-out infinite reverse;
}
.back span {
  font-family: var(--display);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.footer {
  margin: 0 -4vw;
  padding: 28px 4vw 32px;
  border-top: 1px solid rgba(31, 34, 48, 0.12);
  background: var(--footer);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 32px;
  color: var(--ink-2);
}
.footer__copy {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.04em;
}
.footer__links {
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 32px);
}

.nav a:focus-visible,
.footer a:focus-visible,
.back:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
  opacity: 1;
}


/* ══════════════════════════════════════════════════════════════════════
   SMALL SCREENS
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 760px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Five projects + one blank makes an even grid of six. */
  .slot--empty:nth-last-child(-n + 3) { display: none; }
  .wall { padding-top: 7vh; }
}

@media (max-width: 520px) {
  .nav a { font-size: 13px; letter-spacing: 0.1em; }
  .nav .nav__name { font-size: 18px; }
  .nav__links { gap: 14px; }
  .nav .the { display: none; }

  .grid { grid-template-columns: minmax(0, 1fr); }
  .slot--empty { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════
   REDUCED MOTION
   Nothing moves on its own. (The scroll transition is already off: the
   page never enters live mode with this preference set.)
   ══════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
}
