﻿/* The headline font, Rubik Vinyl (SIL Open Font License; see static/fonts/rubik-vinyl/OFL.txt).
   Only the Latin letters are included. Bagel Fat One (style 99) is kept in static/fonts to switch back. */
@font-face {
  font-family: "Rubik Vinyl"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/rubik-vinyl/rubik-vinyl-latin.432d4d8c0169.woff2") format("woff2");
}

/* Home page only: the intro, the picture stage and the ruler slider. */

/* ---------- Home: the intro ---------- */
.intro { display: none; }
html.js .intro.is-playing {
  display: grid; position: fixed; inset: 0; z-index: 100;
  grid-template-columns: 1fr minmax(16rem, 30rem) 1fr; gap: clamp(1rem, 4vw, 4rem); align-items: center;
  padding: clamp(1rem, 3vw, 2.5rem); background: var(--cotton); cursor: pointer;
  font-family: var(--display); text-transform: uppercase; font-size: .9rem;
  transition: opacity .6s ease, transform .7s cubic-bezier(.7, 0, .2, 1);
}
.intro.is-done { opacity: 0; transform: translateY(-4%); pointer-events: none; }
.intro-list p { display: flex; justify-content: space-between; gap: 1rem; margin: 0 0 .45rem; color: var(--ink-soft);
  animation: ty-rise .5s ease both; }
.intro-list p:nth-child(even) { animation-delay: .06s; }
.intro-list .intro-head { color: var(--ink); font-weight: 700; margin-bottom: 1rem; }
.intro-list.is-right p { justify-content: flex-start; }
.intro-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .6rem; }
.intro-grid span { aspect-ratio: 3 / 4; overflow: hidden; background: var(--paper); animation: ty-pop .35s ease both; }
.intro-grid span:nth-child(3n+2) { animation-delay: .08s; }
.intro-grid span:nth-child(3n) { animation-delay: .16s; }
.intro-grid img { width: 100%; height: 100%; object-fit: cover; display: block; }
.intro-logo { position: absolute; left: 50%; bottom: clamp(1rem, 4vh, 2.5rem); transform: translateX(-50%); width: min(70vw, 320px); }
@keyframes ty-rise { from { opacity: 0; transform: translateY(.6rem); } }
@keyframes ty-pop { from { opacity: 0; transform: scale(.9); } }
@media (max-width: 760px) {
  html.js .intro.is-playing { grid-template-columns: 1fr; align-content: center; }
  .intro-list { display: none; }
  .intro-grid { width: min(80vw, 22rem); margin: 0 auto; }
}


/* ---------- Layout ---------- */
.page-home main { max-width: none; padding: 0; }
.page-home main > section:not(.home-stage) { max-width: 72rem; margin-left: auto; margin-right: auto; padding-left: clamp(1rem, 4vw, 3rem); padding-right: clamp(1rem, 4vw, 3rem); }
.home-stage {
  /* Fills the screen under the header; the ruler sits at the very bottom, the search box below the fold.
     --room is the height left once the header, headline, joystick and ruler are counted. The video and
     the two cards share one height; it also shrinks so the three fit side by side. */
  --title-fs: clamp(2.08rem, min(4.42vw, 7.8vh), 4.42rem);  /* APPAREL / HISTORY */
  --title-gap: clamp(0rem, 1vh, 1rem);
  --stage-h: max(24rem, calc(100svh - 16.5rem));  /* header, joystick row and ruler take the rest */
  /* the side cards' height; --scenery-room (set by mario-vine.js) keeps the left card clear of Mario's platform */
  --media-h: calc(min(var(--stage-h) * .7, (100vw - 4rem) / 3.3) - var(--scenery-room, 0px));
  position: relative; isolation: isolate;
  display: flex; flex-direction: column; min-height: calc(100vh - 4.9rem); min-height: calc(100svh - 4.9rem);
  padding: clamp(.4rem, 1.8vh, 1.5rem) 0 0; overflow: hidden;
}
/* Desktop: the whole stage, the slider included, fits the window as the page opens. The header is 7rem tall
   there; the headline's gap, the title line under the cards and the slider take 11rem plus a little more on
   wider screens (their text grows with the window). It ends 10px above the window's bottom, so a sliver of the
   gap under the slider shows (two rows of the background's dots), a sign the page goes on below. */
@media (min-width: 768px) {
  .home-stage { --stage-h: max(24rem, calc(100svh - 18rem - 1vw - 10px)); min-height: calc(100svh - 7rem - 10px); }
}
.home-title, .cat-meta { padding: 0 clamp(1rem, 3vw, 2.25rem); }
/* "Clothing" upper left, "Creates connection" upper right. */
.home-title {
  /* Over the top corners of the stage, so the video can start right at the top. */
  position: absolute; z-index: 4; top: clamp(.4rem, 1.8vh, 1.5rem); left: 0; right: 0; pointer-events: none;
  display: grid; gap: .1em; margin: 0;
  font-family: var(--display); text-transform: uppercase; font-weight: 700;
  font-size: var(--title-fs); line-height: .92; letter-spacing: -.01em;
}
.ht-row { display: block; }
.ht-row.is-right { text-align: right; }
/* "where" above APPAREL, "meets" above CULTURE: small, in the site's regular font. */
.ht-lead {
  display: block; margin: 0 0 .15rem; color: var(--ink-soft);
  font: 700 clamp(.8rem, 1.1vw, 1rem)/1.2 var(--display); text-transform: uppercase; letter-spacing: .12em;
}
/* "APPAREL" and "CULTURE": cream letters with record grooves inside (style 7, Vinyl). */
.ht-row > span {
  display: inline-block; animation: ty-rise .8s cubic-bezier(.2, .7, .2, 1) both;
  font-family: "Rubik Vinyl", var(--display); font-weight: 400; letter-spacing: 0;
  color: var(--ink);  /* dark on the light theme, cream on the dark theme */
}
.ht-row.is-right > span { animation-delay: .12s; }

