/* ============================================================
   ALIBKS - KINETIC NEO-BRUTALIST CUTOUT
   Shared portal stylesheet for photos.alibks.dev and
   projects.alibks.dev.

   This is the vanilla-CSS mirror of the React design system in
   src/index.css. The portals are static HTML served straight from nginx with no
   build step, so they cannot import the Tailwind theme - but they must be
   visually indistinguishable from the main site. Anything that changes here has
   a counterpart there.

   Four rules, same as the main site:
     1. No smooth curves. steps() at 83ms/125ms, never a bezier.
     2. No soft shadows. Elevation is a solid-fill offset silhouette.
     3. No orthogonal boxes. Sheared containers, hand-cut polygons.
     4. Composite-only motion. transform / opacity / clip-path exclusively.
   ============================================================ */

:root {
  /* Palette */
  --paper: #f4efea;
  --paper-2: #e7dfd5;
  --card: #ffffff;
  --cobalt: #050b14;
  --cobalt-2: #0b1522;
  --cobalt-3: #101d2e;
  --ink: #0a0a0a;
  --ink-2: #14161c;
  --cyan: #00d2ff;
  --crimson: #ff2a55;
  --teal: #05668d;
  --mustard: #f4a261;

  /* Type */
  --font-display: "Anton", "Arial Narrow", Impact, sans-serif;
  --font-heavy: "Archivo Black", "Anton", Impact, sans-serif;
  --font-sans: "Space Grotesk", system-ui, sans-serif;
  --font-serif: "Instrument Serif", Georgia, serif;
  --font-mono: "Space Mono", ui-monospace, monospace;
  --font-marker: "Caveat", cursive;

  /* Motion. --cycle is 3 frames (idle sway), --twitch is 4 (clip swap). A
     4-state swap on --cycle would run at 16fps, outside the 8-12 band. */
  --frame: 83ms;
  --frame-slow: 125ms;
  --cycle: 250ms;
  --twitch: 332ms;

  /* Global amplitude dials. The mobile block scales these down instead of
     overriding transforms, so one set of keyframes drives every viewport. */
  --tilt-mul: 1;
  --depth-mul: 1;
}

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

html {
  overflow-x: hidden;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  min-height: 100vh;
  font-synthesis: none;
}

body.dark-canvas {
  background: var(--cobalt);
  color: var(--paper);
}

a {
  color: inherit;
}
button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
}
::selection {
  background: var(--crimson);
  color: #fff;
}
:focus-visible {
  outline: 3px solid var(--cyan);
  outline-offset: 3px;
}
img {
  max-width: 100%;
}

::-webkit-scrollbar {
  width: 14px;
  height: 14px;
}
::-webkit-scrollbar-track {
  background: var(--paper-2);
  border-left: 2px solid var(--ink);
}
::-webkit-scrollbar-thumb {
  background: var(--ink);
  border: 3px solid var(--paper-2);
}
body.dark-canvas ::-webkit-scrollbar-track {
  background: var(--cobalt-2);
  border-left-color: var(--cyan);
}
body.dark-canvas ::-webkit-scrollbar-thumb {
  background: var(--cyan);
  border-color: var(--cobalt-2);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--crimson);
}

/* ============================================================
   TEXTURES
   ============================================================ */

.grain {
  position: fixed;
  inset: -60%;
  z-index: 70;
  pointer-events: none;
  opacity: 0.075;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 240px 240px;
  animation: grain-shift 664ms steps(8) infinite;
  backface-visibility: hidden;
}

@keyframes grain-shift {
  0% {
    transform: translate3d(0, 0, 0);
  }
  14% {
    transform: translate3d(-4%, 3%, 0);
  }
  28% {
    transform: translate3d(3%, -4%, 0);
  }
  42% {
    transform: translate3d(-3%, -3%, 0);
  }
  57% {
    transform: translate3d(4%, 4%, 0);
  }
  71% {
    transform: translate3d(-4%, -1%, 0);
  }
  85% {
    transform: translate3d(2%, 3%, 0);
  }
  100% {
    transform: translate3d(0, 0, 0);
  }
}

.hatch-layer,
.dot-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.hatch-layer {
  opacity: 0.05;
  background-image: repeating-linear-gradient(
    45deg,
    var(--hatch-color, var(--crimson)) 0 var(--hatch-w, 9px),
    transparent var(--hatch-w, 9px) calc(var(--hatch-w, 9px) * 2.4)
  );
}

.dot-layer {
  opacity: 0.14;
  background-image: radial-gradient(
    var(--dot-color, rgba(10, 10, 10, 0.8)) var(--dot-r, 1.8px),
    transparent calc(var(--dot-r, 1.8px) + 0.5px)
  );
  background-size: var(--dot-gap, 10px) var(--dot-gap, 10px);
}

/* ============================================================
   CUT SURFACES

   Per-surface custom properties from NB.cutVars(): --cut-clip-0..3 (four hand-cut
   polygons), --cut-shear (ONE resting angle), --cut-dx/dy (hard shadow), --cut-safe.

   RESTING: content is welded to the card. No counter-rotation; a sheet cut at
   -3deg carries its type at -3deg. HOVER: only the clip animates. No rotation,
   scale or translation on the card, the content or the shadow.
   ============================================================ */

.cut {
  position: relative;
  isolation: isolate;
  display: block;
  transform: rotate(calc(var(--cut-shear, 0deg) * var(--tilt-mul, 1)));
  backface-visibility: hidden;
}

.cut-shadow {
  position: absolute;
  inset: 0;
  z-index: 0;
  clip-path: var(--cut-clip);
  background: var(--cut-shadow-fill, var(--ink));
  transform: translate3d(
    calc(var(--cut-dx, 5px) * var(--depth-mul, 1)),
    calc(var(--cut-dy, 5px) * var(--depth-mul, 1)),
    0
  );
  pointer-events: none;
}

.cut-face {
  position: absolute;
  inset: 0;
  z-index: 1;
  clip-path: var(--cut-clip);
  background: var(--cut-stroke-fill, var(--ink));
  pointer-events: none;
}

.cut-face > i {
  position: absolute;
  inset: var(--cut-stroke-w, 3px);
  clip-path: var(--cut-clip);
  background: var(--cut-fill, var(--card));
  display: block;
}

/* The print. No transform, resting or hovered: it inherits the card's angle by
   being inside it. Safe inset lives here so a caller's padding cannot override it;
   vertices bite up to --cut-safe px across all four states. */
.cut-content {
  position: relative;
  z-index: 2;
  display: block;
  height: 100%;
  box-sizing: border-box;
  padding: var(--cut-safe, 10px);
}

.cut-backdrop {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  clip-path: var(--cut-clip);
  pointer-events: none;
  overflow: hidden;
}

button.cut,
a.cut {
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  width: 100%;
}

/* ============================================================
   DISCRETE VERTEX-JITTER TWITCH

   Hover/focus/active frame-swaps the four hand-cut polygons at 12fps (332ms / 4
   frames, steps(1)). Only clip-path animates; every layer carrying the silhouette
   swaps in lockstep so fill, stroke, texture and shadow always agree on the shape.
   ============================================================ */

.cut-twitch:hover > .cut-face,
.cut-twitch:focus-visible > .cut-face,
.cut-twitch:active > .cut-face,
.cut-twitch:hover > .cut-face > i,
.cut-twitch:focus-visible > .cut-face > i,
.cut-twitch:active > .cut-face > i,
.cut-twitch:hover > .cut-backdrop,
.cut-twitch:focus-visible > .cut-backdrop,
.cut-twitch:active > .cut-backdrop,
.cut-twitch:hover > .cut-shadow,
.cut-twitch:focus-visible > .cut-shadow,
.cut-twitch:active > .cut-shadow {
  animation: cut-clip-twitch var(--twitch) steps(1) infinite;
}

@keyframes cut-clip-twitch {
  0% {
    clip-path: var(--cut-clip-1, var(--cut-clip));
  }
  25% {
    clip-path: var(--cut-clip-2, var(--cut-clip));
  }
  50% {
    clip-path: var(--cut-clip-3, var(--cut-clip));
  }
  75% {
    clip-path: var(--cut-clip-0, var(--cut-clip));
  }
}