/* ---------- The picture stage: left card, video, right card ---------- */
.stage {
  --card-h: var(--media-h);
  --card-w: calc(var(--card-h) * .75);
  --card-turn: 6deg;  /* the cards lean outwards from the corner where they touch the video */
  position: relative; flex: 1 0 auto; height: var(--stage-h); margin: 0 auto; width: 100%;
  user-select: none; -webkit-user-select: none; touch-action: pan-y;
}
.cat-meta, .ruler { position: relative; z-index: 5; }
.stage-link { position: relative; display: block; color: inherit; width: 100%; height: 100%; }
.stack {
  position: absolute; inset: 0; visibility: hidden; z-index: 1;
  /* Cards and video sit in the middle of the space; the bottom padding keeps room for the card titles. */
  /* The video column starts at the very top; the side cards sit along the bottom, level with the card under the video. */
  display: flex; justify-content: center; align-items: flex-end; padding: 0 0 3rem;
}
.stack.is-active { visibility: visible; z-index: 3; }
.stack.is-previous { visibility: visible; z-index: 2; }

/* The frame takes the video's own shape, so there are no black borders and nothing is cropped. */
/* The video is 35% smaller than the cards and sits level with their middles. */
.stack-video {
  position: relative; z-index: 2; flex: none; height: calc(var(--media-h) * .65);
  pointer-events: none; box-shadow: 0 14px 40px rgba(0, 0, 0, .35); line-height: 0;
}
.stack-video video, .stack-video img { display: block; height: 100%; width: auto; max-width: 100vw; pointer-events: none; }
.stack-video img { image-rendering: pixelated; }
.stack-video img.video-still { image-rendering: auto; }
.stack-video [hidden] { display: none !important; }

.card { position: relative; z-index: 1; flex: none; width: var(--card-w); height: var(--card-h); }

/* The middle column: the video, then a smaller card under it. */
.stack-middle { position: relative; z-index: 2; flex: none; display: flex; flex-direction: column; align-items: center;
  align-self: flex-start; }
/* The card under the video: as big as the space allows, still portrait. */
.card.is-bottom { --b-h: calc(var(--stage-h) - var(--media-h) * .65 - 4.6rem); width: calc(var(--b-h) * .75); height: var(--b-h);
  margin-top: 1rem; }
.card.is-left { transform-origin: 100% 100%; transform: rotate(calc(-1 * var(--card-turn))); }
.card.is-right { transform-origin: 0 100%; transform: rotate(var(--card-turn)); }
.card-face {
  position: absolute; inset: 0; overflow: hidden; background: var(--paper);
  border: 4px solid var(--card-edge, var(--paper)); border-radius: 6px; box-shadow: 0 10px 26px rgba(0, 0, 0, .45);
}
.card-face img { display: block; width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
/* The entry's title under its card (two lines at most). */
.card-title {
  position: absolute; top: calc(100% + .6rem); left: 0; right: 0; text-align: center; color: var(--ink);
  /* Shrinks with the cards on narrow screens. */
  font: 700 clamp(.78rem, calc(var(--media-h) * .0585), 1.17rem)/1.25 var(--display); text-transform: uppercase; letter-spacing: .02em;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
.card-tag, .sample-tag {
  position: absolute; z-index: 3; left: .45rem; top: .45rem; padding: .1rem .4rem;
  background: rgba(0, 0, 0, .55); color: #fff; font: 700 .65rem/1.4 var(--display); text-transform: uppercase; letter-spacing: .05em;
}
.sample-tag { top: auto; bottom: .5rem; left: 50%; transform: translateX(-50%); font-size: .7rem; }
/* The video's caption underneath it, twice the size of the card titles. */
.video-title {
  position: absolute; z-index: 4; top: calc(100% + .7rem); left: 0; right: 0; text-align: center; color: var(--ink);
  font: 700 clamp(.63rem, calc(var(--media-h) * .063), 1.26rem)/1.1 var(--display); text-transform: uppercase; letter-spacing: .02em;
}

/* ---------- Card shapes: 1 rounded card (the main site); 2 circle and 3 oval via ?shape=2 or 3 ----------
   For the cut-out shapes, the paper edge is the card's background showing round the photo, and the
   shadow comes from the card itself (a cut-out shape can't have a normal box shadow). */
.card-cut .card { filter: drop-shadow(0 10px 16px rgba(0, 0, 0, .5)); }
.card-cut .card-face { box-shadow: none; border: 0; border-radius: 0; padding: 7px; }

/* 1 Rounded card: the speech bubble without its tail (the main site; the entry page uses it too). */
.card-shape-1 .card-face { border: var(--frame-w) solid var(--card-edge, var(--paper)); border-radius: 16px; }
.card-shape-1 .card-face img { border-radius: 10px; }
/* 2 Circle: a round badge. */
.card-shape-2 .card-face, .card-shape-2 .card-face img { clip-path: circle(closest-side); }
/* 3 Oval: a portrait frame. */
.card-shape-3 .card-face { border-radius: 50%; border-width: 6px; }

/* ---------- Switching consoles: 10 animation styles to compare (?anim=1 to 10 on the home page) ----------
   The new console's video and cards come in (.is-entering); the old ones play a short exit (.is-previous)
   and are then hidden for good. --dir is 1 going forwards and -1 going back, for the sideways styles. */
.stage { --dir: 1; --enter: .7s; --ease: cubic-bezier(.2, .7, .2, 1); }
.stage[data-dir="prev"] { --dir: -1; }
.stack.is-entering .stack-video, .stack.is-entering .card-face { animation-duration: var(--enter); animation-fill-mode: both; animation-timing-function: var(--ease); }
.stack.is-entering .card-face { animation-delay: calc(.1s + var(--deal) * .06s); }
.stack.is-previous .stack-video, .stack.is-previous .card-face { animation: leave-fade .45s ease both; }
@keyframes leave-fade { to { opacity: 0; } }

/* 1 Drop (the original): the video is uncovered top to bottom and the cards are dealt from above. */
.anim-1 .stack.is-entering .stack-video { animation-name: reveal-down; animation-timing-function: cubic-bezier(.65, 0, .25, 1); }
.anim-1 .stack.is-entering .card-face { animation-name: from-above; }
@keyframes reveal-down { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes from-above { from { opacity: 0; transform: translateY(-45%) rotate(-6deg) scale(.9); } }

/* 2 Crossfade: the new fades in over the old. */
.anim-2 .stack.is-entering .stack-video, .anim-2 .stack.is-entering .card-face { animation-name: fade-in; animation-delay: 0s; }
@keyframes fade-in { from { opacity: 0; } }

/* 3 Slide: everything slides in from the side you're heading towards; the old slides out the other way. */
.anim-3 .stack.is-entering .stack-video, .anim-3 .stack.is-entering .card-face { animation-name: slide-in; }
.anim-3 .stack.is-previous .stack-video, .anim-3 .stack.is-previous .card-face { animation-name: slide-out; }
@keyframes slide-in { from { opacity: 0; transform: translateX(calc(var(--dir) * 60vw)); } }
@keyframes slide-out { to { opacity: 0; transform: translateX(calc(var(--dir) * -60vw)); } }

/* 4 Zoom: the new grows into place; the old swells and fades. */
.anim-4 .stack.is-entering .stack-video, .anim-4 .stack.is-entering .card-face { animation-name: zoom-in; }
.anim-4 .stack.is-previous .stack-video, .anim-4 .stack.is-previous .card-face { animation-name: zoom-out; }
@keyframes zoom-in { from { opacity: 0; transform: scale(.6); } }
@keyframes zoom-out { to { opacity: 0; transform: scale(1.15); } }

/* 5 Flip: cards turn over like playing cards. */
.anim-5 .stack.is-entering .stack-video { animation-name: fade-in; }
.anim-5 .stack.is-entering .card-face { animation-name: flip-in; backface-visibility: hidden; }
.anim-5 .stack.is-previous .card-face { animation-name: flip-out; }
@keyframes flip-in { from { opacity: 0; transform: perspective(900px) rotateY(calc(var(--dir) * 90deg)); } }
@keyframes flip-out { to { opacity: 0; transform: perspective(900px) rotateY(calc(var(--dir) * -90deg)); } }

/* 6 Deal from below: cards rise up one by one; the old drop away. */
.anim-6 .stack.is-entering .stack-video { animation-name: fade-in; }
.anim-6 .stack.is-entering .card-face { animation-name: from-below; animation-delay: calc(.05s + var(--deal) * .12s); }
.anim-6 .stack.is-previous .card-face { animation-name: drop-away; }
@keyframes from-below { from { opacity: 0; transform: translateY(70%) rotate(8deg); } }
@keyframes drop-away { to { opacity: 0; transform: translateY(40%) rotate(-6deg); } }

/* 7 Spin: cards spin in from small; the old spin away. */
.anim-7 .stack.is-entering .stack-video { animation-name: zoom-in; }
.anim-7 .stack.is-entering .card-face { animation-name: spin-in; }
.anim-7 .stack.is-previous .card-face { animation-name: spin-out; }
@keyframes spin-in { from { opacity: 0; transform: rotate(calc(var(--dir) * -200deg)) scale(.3); } }
@keyframes spin-out { to { opacity: 0; transform: rotate(calc(var(--dir) * 120deg)) scale(.4); } }

/* 8 Blur: the new comes into focus; the old goes out of focus. */
.anim-8 .stack.is-entering .stack-video, .anim-8 .stack.is-entering .card-face { animation-name: focus-in; }
.anim-8 .stack.is-previous .stack-video, .anim-8 .stack.is-previous .card-face { animation-name: focus-out; }
@keyframes focus-in { from { opacity: 0; filter: blur(18px); } }
@keyframes focus-out { to { opacity: 0; filter: blur(18px); } }

/* 9 Bounce: cards drop in and bounce to a stop. */
.anim-9 .stack.is-entering .stack-video { animation-name: fade-in; }
.anim-9 .stack.is-entering .card-face { animation-name: bounce-in; animation-timing-function: linear; animation-duration: .9s; }
@keyframes bounce-in {
  0% { opacity: 0; transform: translateY(-80%); }
  50% { opacity: 1; transform: translateY(0); }
  65% { transform: translateY(-12%); }
  80% { transform: translateY(0); }
  90% { transform: translateY(-4%); }
  100% { transform: translateY(0); }
}

/* 10 VHS glitch: the picture jumps and shifts colour like a tape changing channel. */
.anim-10 .stack.is-entering .stack-video, .anim-10 .stack.is-entering .card-face { animation-name: glitch-in; animation-timing-function: steps(1, end); animation-duration: .6s; }
.anim-10 .stack.is-previous .stack-video, .anim-10 .stack.is-previous .card-face { animation-name: glitch-out; animation-timing-function: steps(1, end); animation-duration: .35s; }
@keyframes glitch-in {
  0% { opacity: 0; }
  15% { opacity: 1; transform: translate(-8px, 3px) skewX(-8deg); filter: hue-rotate(90deg) saturate(3); clip-path: inset(10% 0 55% 0); }
  30% { transform: translate(10px, -2px); filter: hue-rotate(-60deg) contrast(1.6); clip-path: inset(50% 0 20% 0); }
  45% { transform: translate(-4px, 0) skewX(6deg); filter: none; clip-path: inset(0 0 70% 0); }
  60% { transform: translate(5px, 2px); filter: hue-rotate(180deg); clip-path: inset(30% 0 0 0); }
  80% { transform: none; filter: contrast(1.4); clip-path: inset(0 0 0 0); }
  100% { transform: none; filter: none; clip-path: inset(0 0 0 0); }
}
@keyframes glitch-out {
  0% { transform: translate(6px, 0); filter: hue-rotate(120deg); clip-path: inset(20% 0 40% 0); }
  50% { transform: translate(-8px, 2px); clip-path: inset(60% 0 0 0); }
  100% { opacity: 0; }
}

.stack.is-initial .stack-video { animation-duration: 1.1s; animation-delay: .15s; }
/* Only the cards are links; the empty space around them isn't. */
.card[data-url] { cursor: pointer; }

/* ---------- The ruler ---------- */
.ruler {
  --tick: var(--ink);
  --ruler-x: 0px;
  position: relative; margin-top: .4rem; padding: 1.1rem 0;
  overflow: hidden; cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none; outline-offset: -3px;
}
.ruler::before, .ruler::after {
  content: ""; position: absolute; left: 0; right: 0; height: 14px; pointer-events: none; opacity: .75;
  background-image:
    repeating-linear-gradient(90deg, var(--tick) 0 1px, transparent 1px 48px),
    repeating-linear-gradient(90deg, var(--tick) 0 1px, transparent 1px 8px);
  background-size: 100% 14px, 100% 7px;
  background-repeat: repeat-x;
  background-position: var(--ruler-x) 0, var(--ruler-x) 0;
  transition: opacity .2s ease;
}
.ruler::before { top: 0; }
.ruler::after { bottom: 0; transform: scaleY(-1); }
.ruler.is-grabbed { cursor: grabbing; }
.ruler.is-grabbed::before, .ruler.is-grabbed::after { opacity: 1; }
.ruler-marker {
  position: absolute; z-index: 2; left: 50%; top: 0; bottom: 0; width: 2px; margin-left: -1px;
  background: var(--red); pointer-events: none; opacity: .9;
}
.ruler-marker::before, .ruler-marker::after {
  content: ""; position: absolute; left: 50%; width: 0; height: 0; margin-left: -6px;
  border-left: 6px solid transparent; border-right: 6px solid transparent;
}
.ruler-marker::before { top: 0; border-top: 8px solid var(--red); }
.ruler-marker::after { bottom: 0; border-bottom: 8px solid var(--red); }
.ruler-track { display: flex; gap: clamp(1.6rem, 4vw, 4rem); width: max-content; will-change: transform; }
html:not(.js) .ruler { overflow-x: auto; }
html:not(.js) .ruler-track { padding: 0 1rem; }
.ruler-name {
  padding: 0; border: 0; background: none; color: var(--ink); cursor: inherit; white-space: nowrap;
  font: 700 clamp(1.08rem, 2.52vw, 2.4rem)/1 var(--display); text-transform: uppercase;
  opacity: .22; transition: opacity .35s ease, transform .35s ease;
}
.ruler-name.is-active { opacity: 1; }
.ruler.is-grabbed .ruler-name { transform: scale(.96); }
.ruler-name:focus-visible { outline-offset: 4px; }

/* ---------- Under the ruler ---------- */
.cat-meta {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem;
  margin-top: .25rem; font-family: var(--display); text-transform: uppercase; font-weight: 700;
}

/* ---------- The joystick ---------- */
.joystick { --tilt: 0; display: flex; justify-content: center; line-height: 0; }
.joystick svg { width: clamp(84px, 8vw, 120px); height: auto; overflow: visible; }
.js-shadow { fill: rgba(0, 0, 0, .22); }
.js-stick {
  transform-box: view-box; transform-origin: 60px 66px;
  /* --tilt: leans left / right with the names. */
  transform: translateX(calc(var(--tilt) * 15px)) rotate(calc(var(--tilt) * 20deg));
}
/* --tilt-y (-1 to 1): the page scrolling tips the stick towards you (down: more of the ringed top shows and
   the cap hides the shaft) or away (up: the top flattens, more of the cap's side and the shaft show). */
.js-cap, .js-cap-face, .js-cap-side, .js-shaft { transform-box: view-box; }
.js-cap { transform-origin: 60px 36px; transform: translateY(calc(var(--tilt-y, 0) * 8px)); }
.js-cap-face { transform-origin: 60px 33px; transform: scaleY(calc(1 + var(--tilt-y, 0) * .55)); }
.js-cap-side { transform-origin: 60px 33px; transform: scaleY(calc(1 - var(--tilt-y, 0) * .6)); }
.js-shaft { transform-origin: 60px 68px; transform: scaleY(calc(1 - var(--tilt-y, 0) * .3)); }
.joystick.is-held .js-stick, .is-dragging .js-stick { filter: brightness(.96); }
.cat-meta .joystick { grid-column: 2; }
.cat-all { justify-self: end; color: var(--ink); text-decoration: none; font-size: 1.05rem; }
.cat-all:hover { color: var(--red); }
.cat-all sup { font-size: .68em; color: var(--ink-soft); }

.home-search { padding-top: 2.5rem; padding-bottom: 1rem; text-align: center; }
.home-search .note { margin-top: .9rem; color: var(--ink-soft); }


/* ---------- 11 to 20: old CRT television static (?anim=11 to 20) ----------
   Every card and the video carry a layer of TV "snow" (drawn below, no image file) that's normally invisible.
   The old pictures sink into the static, then the new ones come out of it. */
.stage.is-crt .card-face::after,
.stage.is-crt .stack-video::after {
  content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none; opacity: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1.4 -0.2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E"),
    repeating-linear-gradient(0deg, rgba(0, 0, 0, .28) 0 1px, transparent 1px 3px);
  background-color: #6d6d6d; background-size: 160px 160px, 100% 3px; mix-blend-mode: normal;
}
.stage.is-crt .stack-video { overflow: hidden; }
/* The snow keeps moving while it's showing. */
@keyframes snow { 0% { background-position: 0 0, 0 0; } 25% { background-position: -40px 30px, 0 1px; }
  50% { background-position: 50px -20px, 0 2px; } 75% { background-position: -10px -50px, 0 1px; } 100% { background-position: 30px 40px, 0 0; } }

/* 11 Static burst: the picture is swallowed by static, the new one surfaces from it. */
.anim-11 .stack.is-previous .card-face::after, .anim-11 .stack.is-previous .stack-video::after { animation: snow .12s steps(4) infinite, fill-in .35s ease forwards; }
.anim-11 .stack.is-entering .card-face::after, .anim-11 .stack.is-entering .stack-video::after { animation: snow .12s steps(4) infinite, clear-out .7s ease .1s backwards; opacity: 0; }
.anim-11 .stack.is-entering .stack-video, .anim-11 .stack.is-entering .card-face { animation-name: none; }
@keyframes fill-in { to { opacity: 1; } }
@keyframes clear-out { from { opacity: 1; } to { opacity: 0; } }

/* 12 Channel roll: the picture rolls up through static, the next channel rolls in. */
.anim-12 .stack.is-previous .card-face, .anim-12 .stack.is-previous .stack-video { animation: roll-out .4s ease-in both; }
.anim-12 .stack.is-previous .card-face::after, .anim-12 .stack.is-previous .stack-video::after { animation: snow .1s steps(3) infinite, fill-in .25s linear forwards; }
.anim-12 .stack.is-entering .card-face, .anim-12 .stack.is-entering .stack-video { animation-name: roll-in; animation-duration: .55s; }
.anim-12 .stack.is-entering .card-face::after, .anim-12 .stack.is-entering .stack-video::after { animation: snow .1s steps(3) infinite, clear-out .55s ease backwards; }
@keyframes roll-out { to { transform: translateY(-35%); opacity: 0; } }
@keyframes roll-in { from { transform: translateY(35%); } }

/* 13 Power off / on: the picture collapses to a bright line, then the new one opens out of static. */
.anim-13 .stack.is-previous .card-face, .anim-13 .stack.is-previous .stack-video { animation: tv-off .4s ease-in both; }
.anim-13 .stack.is-entering .card-face, .anim-13 .stack.is-entering .stack-video { animation-name: tv-on; animation-duration: .6s; }
.anim-13 .stack.is-entering .card-face::after, .anim-13 .stack.is-entering .stack-video::after { animation: snow .1s steps(3) infinite, clear-out .6s ease .15s backwards; }
@keyframes tv-off { 60% { transform: scaleY(.02); filter: brightness(3); } 100% { transform: scale(0, .02); filter: brightness(4); opacity: 0; } }
@keyframes tv-on { 0% { transform: scale(0, .02); filter: brightness(4); } 45% { transform: scaleY(.02); filter: brightness(3); } 100% { transform: none; filter: none; } }

/* 14 Snowfall: heavy snow that slowly thins out to reveal the new picture. */
.anim-14 .stack.is-previous .card-face::after, .anim-14 .stack.is-previous .stack-video::after { animation: snow .15s steps(4) infinite, fill-in .3s linear forwards; }
.anim-14 .stack.is-entering .stack-video, .anim-14 .stack.is-entering .card-face { animation-name: none; }
.anim-14 .stack.is-entering .card-face::after, .anim-14 .stack.is-entering .stack-video::after { animation: snow .15s steps(4) infinite, thin-out 1.2s ease-out backwards; }
@keyframes thin-out { 0% { opacity: 1; } 40% { opacity: .85; } 100% { opacity: 0; } }

/* 15 Scan wipe: a band of static sweeps down the screen, leaving the new picture behind it. */
.anim-15 .stack.is-entering .card-face, .anim-15 .stack.is-entering .stack-video { animation-name: wipe-down; animation-duration: .7s; animation-timing-function: linear; }
.anim-15 .stack.is-entering .card-face::after, .anim-15 .stack.is-entering .stack-video::after { opacity: 1; animation: snow .1s steps(3) infinite, band .7s linear forwards; }
.anim-15 .stack.is-previous .card-face::after, .anim-15 .stack.is-previous .stack-video::after { animation: snow .1s steps(3) infinite, fill-in .3s linear forwards; }
@keyframes wipe-down { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } }
@keyframes band { 0% { clip-path: inset(0 0 85% 0); } 100% { clip-path: inset(100% 0 0 0); } }

/* 16 Tearing: the picture shears sideways in strips of static before the new one locks in. */
.anim-16 .stack.is-previous .card-face, .anim-16 .stack.is-previous .stack-video { animation: tear .35s steps(1, end) both; }
.anim-16 .stack.is-previous .card-face::after, .anim-16 .stack.is-previous .stack-video::after { animation: snow .1s steps(3) infinite, fill-in .35s steps(3) forwards; }
.anim-16 .stack.is-entering .card-face, .anim-16 .stack.is-entering .stack-video { animation-name: tear-in; animation-duration: .6s; animation-timing-function: steps(1, end); }
.anim-16 .stack.is-entering .card-face::after, .anim-16 .stack.is-entering .stack-video::after { animation: snow .1s steps(3) infinite, clear-out .6s steps(4) backwards; }
@keyframes tear { 0% { clip-path: inset(0 0 60% 0); transform: translateX(8%); } 33% { clip-path: inset(30% 0 30% 0); transform: translateX(-10%); }
  66% { clip-path: inset(60% 0 0 0); transform: translateX(6%); } 100% { opacity: 0; } }
@keyframes tear-in { 0% { transform: translateX(-9%); clip-path: inset(50% 0 20% 0); } 25% { transform: translateX(7%); clip-path: inset(0 0 55% 0); }
  50% { transform: translateX(-4%); clip-path: inset(20% 0 0 0); } 75% { transform: translateX(2%); clip-path: inset(0); } 100% { transform: none; clip-path: inset(0); } }

/* 17 Flicker: the picture blinks between itself and static before settling. */
.anim-17 .stack.is-previous .card-face::after, .anim-17 .stack.is-previous .stack-video::after { animation: snow .1s steps(3) infinite, blink-on .4s steps(1, end) forwards; }
.anim-17 .stack.is-entering .stack-video, .anim-17 .stack.is-entering .card-face { animation-name: none; }
.anim-17 .stack.is-entering .card-face::after, .anim-17 .stack.is-entering .stack-video::after { animation: snow .1s steps(3) infinite, blink-off .8s steps(1, end) backwards; }
@keyframes blink-on { 0% { opacity: 0; } 20% { opacity: 1; } 40% { opacity: 0; } 60% { opacity: 1; } 80% { opacity: .3; } 100% { opacity: 1; } }
@keyframes blink-off { 0% { opacity: 1; } 15% { opacity: 0; } 30% { opacity: 1; } 50% { opacity: .2; } 65% { opacity: 1; } 80% { opacity: 0; } 90% { opacity: .5; } 100% { opacity: 0; } }

/* 18 Degauss: a bright flash and a wobble of colour, through static, as the new picture appears. */
.anim-18 .stack.is-previous .card-face::after, .anim-18 .stack.is-previous .stack-video::after { animation: snow .1s steps(3) infinite, fill-in .3s ease forwards; }
.anim-18 .stack.is-entering .card-face, .anim-18 .stack.is-entering .stack-video { animation-name: degauss; animation-duration: .9s; }
.anim-18 .stack.is-entering .card-face::after, .anim-18 .stack.is-entering .stack-video::after { animation: snow .1s steps(3) infinite, clear-out .5s ease backwards; }
@keyframes degauss { 0% { filter: brightness(3) hue-rotate(0deg); transform: scale(1.04) skewX(3deg); }
  30% { filter: brightness(1.6) hue-rotate(140deg) saturate(2); transform: scale(.98) skewX(-3deg); }
  60% { filter: hue-rotate(-60deg); transform: skewX(1.5deg); } 100% { filter: none; transform: none; } }

/* 19 Vertical hold: the picture slips and jumps up the screen in static before it catches. */
.anim-19 .stack.is-previous .card-face, .anim-19 .stack.is-previous .stack-video { animation: slip-out .4s steps(4) both; }
.anim-19 .stack.is-previous .card-face::after, .anim-19 .stack.is-previous .stack-video::after { animation: snow .1s steps(3) infinite, fill-in .4s linear forwards; }
.anim-19 .stack.is-entering .card-face, .anim-19 .stack.is-entering .stack-video { animation-name: hold; animation-duration: .8s; animation-timing-function: steps(8, end); }
.anim-19 .stack.is-entering .card-face::after, .anim-19 .stack.is-entering .stack-video::after { animation: snow .1s steps(3) infinite, clear-out .8s steps(6) backwards; }
@keyframes slip-out { to { transform: translateY(-60%); opacity: 0; } }
@keyframes hold { 0% { transform: translateY(70%); } 25% { transform: translateY(-40%); } 50% { transform: translateY(20%); }
  75% { transform: translateY(-6%); } 100% { transform: none; } }

/* 20 Tuning in: the static slowly gives way as the new picture is "tuned in", fuzzy at first. */
.anim-20 .stack.is-previous .card-face::after, .anim-20 .stack.is-previous .stack-video::after { animation: snow .15s steps(4) infinite, fill-in .4s ease forwards; }
.anim-20 .stack.is-entering .card-face, .anim-20 .stack.is-entering .stack-video { animation-name: tune-in; animation-duration: 1.1s; }
.anim-20 .stack.is-entering .card-face::after, .anim-20 .stack.is-entering .stack-video::after { animation: snow .15s steps(4) infinite, tune-snow 1.1s ease backwards; }
@keyframes tune-in { 0% { filter: blur(8px) contrast(.6) brightness(1.4); } 60% { filter: blur(2px) contrast(.9); } 100% { filter: none; } }
@keyframes tune-snow { 0% { opacity: 1; } 50% { opacity: .55; } 100% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .stage .card-face::after, .stage .stack-video::after { animation: none !important; opacity: 0 !important; }
}

@media (prefers-reduced-motion: reduce) {
  .stack.is-entering .stack-video, .stack.is-entering .card-face, .stack.is-previous .stack-video, .stack.is-previous .card-face { animation: none; }
  .card { transition: none; }
}

/* ---------- Phones: the video across the full width, the three cards in a row under it ---------- */
@media (max-width: 767px) {
  .home-title { position: static; margin: 0 0 .5rem; }
  .stage { --stage-h: auto; height: auto; }
  .stack { position: relative; flex-wrap: wrap; align-items: flex-start; justify-content: center;
    gap: 0 1.5px;
    padding: 0 0 2.75rem; }
  .stack:not(.is-active):not(.is-previous) { display: none; }
  .stack.is-previous { position: absolute; inset: 0; }
  .stack-middle { display: contents; }
  /* The video as wide as the screen, unless that would push the slider off the bottom: then it is just short
     enough for WHERE and the whole slider to fit on the screen (the height once the browser's bars slide away).
     324px: the headline, the gaps, the card titles, the console name row and the slider, less a little; 7px: the frame. */
  /* The cards as big as they can be: the two outer cards lean out 4deg, so their top corners reach about 8.7%
     of a card's width further out (three cards take 3.175 card widths). They end up 1.5px apart and about 1px
     from the screen's edges. */
  .stage { --card-w: calc((100vw - 5px) / 3.175); --card-h: calc(var(--card-w) / .75); --card-turn: 4deg;
    --video-h: min((100vw - 1rem - 7px) * .75, 100vh - var(--card-h) - var(--scenery-below, 0px) - 324px - 7px - var(--video-fix, 0px)); }
  .stack-video { order: -1; flex: none; height: auto; margin: 0 auto 1.1rem; box-shadow: none; }
  .stack-video video, .stack-video img { height: max(9rem, var(--video-h)); width: auto; max-width: calc(100vw - 1rem - 7px); }
  /* the frames half as thick as on bigger screens, the cards' the same as the video's */
  .stage .stack-video, .stage.card-shape-0 .stack-video { border-width: var(--frame-w); }
  .stage .card-face, .stage.card-shape-1 .card-face, .stage.card-shape-3 .card-face { border-width: var(--frame-w); }
  .stack-video video { aspect-ratio: 4 / 3; }
  .stack::after { content: ""; order: 0; flex: 0 0 100%; height: 0; }  /* (the cards always start a new row) */
  .card, .card.is-bottom { width: var(--card-w); height: var(--card-h); margin: 0; }
  .card.is-left { order: 1; } .card.is-bottom { order: 2; } .card.is-right { order: 3; }
  .card-title { font-size: clamp(.62rem, 2.9vw, .85rem); }
}

/* Switching: the old console's card titles vanish at once; the new ones appear after their cards land. */
.stack.is-previous .card-title { visibility: hidden; }
.stack.is-entering .card-title { animation: title-in .3s ease both; animation-delay: calc(var(--enter) * .8); }
@keyframes title-in { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .stack.is-entering .card-title { animation: none; } }

/* ---------- A darker tone around the cards and video ----------
   Soft see-through shadows behind the video and each card. They overlap so they bleed into each other,
   and fade out to the normal background before the APPAREL / HISTORY corners. The dots are drawn on top
   at full strength. */
/* --dot-solid: the dots' colour as it looks on the plain background, so they stay as bright in the shade. */
.home-stage { --shade: rgba(43, 36, 32, .28); --dot-solid: #ece6da; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .home-stage { --shade: rgba(0, 0, 0, .92); --dot-solid: #24211e; }
}
:root[data-theme="dark"] .home-stage { --shade: rgba(0, 0, 0, .92); --dot-solid: #24211e; }
.home-stage::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(var(--dot-solid) 1px, transparent 1.2px) 0 0 / 7px 7px,         /* the dots, on top */
    radial-gradient(ellipse 30% 42% at 50% 22%, var(--shade), transparent 100%),   /* the video */
    radial-gradient(ellipse 30% 48% at 27% 60%, var(--shade), transparent 100%),   /* left card */
    radial-gradient(ellipse 30% 50% at 50% 58%, var(--shade), transparent 100%),   /* middle card */
    radial-gradient(ellipse 30% 48% at 73% 60%, var(--shade), transparent 100%),   /* right card */
    radial-gradient(ellipse 54% 66% at 50% 52%, var(--shade), transparent 100%),   /* joins them up */
    var(--cotton);
}
/* "and" under APPAREL, "collide" under HISTORY. */
.ht-lead.is-after { margin: .2rem 0 0; }

/* The video gets the same thick rounded edge as the shirt cards. */
.stack-video { border: var(--frame-w) solid var(--card-edge, var(--paper)); border-radius: 16px; overflow: hidden; background: var(--card-edge, var(--paper)); }
.stack-video video, .stack-video img { border-radius: 10px; }
.card-shape-0 .stack-video, .card-cut .stack-video { border-width: 4px; border-radius: 6px; }

/* ---------- Shirt cards move a little under the mouse, and one at random every 3 to 7 seconds (home.js adds
   .is-wiggling for one round): 10 styles, ?wiggle=1 to 10, 7 (Tilt) by default (compare at /wiggles/) ---------- */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .wiggle-1 .card:hover .card-face { animation: wg-wiggle .5s ease-in-out infinite; }
  .wiggle-2 .card:hover .card-face { animation: wg-shake .45s ease-in-out infinite; }
  .wiggle-3 .card:hover .card-face { animation: wg-bounce .7s ease-in-out infinite; }
  .wiggle-4 .card:hover .card-face { animation: wg-pulse .9s ease-in-out infinite; }
  .wiggle-5 .card:hover .card-face { animation: wg-jelly .8s ease-in-out infinite; }
  .wiggle-6 .card:hover .card-face { animation: wg-swing 1s ease-in-out infinite; transform-origin: 50% 0; }
  .wiggle-7 .card:hover .card-face { animation: wg-tilt 1.2s ease-in-out infinite; }
  .wiggle-8 .card:hover .card-face { animation: wg-float 1.6s ease-in-out infinite; }
  .wiggle-9 .card:hover .card-face { animation: wg-rubber .9s ease-in-out infinite; }
  .wiggle-10 .card:hover .card-face { animation: wg-tada 1.1s ease-in-out infinite; }
}
/* The random wiggle also runs on phones and tablets (no hover there). */
@media (prefers-reduced-motion: no-preference) {
  .wiggle-1 .card.is-wiggling .card-face { animation: wg-wiggle .5s ease-in-out infinite; }
  .wiggle-2 .card.is-wiggling .card-face { animation: wg-shake .45s ease-in-out infinite; }
  .wiggle-3 .card.is-wiggling .card-face { animation: wg-bounce .7s ease-in-out infinite; }
  .wiggle-4 .card.is-wiggling .card-face { animation: wg-pulse .9s ease-in-out infinite; }
  .wiggle-5 .card.is-wiggling .card-face { animation: wg-jelly .8s ease-in-out infinite; }
  .wiggle-6 .card.is-wiggling .card-face { animation: wg-swing 1s ease-in-out infinite; transform-origin: 50% 0; }
  .wiggle-7 .card.is-wiggling .card-face { animation: wg-tilt 1.2s ease-in-out infinite; }
  .wiggle-8 .card.is-wiggling .card-face { animation: wg-float 1.6s ease-in-out infinite; }
  .wiggle-9 .card.is-wiggling .card-face { animation: wg-rubber .9s ease-in-out infinite; }
  .wiggle-10 .card.is-wiggling .card-face { animation: wg-tada 1.1s ease-in-out infinite; }
}
@keyframes wg-wiggle { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-2deg); } 75% { transform: rotate(2deg); } }
@keyframes wg-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
@keyframes wg-bounce { 0%, 100% { transform: translateY(0); } 40% { transform: translateY(-9px); } 60% { transform: translateY(-6px); } }
@keyframes wg-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }
@keyframes wg-jelly { 0%, 100% { transform: scale(1, 1); } 30% { transform: scale(1.04, .96); } 50% { transform: scale(.97, 1.03); } 70% { transform: scale(1.02, .98); } }
@keyframes wg-swing { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(3deg); } 75% { transform: rotate(-3deg); } }
@keyframes wg-tilt { 0%, 100% { transform: perspective(700px) rotateY(0) rotateX(0); } 25% { transform: perspective(700px) rotateY(7deg) rotateX(2deg); } 75% { transform: perspective(700px) rotateY(-7deg) rotateX(-2deg); } }
@keyframes wg-float { 0%, 100% { transform: translateY(0) rotate(0); } 50% { transform: translateY(-7px) rotate(.6deg); } }
@keyframes wg-rubber { 0%, 100% { transform: scale(1, 1); } 30% { transform: scale(1.06, .94); } 45% { transform: scale(.95, 1.05); } 60% { transform: scale(1.03, .97); } 75% { transform: scale(.99, 1.01); } }
@keyframes wg-tada { 0%, 100% { transform: scale(1) rotate(0); } 10%, 20% { transform: scale(.96) rotate(-2deg); } 30%, 50%, 70% { transform: scale(1.04) rotate(2deg); } 40%, 60% { transform: scale(1.04) rotate(-2deg); } 80% { transform: scale(1) rotate(0); } }