/* ============================================================
   RANSOM TOKENS

   Every character is its own cutout: paper stock, keyline, tilt, and an asymmetric
   multi-vertex polygon with harshly displaced corners.

   Three sibling layers, NOT a ::before with negative z-index. Every tile carries a
   transform, which makes it a stacking context, and inside one a negative-z child
   paints after its parent's background instead of behind it - which hid every
   coloured fill under the black keyline.

   RESTING: glyph sits at the tile's angle. HOVER: only the clip swaps, four states
   at 12fps; the glyph does not move.
   ============================================================ */

.ransom {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.14em 0.06em;
  line-height: 1;
}

.ransom-token {
  display: inline-block;
  position: relative;
  isolation: isolate;
  transform: rotate(var(--tok-r, 0deg)) translate3d(0, var(--tok-y, 0em), 0);
  line-height: 1;
  padding: var(--tok-pad, 0.15em 0.35em);
  text-align: center;
  backface-visibility: hidden;
}

.ransom-key,
.ransom-fill {
  position: absolute;
  inset: 0;
  display: block;
  clip-path: var(--tok-clip-0, none);
  pointer-events: none;
}

/* Keyline: offset silhouette separating the scrap from the CANVAS behind it, so
   its colour follows the canvas, not the tile. */
.ransom-key {
  z-index: 0;
  background: var(--tok-key, var(--ink));
  transform: translate3d(3px, 3px, 0);
}

.ransom-fill {
  z-index: 1;
  background: var(--tok-bg, var(--card));
}

/* Glyph. Colour bound inline from the same pair as the fill and forced, so an
   enclosing heading's colour rule cannot repaint it. */
.ransom-ink {
  position: relative;
  z-index: 2;
  display: block;
  color: var(--tok-fg, currentColor) !important;
}

/* Idle sway. Only after the entrance finishes; mutually exclusive with it. */
.ransom-idle .ransom-token {
  animation-name: var(--tok-sway, sway-a);
  animation-duration: var(--tok-dur, 3.2s);
  animation-delay: var(--tok-delay, 0s);
  animation-iteration-count: infinite;
  animation-timing-function: steps(3, jump-none);
}

/* Hover: the run re-cuts, glyphs stationary. Both clipped layers swap in lockstep. */
.ransom:hover .ransom-key,
.ransom:hover .ransom-fill,
.ransom:focus-visible .ransom-key,
.ransom:focus-visible .ransom-fill,
.ransom:active .ransom-key,
.ransom:active .ransom-fill {
  animation: tok-clip-twitch var(--twitch) steps(1) infinite;
}

@keyframes tok-clip-twitch {
  0% {
    clip-path: var(--tok-clip-1, var(--tok-clip-0, none));
  }
  25% {
    clip-path: var(--tok-clip-2, var(--tok-clip-0, none));
  }
  50% {
    clip-path: var(--tok-clip-3, var(--tok-clip-0, none));
  }
  75% {
    clip-path: var(--tok-clip-0, none);
  }
}

/* Tile under the pointer runs its states in a different order to lead the run. */
.ransom-token:hover,
.ransom-token:active {
  z-index: 5;
}
.ransom-token:hover .ransom-key,
.ransom-token:hover .ransom-fill,
.ransom-token:active .ransom-key,
.ransom-token:active .ransom-fill {
  animation: tok-clip-twitch-hot var(--twitch) steps(1) infinite;
}

@keyframes tok-clip-twitch-hot {
  0% {
    clip-path: var(--tok-clip-3, var(--tok-clip-0, none));
  }
  25% {
    clip-path: var(--tok-clip-1, var(--tok-clip-0, none));
  }
  50% {
    clip-path: var(--tok-clip-2, var(--tok-clip-0, none));
  }
  75% {
    clip-path: var(--tok-clip-0, none);
  }
}

.ransom-space {
  display: inline-block;
  width: 0.3em;
}

/* Wrapper for standalone titles: takes the caller's font-size classes, is measured
   against the parent, must not shrink-wrap the scaled run. */
.ransom-fit {
  display: block;
  max-width: 100%;
}

/* Wrapper for inline runs (CutoutWord): sits in the text flow. */
.ransom-fit-inline {
  display: inline-flex;
  vertical-align: baseline;
  max-width: 100%;
}

/* The scaled run: --ransom-fit is a plain multiplier so every em scales with the
   type. font-size, not transform, or a gap is left under the heading. */
.ransom-scaled {
  font-size: calc(1em * var(--ransom-fit, 1));
  flex-wrap: nowrap;
}

.ransom-inline {
  gap: 0.06em 0.03em;
  flex-wrap: wrap;
  vertical-align: baseline;
}

/* Entrance. Runs once; NB.ransom swaps to .ransom-idle when settled.
   iteration-count:1 explicit so it cannot inherit .ransom-idle's infinite. */
.ransom-enter .ransom-token {
  animation: ransom-paste 332ms steps(5, jump-none) var(--tok-enter, 0s) 1 both;
}
.ransom-enter .ransom-key,
.ransom-enter .ransom-fill {
  animation: ransom-paste-cut 332ms steps(4, jump-none) var(--tok-enter, 0s) 1 both;
}

@keyframes ransom-paste {
  0% {
    opacity: 0;
    transform: translate3d(calc(var(--tok-from, 1) * 26px), -18px, 0)
      rotate(calc(var(--tok-r, 0deg) + var(--tok-from, 1) * 20deg));
  }
  40% {
    opacity: 1;
    transform: translate3d(calc(var(--tok-from, 1) * 6px), 4px, 0)
      rotate(calc(var(--tok-r, 0deg) - var(--tok-from, 1) * 7deg));
  }
  70% {
    opacity: 1;
    transform: translate3d(0, -2px, 0) rotate(calc(var(--tok-r, 0deg) + 3deg));
  }
  100% {
    opacity: 1;
    transform: rotate(var(--tok-r, 0deg)) translate3d(0, var(--tok-y, 0em), 0);
  }
}

@keyframes ransom-paste-cut {
  0% {
    clip-path: var(--tok-clip-1, var(--tok-clip-0, none));
  }
  33% {
    clip-path: var(--tok-clip-2, var(--tok-clip-0, none));
  }
  66% {
    clip-path: var(--tok-clip-3, var(--tok-clip-0, none));
  }
  100% {
    clip-path: var(--tok-clip-0, none);
  }
}

@keyframes sway-a {
  0%,
  100% {
    transform: rotate(var(--tok-r, 0deg)) translate3d(0, var(--tok-y, 0em), 0);
  }
  50% {
    transform: rotate(calc(var(--tok-r, 0deg) + 1.6deg))
      translate3d(0, calc(var(--tok-y, 0em) - 0.02em), 0);
  }
}
@keyframes sway-b {
  0%,
  100% {
    transform: rotate(var(--tok-r, 0deg)) translate3d(0, var(--tok-y, 0em), 0);
  }
  50% {
    transform: rotate(calc(var(--tok-r, 0deg) - 2deg))
      translate3d(0.5px, calc(var(--tok-y, 0em) + 0.02em), 0);
  }
}
@keyframes sway-c {
  0%,
  100% {
    transform: rotate(var(--tok-r, 0deg)) translate3d(0, var(--tok-y, 0em), 0);
  }
  33% {
    transform: rotate(calc(var(--tok-r, 0deg) + 1.1deg)) translate3d(-0.5px, var(--tok-y, 0em), 0);
  }
  66% {
    transform: rotate(calc(var(--tok-r, 0deg) - 1.4deg))
      translate3d(0.5px, calc(var(--tok-y, 0em) - 0.015em), 0);
  }
}
@keyframes sway-d {
  0%,
  100% {
    transform: rotate(var(--tok-r, 0deg)) translate3d(0, var(--tok-y, 0em), 0);
  }
  50% {
    transform: rotate(calc(var(--tok-r, 0deg) - 1.2deg))
      translate3d(-0.5px, calc(var(--tok-y, 0em) - 0.01em), 0);
  }
}

/* ============================================================
   CHIPS / TAGS
   ============================================================ */

.chip {
  display: inline-block;
  position: relative;
  clip-path: var(--chip-clip-0, none);
  background: var(--chip-bg, var(--ink));
  color: var(--chip-fg, var(--paper));
  padding: 5px 10px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.35;
  transform: rotate(var(--chip-r, 0deg));
}

.chip::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  clip-path: var(--chip-clip-0, none);
  background: var(--ink);
  transform: translate3d(2px, 3px, 0);
}