/* The /wiggles/ page: one sample card per style. */
.wiggle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 2rem 1.5rem; margin: 1.5rem 0 3rem; }
.wiggle-grid .card { position: relative; width: 100%; aspect-ratio: 3 / 4; height: auto; }
.wiggle-grid .card-face { border: var(--frame-w) solid var(--card-edge, var(--paper)); border-radius: 16px; }
.wiggle-grid .card-face img { border-radius: 10px; }
.wiggle-grid p { margin: .75rem 0 0; text-align: center; font-weight: 700; }
.wiggle-grid a { color: var(--ink); }

/* Mario on his vine beside APPAREL's L (mario-vine.js sets its place, width and height): vine above him and
   below him repeats to any length, so it can reach from the L to the H. Sharp pixels, no blur. */
.home-title { isolation: isolate; }
/* A gap above the video, plus room for the pipe's platform (mario-vine.js measures it). */
/* Phones: the video's frame touches the bottom of the pipe's platform (mario-vine.js measures it). */
@media (max-width: 767px) { .home-title { position: relative; margin-bottom: var(--scenery-below, 1rem); } }
/* Phones: HISTORY (and COLLIDE) a little higher, so the vine is shorter (about 7%) and the page shorter too; any
   higher and the key from the ! block rises into MENU. */
@media (max-width: 767px) { .ht-row.is-right { margin-top: -.12em; } }
/* --px is one game pixel on the screen. The vine grows out of a brown block at the bottom; left of it a green
   pipe stands on a platform. Mario, Yoshi, the blocks and the rest are drawn by mario-vine.js on two canvases
   (game pixels, scaled up sharp): one over everything, one behind the video for when he flies past it. */
.ht-vine { --px: 1px; position: absolute; z-index: -1; pointer-events: none;
  animation: ty-rise .8s cubic-bezier(.2, .7, .2, 1) .3s both; }
.ht-vine[hidden] { display: none; }
.ht-stem, .ht-block, .ht-pipe, .ht-platform, .ht-platform > span {
  position: absolute; display: block; image-rendering: pixelated; }
.ht-stem { left: 0; top: 0; bottom: calc(16 * var(--px)); width: calc(18 * var(--px));
  background: url("../img/vine-up.59f9a2b92407.png") center bottom / calc(18 * var(--px)) calc(16 * var(--px)) repeat-y; }
.ht-block { left: 0; bottom: 0; width: calc(16 * var(--px)); height: calc(16 * var(--px));
  background: url("../img/vine-block.be0590d33256.png") 0 0 / 100% 100% no-repeat; }
/* The pipe: its top level with the block's top, just left of the block. */
.ht-pipe { left: calc(-32 * var(--px)); bottom: calc(-16 * var(--px)); width: calc(32 * var(--px)); height: calc(32 * var(--px));
  background: url("../img/pipe.997de8e0a897.png") 0 0 / 100% 100% no-repeat; }