.chip-cyan {
  --chip-bg: var(--cyan);
  --chip-fg: var(--ink);
}
.chip-crimson {
  --chip-bg: var(--crimson);
  --chip-fg: #fff;
}
.chip-mustard {
  --chip-bg: var(--mustard);
  --chip-fg: var(--ink);
}
.chip-white {
  --chip-bg: var(--card);
  --chip-fg: var(--ink);
}
.chip-teal {
  --chip-bg: var(--teal);
  --chip-fg: var(--paper);
}

/* Interactive chips need a real tap target. A static metadata tag can be 24px
   tall; a button a thumb has to hit cannot. */
button.chip,
a.chip {
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 7px 12px;
}

/* Interactive chips twitch through their three cuts; static ones do not, so a
   wall of metadata tags is not all animating under one pointer. */
button.chip:hover,
a.chip:hover,
button.chip:focus-visible,
a.chip:focus-visible {
  animation: chip-twitch var(--cycle) steps(1) infinite;
}
button.chip:hover::before,
a.chip:hover::before,
button.chip:focus-visible::before,
a.chip:focus-visible::before {
  animation: chip-clip-twitch var(--cycle) steps(1) infinite;
}

@keyframes chip-clip-twitch {
  0% {
    clip-path: var(--chip-clip-1, var(--chip-clip-0, none));
  }
  33% {
    clip-path: var(--chip-clip-2, var(--chip-clip-0, none));
  }
  66% {
    clip-path: var(--chip-clip-0, none);
  }
}

@keyframes chip-twitch {
  0% {
    clip-path: var(--chip-clip-1, var(--chip-clip-0, none));
    transform: rotate(calc(var(--chip-r, 0deg) + var(--chip-flip, 5deg)))
      translate3d(0, -2px, 0);
  }
  33% {
    clip-path: var(--chip-clip-2, var(--chip-clip-0, none));
    transform: rotate(calc(var(--chip-r, 0deg) - var(--chip-flip, 5deg)))
      translate3d(1px, 1px, 0);
  }
  66% {
    clip-path: var(--chip-clip-0, none);
    transform: rotate(calc(var(--chip-r, 0deg) + var(--chip-flip, 5deg) * 0.4))
      translate3d(-1px, -1px, 0);
  }
}

/* ============================================================
   TOP BAR
   ============================================================ */

.bar {
  position: sticky;
  top: 0;
  z-index: 60;
  transform: skewY(-0.7deg);
  background: var(--paper);
  border-bottom: 4px solid var(--ink);
  margin: 0 -24px;
  padding: 0 24px;
}

body.dark-canvas .bar {
  background: var(--cobalt);
  border-bottom-color: var(--cyan);
}

.bar::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 6px;
  opacity: 0.7;
  background-image: repeating-linear-gradient(
    45deg,
    var(--cyan) 0 5px,
    transparent 5px 12px
  );
}

.bar-row {
  transform: skewY(0.7deg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 0;
}

.bar-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  opacity: 0.7;
}

.lamp {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.lamp i {
  width: 9px;
  height: 9px;
  background: var(--crimson);
  border: 2px solid var(--ink);
  animation: lamp-blink 1.4s steps(1) infinite;
}
@keyframes lamp-blink {
  0%,
  70% {
    opacity: 1;
  }
  78% {
    opacity: 0.15;
  }
  86% {
    opacity: 1;
  }
  92% {
    opacity: 0.4;
  }
}

/* ============================================================
   LAYOUT
   ============================================================ */

main {
  position: relative;
  z-index: 10;
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 24px 96px;
}

.ghost {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  user-select: none;
  font-family: var(--font-display);
  font-size: clamp(150px, 30vw, 400px);
  line-height: 1;
  text-transform: uppercase;
  transform: skewY(-3deg);
  background-image: radial-gradient(rgba(10, 10, 10, 0.9) 2px, transparent 2.5px);
  background-size: 9px 9px;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-stroke: 2px rgba(10, 10, 10, 0.4);
  opacity: 0.4;
}

body.dark-canvas .ghost {
  background-image: radial-gradient(rgba(0, 210, 255, 0.9) 2px, transparent 2.5px);
  -webkit-text-stroke: 2px rgba(0, 210, 255, 0.35);
  opacity: 0.5;
}

.hero {
  position: relative;
  padding: 76px 0 40px;
}

.kicker {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--crimson);
}
.kicker::before {
  content: "";
  width: 28px;
  height: 6px;
  background: currentColor;
  clip-path: polygon(0 0, 100% 2px, calc(100% - 3px) 100%, 2px 100%);
}
body.dark-canvas .kicker {
  color: var(--cyan);
}

.hero-sub {
  margin-top: 22px;
  max-width: 58ch;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(18px, 2.3vw, 26px);
  line-height: 1.35;
  opacity: 0.88;
}

.counts {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 24px;
}

.zone {
  margin-top: 64px;
}

.zone-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  border-bottom: 5px solid var(--ink);
  padding-bottom: 10px;
}
body.dark-canvas .zone-head {
  border-bottom-color: var(--cyan);
}

.zone-count {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  opacity: 0.65;
  white-space: nowrap;
}

.zone-hint {
  margin-top: 12px;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(16px, 2vw, 20px);
  opacity: 0.78;
}

/* Zone reveal: stepped sheet drop, once, on intersection. */
@media (prefers-reduced-motion: no-preference) {
  .zone {
    opacity: 0;
    transform: translate3d(0, 26px, 0);
  }
  .zone.in-view {
    animation: sheet-drop 332ms steps(4, jump-none) forwards;
  }
}

@keyframes sheet-drop {
  0% {
    opacity: 0;
    transform: translate3d(0, 26px, 0) rotate(-1.2deg);
  }
  50% {
    opacity: 1;
    transform: translate3d(0, -6px, 0) rotate(0.7deg);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

/* ============================================================
   POLAROID

   Deliberately NOT cut paper: a print has four straight machine-trimmed edges, and
   the TAPE is what reads as handmade. Clipping the paper broke the tape - a strip
   has to overhang a real corner to look stuck on. Hover shifts the print against
   its fixed tape in discrete frames; nothing rotates or scales.
   ============================================================ */

.polaroid {
  position: relative;
  display: block;
  isolation: isolate;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  text-align: left;
  transform: rotate(var(--pola-tilt, 0deg)) translate3d(0, var(--pola-hang, 0px), 0);
  backface-visibility: hidden;
}

button.polaroid {
  cursor: zoom-in;
}

.polaroid-shadow {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  background: var(--ink);
  transform: translate3d(var(--pola-dx, 7px), var(--pola-dy, 8px), 0);
  pointer-events: none;
}

.polaroid-face {
  position: relative;
  z-index: 2;
  display: block;
  background: var(--card);
  border: 3px solid var(--ink);
  padding: 10px 10px 14px;
}

.polaroid-well {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--ink);
  aspect-ratio: var(--pola-ratio, 1 / 1);
}

.polaroid-well img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.28) brightness(1.02);
  transition: filter var(--frame) steps(1);
}

/* Colour returns on hover - the print "develops" rather than magnifying. */
.polaroid:hover .polaroid-well img {
  filter: none;
}

.tone-teal,
.tone-cyan,
.photo-grain {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
  transition: opacity var(--frame) steps(1);
}

.tone-teal {
  mix-blend-mode: multiply;
  background: var(--teal);
  opacity: 0.55;
}
.tone-cyan {
  mix-blend-mode: screen;
  background: var(--cyan);
  opacity: 0.42;
}
.polaroid:hover .tone-teal,
.polaroid:hover .tone-cyan {
  opacity: 0;
}

.photo-grain {
  mix-blend-mode: overlay;
  opacity: 0.35;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='pg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23pg)'/%3E%3C/svg%3E");
  background-size: 240px 240px;
}

.polaroid-caption {
  display: block;
  margin-top: 8px;
  min-height: 1.4em;
  font-family: var(--font-marker);
  font-size: 20px;
  line-height: 1.05;
  color: #1a1a17;
  transform: rotate(-1.4deg);
}

.polaroid-stamp {
  position: absolute;
  top: 15px;
  right: 16px;
  z-index: 4;
  padding: 2px 6px;
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  border: 2px solid var(--ink);
  background: var(--stamp-bg, var(--crimson));
  clip-path: polygon(3px 1px, 100% 0, calc(100% - 2px) 100%, 0 calc(100% - 3px));
}

/* Tape overhangs the paper edge; its own corners are snipped, so the tape is the
   torn element rather than the print. */