/* The platform under the pipe: its right end 6 game pixels past the vine's left side, so the flat of its top
   runs right under the pipe's bottom right corner (the end of the platform curves down). It can be made shorter
   (mario-vine.js sets --plat-w): the left half shows the picture's left end, the right half its right end. */
.ht-platform { --plat-w: 246; right: calc(12 * var(--px)); bottom: calc(-49 * var(--px));
  width: calc(var(--plat-w) * var(--px)); height: calc(33 * var(--px)); }
.ht-platform > span { top: 0; bottom: 0; width: 50%;
  background: url("../img/platform.c233c9623543.png") 0 0 / calc(246 * var(--px)) 100% no-repeat; }
.ht-platform > span + span { right: 0; background-position: right 0 top 0; }
.ht-scene { position: absolute; z-index: 6; pointer-events: none; image-rendering: pixelated; }
/* The pipe and its platform show only while a scene plays (mario-vine.js adds is-world, flickering it on and off). */
.ht-vine:not(.is-world) .ht-pipe, .ht-vine:not(.is-world) .ht-platform { visibility: hidden; }
/* The key game: the ! block can be clicked for a key, which is dragged into the keyhole under COLLIDE. */
.ht-excl, .ht-key, .ht-keyhole { position: absolute; display: block; image-rendering: pixelated; }
.ht-excl[hidden], .ht-key[hidden], .ht-keyhole[hidden] { display: none; }
.ht-excl { z-index: 8; touch-action: none; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
.ht-excl.is-key { cursor: grab; }
.ht-excl.is-ready { cursor: pointer; }
.ht-keyhole { z-index: 5; pointer-events: none; }
.ht-key { z-index: 9; cursor: grab; touch-action: none; -webkit-user-drag: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.ht-key.is-held { cursor: grabbing; }
.ht-scene.is-back { z-index: 2; }
/* Fire Mario on Yoshi runs along the top of the video: a little more room above it (mario-vine.js sets it). */
@media (min-width: 768px) { .stack-middle { margin-top: var(--yoshi-room, 0px); } }
/* A video with sound: a see-through speaker in its top right corner (video-sound.js); muted until tapped. */
.video-sound { position: absolute; top: .45rem; right: .45rem; z-index: 3; display: flex; align-items: center;
  justify-content: center; width: 2.1rem; height: 2.1rem; border-radius: 50%; color: #fff; cursor: pointer;
  background: rgba(0, 0, 0, .35); opacity: .7; pointer-events: auto; line-height: 0; transition: opacity .2s; }
.video-sound:hover, .video-sound:focus-visible, .video-sound.is-on { opacity: 1; }
.video-sound .sound-on { display: none; }
.video-sound.is-on .sound-on { display: inline; }
.video-sound.is-on .sound-off { display: none; }