.polaroid-tape {
  position: absolute;
  top: -11px;
  z-index: 5;
  width: 72px;
  height: 22px;
  background: rgba(228, 222, 160, 0.72);
  border-left: 2px dashed rgba(10, 10, 10, 0.22);
  border-right: 2px dashed rgba(10, 10, 10, 0.22);
  clip-path: polygon(3px 2px, 100% 0, calc(100% - 4px) 100%, 0 calc(100% - 3px));
  transform: rotate(var(--tape-r, -38deg));
  pointer-events: none;
}
.polaroid-tape.left {
  left: -14px;
}
.polaroid-tape.right {
  right: -14px;
}

/* Hover: the print shifts against its tape at 12fps, tape stays put. Offsets only. */
.polaroid:hover,
.polaroid:focus-visible,
.polaroid:active {
  z-index: 6;
}
.polaroid:hover > .polaroid-face,
.polaroid:focus-visible > .polaroid-face,
.polaroid:active > .polaroid-face {
  animation: none;
}
.polaroid:hover > .polaroid-shadow,
.polaroid:focus-visible > .polaroid-shadow,
.polaroid:active > .polaroid-shadow {
  animation: none;
}

@keyframes pola-shift {
  0% { transform: translate3d(-2px, -2px, 0); }
  25% { transform: translate3d(1px, -3px, 0); }
  50% { transform: translate3d(-1px, -1px, 0); }
  75% { transform: translate3d(0, -2px, 0); }
}

@keyframes pola-shadow-shift {
  0% { transform: translate3d(calc(var(--pola-dx, 7px) + 4px), calc(var(--pola-dy, 8px) + 5px), 0); }
  25% { transform: translate3d(calc(var(--pola-dx, 7px) + 6px), calc(var(--pola-dy, 8px) + 2px), 0); }
  50% { transform: translate3d(calc(var(--pola-dx, 7px) + 3px), calc(var(--pola-dy, 8px) + 6px), 0); }
  75% { transform: translate3d(calc(var(--pola-dx, 7px) + 5px), calc(var(--pola-dy, 8px) + 4px), 0); }
}

/* ============================================================
   PAGE CURTAIN
   Same six-band stepped wipe as the React TransitionProvider, so moving between
   alibks.dev and a portal reads as one continuous transition.
   ============================================================ */

.page-wipe {
  position: fixed;
  inset: 0;
  z-index: 200;
  pointer-events: none;
  display: grid;
  grid-template-rows: repeat(6, 1fr);
  overflow: hidden;
}

.page-wipe span {
  display: block;
  transform: translate3d(-102%, 0, 0) skewX(-9deg);
  will-change: transform;
}

.page-wipe span:nth-child(1) {
  background: var(--ink);
  animation-delay: 0ms;
}
.page-wipe span:nth-child(2) {
  background: var(--crimson);
  animation-delay: 42ms;
}
.page-wipe span:nth-child(3) {
  background: var(--ink);
  animation-delay: 84ms;
}
.page-wipe span:nth-child(4) {
  background: var(--cyan);
  animation-delay: 126ms;
}
.page-wipe span:nth-child(5) {
  background: var(--ink);
  animation-delay: 168ms;
}
.page-wipe span:nth-child(6) {
  background: var(--mustard);
  animation-delay: 210ms;
}

.page-wipe.wipe-in span {
  animation-name: wipe-in;
  animation-duration: 415ms;
  animation-timing-function: steps(5, jump-none);
  animation-fill-mode: forwards;
}
.page-wipe.wipe-out span {
  animation-name: wipe-out;
  animation-duration: 332ms;
  animation-timing-function: steps(4, jump-none);
  animation-fill-mode: forwards;
}

@keyframes wipe-in {
  from {
    transform: translate3d(-102%, 0, 0) skewX(-9deg);
  }
  to {
    transform: translate3d(0, 0, 0) skewX(-9deg);
  }
}
@keyframes wipe-out {
  from {
    transform: translate3d(0, 0, 0) skewX(-9deg);
  }
  to {
    transform: translate3d(102%, 0, 0) skewX(-9deg);
  }
}

/* Arrival: the portal fades up from under the curtain on first paint. */
.arrive {
  animation: sheet-drop 332ms steps(4, jump-none) both;
}

/* ============================================================
   DECO MARKS
   ============================================================ */

.deco {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.deco span {
  position: absolute;
  display: block;
  animation: deco-drift 6s steps(3, jump-none) infinite;
  transform: rotate(var(--deco-r, 0deg));
  backface-visibility: hidden;
}

@keyframes deco-drift {
  0%,
  100% {
    transform: rotate(var(--deco-r, 0deg)) translate3d(0, 0, 0);
  }
  50% {
    transform: rotate(calc(var(--deco-r, 0deg) + 6deg))
      translate3d(0, calc(var(--deco-d, 8px) * -1), 0);
  }
}

/* ============================================================
   BUTTONS / CTA
   ============================================================ */

.cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
}

.btn-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 16px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* ============================================================
   FOOTER
   ============================================================ */

.portal-footer {
  margin-top: 80px;
  transform: skewY(-0.6deg);
  border-top: 5px solid var(--ink);
  background: var(--ink);
  color: rgba(244, 239, 234, 0.75);
}
body.dark-canvas .portal-footer {
  border-top-color: var(--cyan);
}

.portal-footer > div {
  transform: skewY(0.6deg);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 0;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

.portal-footer a {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  transition: color var(--frame) steps(1);
}
.portal-footer a:hover {
  color: var(--cyan);
}

/* ============================================================
   SKELETONS
   ============================================================ */

.skel-row {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin-top: 22px;
}

.skel {
  display: block;
  position: relative;
  overflow: hidden;
  flex: 1 1 200px;
  background: var(--paper-2);
  border: 3px solid var(--ink);
  clip-path: polygon(5px 0, 100% 4px, calc(100% - 3px) 100%, 0 calc(100% - 5px));
}
body.dark-canvas .skel {
  background: var(--cobalt-2);
  border-color: var(--cyan);
}

.skel::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    45deg,
    transparent 0 14px,
    rgba(0, 210, 255, 0.14) 14px 18px
  );
  animation: skel-slide 664ms steps(6) infinite;
}

@keyframes skel-slide {
  to {
    transform: translate3d(96px, 0, 0);
  }
}

.skel-print {
  aspect-ratio: 3 / 4;
}
.skel-tag {
  height: 132px;
  max-width: 250px;
}
.skel-order {
  height: 128px;
  flex-basis: 100%;
}
.skel-line {
  height: 62px;
  flex-basis: 100%;
}

.empty {
  padding: 44px 0;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.6;
}

/* ============================================================
   MOBILE
   Same amplitude reduction as the main site: the polygons stay, the tilt halves.
   ============================================================ */

@media (max-width: 767px) {
  main {
    padding: 0 14px 64px;
  }
  .bar {
    margin: 0 -14px;
    padding: 0 14px;
  }
  /* Amplitude, not structure: the polygons and twitch keyframes are untouched,
     they just tilt and cast half as far. Overriding the transforms directly here
     would clobber the keyframed ones and kill the twitch on mobile entirely. */
  :root {
    --tilt-mul: 0.5;
    --depth-mul: 0.7;
  }
  .ransom-idle .ransom-token {
    animation: none;
  }
  .grain {
    opacity: 0.05;
    animation-duration: 996ms;
  }
  .deco {
    display: none;
  }
  .bar-meta {
    display: none;
  }
  .page-wipe {
    grid-template-rows: repeat(4, 1fr);
  }
}

/* Coarse pointers.
   The :hover rules already include :active, so touch gets the twitch for as long as
   a finger is down. This block only drops :focus-visible, which on touch fires on
   tap and would leave a surface twitching after the finger lifts. */
@media (hover: none) {
  .cut-twitch:focus-visible > .cut-face,
  .cut-twitch:focus-visible > .cut-face > i,
  .cut-twitch:focus-visible > .cut-backdrop,
  .cut-twitch:focus-visible > .cut-shadow,
  .ransom:focus-visible .ransom-key,
  .ransom:focus-visible .ransom-fill,
  .polaroid:focus-visible > .polaroid-face,
  .polaroid:focus-visible > .polaroid-shadow {
    animation: none;
  }
}

/* ============================================================
   REDUCED MOTION
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .grain,
  .deco span {
    animation: none;
  }
  .page-wipe {
    display: none;
  }
  .zone {
    opacity: 1;
    transform: none;
  }
}
