/* The home page: a hero over a map of the teams, the running tally, the
 * Founding 50 ticket, the newest uploads and the notebook page. Loaded only by
 * templates/index.html, after ticket.css; everything sits under .landing or a
 * class no other page uses. Colours come from the theme tokens in style.css, so Machine Shop
 * and Clean Lab both carry it. The design cards keep their site-wide rules. */

/* The sections were designed on a bare reset; the site's base type rules
   (h1-h3 margins, underlined link hovers) would otherwise leak in. Cards are
   left alone: they live in .cards, outside these. */
.landing :where(.hero, .tally, .f50, .explore__head, .notebook) :where(h1, h2, p, ul, li) { margin: 0; padding: 0; list-style: none; }
.landing :where(.hero, .tally, .f50, .notebook) :where(img, svg) { display: block; max-width: 100%; }
.landing :where(.hero, .tally, .f50, .explore__head, .notebook) a:hover { text-decoration: none; }
.landing :where(.hero, .f50) button { font: inherit; color: inherit; background: none; border: 0; }
/* the map runs edge to edge, so the page drops its reading width and padding */
.page.page--landing { max-width: none; padding: 0; }


.wrap{ width:100%; max-width:var(--wrap); margin-inline:auto; padding-inline:var(--gut); }

.ul-link{ position:relative; display:inline-flex; align-items:center; gap:.4em; color:var(--text-dim); font-weight:600; transition:color .18s var(--ease); }
.ul-link svg{ width:1em; height:1em; }
.ul-link::after{
  content:""; position:absolute; left:0; right:0; bottom:-3px; height:1px; background:currentColor;
  transform:scaleX(0); transform-origin:left; transition:transform .3s var(--ease);
}
.ul-link:hover{ color:var(--text); }
.ul-link:hover::after{ transform:scaleX(1); }

/* scroll-reveal: IntersectionObserver adds .in. Each section has its own
   entrance (plotter, print bed, ticket feed; see the sections below), and
   all of them are armed only by html.rv-on, which the head script sets
   when motion is allowed. No script or reduced motion = always visible. */

/* the carousel slides in from the side on load (the heading's entrance
   is part of the scripted bubble timeline instead) */
@keyframes pop-in-r{
  from{ opacity:0; transform:perspective(850px) translate3d(140px,24px,240px) rotateY(22deg) rotateX(-10deg) scale(1.08); }
  to{ opacity:1; transform:none; }
}
.pop{ animation-duration:.8s; animation-timing-function:var(--ease-out); animation-fill-mode:both; }
.pop--r{ animation-name:pop-in-r; animation-delay:.12s; }

/* ===================================================================
   2. Hero, over a map of where the teams are
   =================================================================== */
.hero{ position:relative; isolation:isolate; padding-block:clamp(2.6rem,6vw,5rem) var(--sec-y); }
/* where the teams are: every orange dot is a cluster of FTC teams that
   played the 2025-26 season, on a dotted world map (assets/ftc-map.svg,
   built by map/build_map.py). It sits under the copy as texture, so it is
   dimmed and fades out towards the bottom of the hero. The script lays
   copies of it side by side and turns it slowly, like a globe; resting the
   pointer on a dot stops it and names the team (assets/ftc-teams.json). */
/* only the art sinks under the copy; the ring and tag rise above it, so a
   team named next to the headline reads as a card, not a smudge behind it */
.hero__map{ position:absolute; inset:0; overflow:hidden; pointer-events:none; }
.hero__map-art{
  position:absolute; inset:0; z-index:-1; opacity:.75;
  /* two masks, intersected: a fade-out at the bottom, and a soft hollow
     behind the headline so the busiest dots never sit under the words */
  --map-hollow:radial-gradient(ellipse 34rem 22rem at 22% 44%, rgb(0 0 0 / .1) 35%, #000 100%);
  --map-fade:linear-gradient(180deg, #000 0%, #000 62%, transparent 96%);
  mask-image:var(--map-hollow), var(--map-fade);
  mask-composite:intersect;
  -webkit-mask-image:var(--map-hollow), var(--map-fade);
  -webkit-mask-composite:source-in;
}
.hero__map-track{ position:absolute; top:1.5rem; left:50%; display:flex; transform:translateX(-50%); will-change:transform; }
.hero__map-track img{ display:block; flex:none; width:var(--tile, min(100vw, 88rem)); max-width:none; height:auto; }
/* on a phone the hero is tall and narrow: show the Americas and the
   Atlantic at a readable size instead of a postage-stamp world */
@media (max-width:40rem){
  .hero__map-art{ --map-hollow:radial-gradient(ellipse 20rem 13rem at 40% 10rem, rgb(0 0 0 / .12) 35%, #000 100%); }
  .hero__map-track{ top:1rem; left:24%; transform:translateX(-24%); }
  .hero__map-track img{ width:var(--tile, 56rem); }
}
/* the dot under the pointer gets a ring, and a paper tag with the team */
.hero__map-ring{
  position:absolute; left:0; top:0; z-index:6; width:24px; height:24px; margin:-12px 0 0 -12px;
  border:2px solid var(--orange); border-radius:50%; box-shadow:0 0 0 5px color-mix(in srgb, var(--orange) 16%, transparent);
  opacity:0; scale:.3; transition:opacity .2s var(--ease-out), scale .4s cubic-bezier(.34,1.6,.5,1);
}
.hero__map.is-botfolio .hero__map-ring{
  border-color:var(--map-botfolio); box-shadow:0 0 0 5px color-mix(in srgb, var(--map-botfolio) 18%, transparent);
}
.hero__map-tag{
  position:absolute; left:0; top:0; z-index:6; width:max-content; max-width:17rem; margin:0;
  padding:.6rem .85rem .65rem; border-radius:var(--r);
  background:var(--paper-hi); color:var(--paper-ink); box-shadow:0 12px 24px -10px rgb(var(--shade) / .75);
  opacity:0; translate:0 6px; transition:opacity .2s var(--ease-out), translate .3s var(--ease-out);
}
.hero__map-tag::after{
  content:""; position:absolute; left:var(--stem, 50%); bottom:-5px; width:10px; height:10px; margin-left:-5px;
  background:inherit; rotate:45deg;
}
.hero__map-tag.is-below::after{ top:-5px; bottom:auto; }
.hero__map.is-on .hero__map-ring{ opacity:1; scale:1; }
.hero__map.is-on .hero__map-tag{ opacity:1; translate:0 0; }
.hero__map-name{ display:block; font:800 1.02rem/1.2 var(--f-sans); font-stretch:108%; letter-spacing:-.005em; }
.hero__map-meta{ display:block; margin-top:.2rem; font-size:.82rem; line-height:1.3; color:var(--paper-dim); }
.hero__map-more{ display:block; margin-top:.35rem; font:700 .7rem/1.2 var(--f-sans); letter-spacing:.08em; text-transform:uppercase; color:var(--note); }
.hero__map-more:empty{ display:none; }
.hero__credit{
  position:absolute; right:clamp(1rem,3vw,2rem); bottom:.8rem; margin:0;
  max-width:calc(100% - 2rem); font-size:.8rem; line-height:1.35; color:var(--text-faint); text-align:right;
}
.hero__credit a{ color:inherit; text-underline-offset:2px; }
/* on a phone the carousel runs to the bottom of the hero, so the credit
   follows it in the flow instead of sitting on top of its controls */
@media (max-width:40rem){
  .hero__credit{ position:static; max-width:none; margin:1.6rem 1rem 0; text-align:left; }
}
.hero__inner{ display:grid; gap:clamp(2.2rem,5vw,3.5rem); align-items:center; }
@media (min-width:68rem){ .hero__inner{ grid-template-columns:minmax(0,1fr) minmax(0,.86fr); } }
.hero__copy{ position:relative; }

.hero__title{
  position:relative; margin:0; color:var(--text);
  font:850 clamp(2.45rem,5.3vw,4.4rem)/1.02 var(--f-sans); font-stretch:115%;
  letter-spacing:-.025em; text-wrap:balance;
  --noun:var(--orange);
}
/* the script splits the heading into words so the bubbles can find the ink */
.hero__title .w{ display:inline-block; }
/* "that intake." keeps a line to itself, so when the last word changes
   nothing above it reflows */
.hero__tail{ display:block; white-space:nowrap; }
/* the last word cycles through mechanisms; each one has its own colour,
   and the photo frame next to it picks the same colour up */
.hero__noun{ position:relative; display:inline-block; color:var(--noun); transition:color .45s var(--ease-out); }
.hero__noun .w{ transform-origin:50% 50% -.4em; backface-visibility:hidden; }
/* a real upload sits inside the sentence, tilted like a photo taped into a notebook */
.hero__thumb{ width:1.45em; height:.8em; margin:0 .04em; vertical-align:-.02em; }
.hero__thumb img{
  display:block; width:100%; height:100%; object-fit:cover; border-radius:5px;
  border:.06em solid var(--noun); transform:rotate(-4deg);
  box-shadow:0 .08em .22em rgb(var(--shade) / .45);
  transition:border-color .45s var(--ease-out);
}
/* while the bubbles run, the sentence stays one colour so they read */
.intro .hero__noun, .intro .hero__stop{ color:var(--text); }
.intro .hero__thumb img{ border-color:var(--paper); }
/* ...except while the last word is spinning in, when each one shows its colour */
.intro .is-spinning .hero__noun{ color:var(--noun); transition-duration:.15s; }
.intro .is-spinning .hero__thumb img{ border-color:var(--noun); }
/* and once the bubbles have filled "intake", it, the nut and the frame are orange */
.intro .is-filled .hero__noun, .intro .is-filled .hero__stop{ color:var(--noun); }
.intro .is-filled .hero__thumb img{ border-color:var(--noun); }

/* the i in "built" is a dotless i wearing the Botfolio robot's head */
.hero__i{ position:relative; }
.hero__head{
  position:absolute; left:50%; top:.15em; width:.31em; height:.25em; max-width:none;
  translate:-50% 0; overflow:visible; transform-origin:50% 100%;
  transition:scale .55s cubic-bezier(.34,1.7,.5,1), rotate .55s cubic-bezier(.34,1.7,.5,1);
}
.hero__head rect:first-child{ fill:var(--bg); stroke:var(--orange); }
.hero__head .eye{
  fill:var(--paper); transform-box:fill-box; transform-origin:50% 50%;
  /* the script points --look-x/-y (each -1..1) at the cursor; in viewBox
     units the eyes have about 4 to spare each way inside the frame */
  translate:calc(var(--look-x, 0) * 4px) calc(var(--look-y, 0) * 3.5px);
}
/* and the head leans a little towards it, pivoting on the stem */
@media (prefers-reduced-motion:no-preference){
  .hero__head{ transform:rotate(calc(var(--look-x, 0) * 7deg)); }
}
/* it pops onto the stem as the sentence lands, then blinks once the button
   has formed, and again whenever the heading is pointed at */
.intro:not(.landed) .hero__head{ scale:0; rotate:-25deg; }
@media (prefers-reduced-motion:no-preference){
  .formed .hero__head .eye{ animation:blink .38s ease-in-out 1.1s 2; }
  .hero__title:hover .hero__head .eye{ animation:blink .38s ease-in-out 1; }
}
@keyframes blink{ 0%,100%{ scale:1 1; } 45%,55%{ scale:1 .12; } }

/* the full stop is a hex nut. While the reel spins the word under it keeps
   changing width, so the nut stays off the line and screws on once it stops */
.hero__stop{
  display:inline-block; width:.29em; height:.29em; margin-left:.035em; vertical-align:0;
  color:var(--noun);
  transition:color .45s var(--ease-out), scale .45s cubic-bezier(.34,1.6,.5,1), rotate .8s cubic-bezier(.2,1.25,.4,1);
}
.intro .hero__title:not(.reel-stopped) .hero__stop{ scale:0; rotate:-240deg; }
.hero__stop svg{ display:block; width:100%; height:100%; }
.hero__actions{ display:flex; flex-wrap:wrap; align-items:center; gap:1rem; margin-top:2rem; }

/* The fizz layer is a copy of the heading laid exactly over it, with
   transparent text. The script paints flat orange circles into its
   background, and background-clip:text keeps them inside the letterforms,
   so the bubbles only ever show where there is ink. It lives inside the
   heading, so it rides the same entrance and the bubbles are already
   flowing while the sentence flies in. */
.hero__fizz{
  position:absolute; inset:0; pointer-events:none; user-select:none;
  color:transparent; -webkit-text-fill-color:transparent; background-repeat:no-repeat;
  -webkit-background-clip:text; background-clip:text;
}
.hero__fizz .hero__thumb, .hero__fizz .hero__head, .hero__fizz .hero__stop{ visibility:hidden; }
/* a word on its own layer (the reel's backface-visibility) is clipped at the
   overlay's top-left corner instead of where it sits, so keep it flat here */
.hero__fizz .hero__noun .w{ backface-visibility:visible; }

/* once bubbles drip out of the letters they are drawn on this canvas; the
   goo filter makes any two that get close grow a bridge and run together */
.bubbles{ position:absolute; z-index:3; pointer-events:none; filter:url(#goo); }

.intro .hero__title{ opacity:0; }
.intro:not(.formed) .hero__actions .btn--orange{ background:transparent; color:transparent; box-shadow:none; transition:none; }
.intro:not(.landed) .hero__actions .btn--outline{ opacity:0; }
.hero__actions .btn--outline{ transition:opacity .45s var(--ease-out), border-color .16s var(--ease), background-color .16s var(--ease); }
.formed .hero__actions .btn--outline{ transition-delay:.12s, 0s, 0s; }

/* the CTA arrow doesn't spin in place: one icon slides off like a train leaving
   the platform while the next is already arriving behind it, on a loop */
.btn__arrow{ position:relative; display:inline-block; width:1.05em; height:1.05em; overflow:hidden; }
/* the arrow sits still until the button is pointed at, then the relay runs */
.btn__arrow svg{ position:absolute; inset:0; width:100%; height:100%; }
.btn__arrow svg + svg{ opacity:0; animation-delay:-.6s; }
@keyframes arrow-relay{
  0%{ transform:translateX(-120%); opacity:0; }
  12%{ opacity:1; }
  45%{ transform:translateX(0); opacity:1; }
  80%{ opacity:1; }
  100%{ transform:translateX(120%); opacity:0; }
}
.btn:hover .btn__arrow svg, .btn:focus-visible .btn__arrow svg{ animation:arrow-relay 1.2s cubic-bezier(.5,0,.5,1) infinite; }
.btn:hover .btn__arrow svg + svg, .btn:focus-visible .btn__arrow svg + svg{ animation-delay:-.6s; }

/* the hand-drawn note starts sketching its arrow as soon as the first drop
   reaches the button, so the arrow and the button form together */
/* it lives in the carousel column and is placed against the carousel's edge
   (the carousel is centred, min(100%, 26rem) wide): a small one in the gap on
   its left while the hero is two columns, then mirrored to the right of it
   once the page margin has room for the full-size arrow */
.hero__point{ display:none; position:absolute; z-index:3; pointer-events:none; }
.hero__point-arrow{ width:100%; height:auto; overflow:visible; }
.hero__point-text{
  display:block; margin-top:.15rem; margin-left:.3rem; rotate:-3deg;
  font:700 1.02rem/1.1 var(--f-hand); color:var(--orange);
}
@media (min-width:68rem){
  .hero__point{ display:block; top:calc(50% - 9.5rem); right:calc(50% + min(50%, 13rem) - .4rem); width:6.75rem; }
}
@media (min-width:90rem){
  .hero__point{ top:calc(50% - 8.5rem); right:auto; left:calc(50% + min(50%, 13rem) + .6rem); width:9.75rem; }
  .hero__point-arrow{ scale:-1 1; }
  .hero__point-text{ margin-left:1.4rem; rotate:3deg; font-size:1.3rem; }
}
.intro:not(.pointing) .hero__point{ opacity:0; }
.pointing .pt-shaft{ stroke-dasharray:200; animation:point-draw 1.4s var(--ease-out) .1s both; }
.pointing .pt-head{ stroke-dasharray:60; animation:point-draw-head .55s var(--ease-out) 1.35s both; }
.pointing .hero__point-text{ animation:point-text-in .8s var(--ease-out) 1.7s both; }
@keyframes point-draw{ from{ stroke-dashoffset:200; } to{ stroke-dashoffset:0; } }
@keyframes point-draw-head{ from{ stroke-dashoffset:60; } to{ stroke-dashoffset:0; } }
@keyframes point-text-in{ from{ opacity:0; } to{ opacity:1; } }

.hero__art{ position:relative; min-width:0; display:flex; flex-direction:column; align-items:center; gap:.7rem; }

/* up/down chevrons double as the vertical carousel's prev/next controls */
.bf-chev{
  display:grid; place-items:center; width:2.75rem; height:2.75rem; border-radius:var(--r-sm);
  border:1px solid var(--line-strong); background:var(--bg-raised); color:var(--text-dim); cursor:pointer;
  transition:border-color .16s var(--ease), color .16s var(--ease), transform .1s var(--ease);
}
.bf-chev svg{ width:18px; height:18px; }
.bf-chev:hover{ border-color:var(--text); color:var(--text); }
.bf-chev:active{ transform:scale(.94); }
.bf-chev--up{ margin-bottom:1.4rem; }

/* ===================================================================
   3. Running tally: four big counters that roll up like an odometer
   =================================================================== */
.tally{ background:var(--bg-sunk); border-block:1px solid var(--line); }
.tally__inner{ padding-block:clamp(2.4rem,5vw,3.6rem); }
.tally__lead{ margin-bottom:1.25rem; font:700 1.05rem/1.2 var(--f-sans); color:var(--text-dim); }
/* one counter per row on phones, two on tablets, four only once each
   column is wide enough to keep a model beside its number */
.stats{ display:grid; grid-template-columns:minmax(0,1fr); gap:1.7rem; }
@media (min-width:40rem){ .stats{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:1.9rem 2rem; } }
@media (min-width:75rem){ .stats{ grid-template-columns:repeat(4,minmax(0,1fr)); gap:1.6rem 2rem; } }
.stat{
  position:relative; display:grid; grid-template-columns:minmax(min-content,11rem) minmax(0,1fr); column-gap:.4rem;
  padding-top:.85rem; border-top:1px solid var(--line-strong);
}
.stat > *{ grid-column:1 / -1; }
.stat__tick{ grid-row:1; } .stat__num{ grid-row:2; } .stat__label{ grid-row:3; }
/* the designs and CAD counters each show a real STL turning slowly to the
   right of the number, beside the caption line too; it adds no height, so
   every counter keeps the same shape */
.stat__shot{ position:relative; grid-column:2; grid-row:1 / span 2; }
.stat__shot canvas{ position:absolute; inset:0; display:block; width:100%; height:100%; }
/* four across leaves little room beside the number, so the model borrows
   most of the empty gutter to its right */
@media (min-width:75rem){ .stat__shot{ margin-right:-1.6rem; } }
.stat__shot ~ .stat__tick, .stat__shot ~ .stat__num{ grid-column:1; }
/* above each number, a line that quietly turns through examples of what
   is being counted (a team, a design, a file); the opens get an icon */
.stat__tick{
  position:relative; height:1.6rem; margin-bottom:.5rem; overflow:hidden;
  font:700 .84rem/1.6rem var(--f-sans); letter-spacing:.02em; color:var(--orange);
}
.stat__tick span{ position:absolute; inset:0 auto 0 0; max-width:100%; padding-left:.85rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.stat__tick span::before{ content:""; position:absolute; left:0; top:50%; width:.42rem; height:.42rem; margin-top:-.21rem; border-radius:1px; background:var(--orange); }
.stat__tick svg{ display:block; width:1.6rem; height:1.6rem; overflow:visible; }
@media (prefers-reduced-motion:no-preference){
  .stat__tick svg .drop{ animation:stat-drop 3.2s var(--ease-out) infinite; }
}
/* the arrow dips into the tray now and then */
@keyframes stat-drop{ 0%,70%,100%{ transform:translateY(0); } 80%{ transform:translateY(2.5px); } 90%{ transform:translateY(-1px); } }
.stat__num{
  display:flex; align-items:flex-start;
  font:800 clamp(2.75rem,6.4vw,4.9rem)/1 var(--f-sans); font-stretch:112%; letter-spacing:-.035em;
  font-variant-numeric:tabular-nums; color:var(--text);
}
@media (max-width:39.99rem){ .stat__num{ font-size:3.3rem; } }
.stat__plus{ margin-left:.04em; font-size:.5em; line-height:1.1; color:var(--orange); }
.stat__label{ margin-top:.6rem; font-size:clamp(.95rem,1.3vw,1.06rem); line-height:1.35; color:var(--text-dim); }
/* odometer: each digit is a strip of 0-9 (three times over) inside a
   one-digit window; rolling up is a single transform per strip */
.odo{ display:inline-flex; }
.odo__col{ display:block; height:1em; overflow:hidden; }
.odo__strip{ display:flex; flex-direction:column; transition:transform 3.2s var(--ease-out); }
.odo__strip span{ display:block; height:1em; line-height:1; }
.odo__sep{ display:block; line-height:1; }

/* ===================================================================
   4. Founding 50: a golden ticket
   =================================================================== */
/* The ticket itself is ticket.css; this is its place on the home page and
   its scroll entrance (the slot opens and the ticket feeds out of it). */
.f50{ padding-block:var(--sec-y); }
.f50__stage{ max-width:60rem; margin-inline:auto; perspective:1100px; }
.rv-on .f50__stage{ position:relative; }
.rv-on .f50__stage::before{
  content:""; position:absolute; z-index:2; left:-1.5%; right:-1.5%; top:-3px; height:6px; border-radius:3px;
  background:rgb(var(--shade) / .7); box-shadow:inset 0 2px 2px rgb(var(--shade) / .9), 0 1px 0 var(--line-strong);
  transform:scaleX(0); pointer-events:none;
}
.rv-on .f50__stage.in::before{ animation:slot-open 2.1s var(--ease) both; }
.rv-on .f50__stage:not(.in) .ticket{ transform:translateY(-100%); clip-path:inset(100% 0 0 0); }
.rv-on .f50__stage.in .ticket{ animation:ticket-feed 1.35s .32s both; }
.rv-on .f50__stage.in .ticket::before{ animation:foil-pass 1s 1.7s var(--ease) both; }

/* ===================================================================
   5. Designs-of-the-week carousel (lives in the hero)
   =================================================================== */
.bf-carousel{ position:relative; min-width:0; width:100%; max-width:26rem; }
.bf-frame{ position:relative; padding-right:1.1rem; }

.bf-rail{ position:absolute; display:flex; gap:.34rem; z-index:5; top:50%; right:0; translate:0 -50%; flex-direction:column; }
.bf-tick{ width:3px; height:1.5rem; background:var(--line-strong); border-radius:2px; transition:background .3s var(--ease), transform .3s var(--ease); }
.bf-tick.is-on{ background:var(--orange); transform:scaleX(1.9); }

.bf-stage{
  position:relative; perspective:var(--persp); transform-style:preserve-3d;
  touch-action:pan-y; overflow:clip; overflow-clip-margin:8px;
  height:clamp(340px, 60vh, 520px); min-height:300px;
  /* soften the very top/bottom edge instead of hard-clipping the peeking cards */
  mask-image:linear-gradient(180deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
}
.bf-card{
  position:absolute; left:0; right:0; top:50%; display:flex; flex-direction:column; gap:.65rem; cursor:pointer;
  /* the centre card leaves room above and below for its neighbours to show */
  --ch:clamp(190px, 64%, 300px); height:var(--ch); margin-top:calc(var(--ch) / -2);
  /* transform gets a back-out curve so each seat change overshoots a touch before it settles */
  transition:transform .85s cubic-bezier(.34,1.56,.64,1), opacity .4s var(--ease-out), filter .4s var(--ease-out), visibility .4s var(--ease-out);
  will-change:transform, opacity;
}
.bf-card__frame{
  position:relative; display:block; overflow:hidden; flex:1; background:var(--bg-sunk);
  border-radius:var(--r); box-shadow:var(--shadow-card);
}
.bf-card__img{ width:100%; height:100%; object-fit:cover; }
.bf-card__scrim{
  position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, rgba(8,20,40,.55) 0%, transparent 32%, transparent 60%, rgba(8,20,40,.65) 100%);
}
.bf-card__badges{ position:absolute; left:.7rem; top:.7rem; display:flex; gap:.35rem; }
.bf-card__badge{
  padding:.3em .55em; background:rgba(10,26,50,.82); border:1px solid rgba(246,242,232,.28);
  border-radius:var(--r-sm); font:700 .72rem/1.2 var(--f-sans); letter-spacing:.03em; color:#f6f2e8;
}
.bf-card__badge--tested{ color:var(--green); border-color:rgba(127,207,127,.45); }
.bf-card__title{
  position:absolute; top:.75rem; right:.8rem; max-width:62%; text-align:right;
  font:700 .95rem/1.25 var(--f-sans); color:#fff; text-shadow:0 1px 8px rgba(0,0,0,.7);
}
.bf-card__meta{
  position:absolute; left:.8rem; bottom:.75rem; max-width:70%;
  font:600 .78rem/1.3 var(--f-sans); color:#eef1f5; text-shadow:0 1px 8px rgba(0,0,0,.7);
}

.bf-card.is-center{ transform:translate3d(0,0,80px) scale(1); opacity:1; filter:none; z-index:3; }
.bf-card.is-prev{ transform:translate3d(0,-74%,-110px) rotateX(-17deg) rotateZ(-1.2deg) scale(.9); opacity:var(--peek-alpha); filter:brightness(.78) saturate(.9); z-index:2; }
.bf-card.is-next{ transform:translate3d(0,74%,-110px) rotateX(17deg) rotateZ(1.2deg) scale(.9); opacity:var(--peek-alpha); filter:brightness(.78) saturate(.9); z-index:2; }
.bf-card.is-prev:hover, .bf-card.is-next:hover{ opacity:1; filter:none; }
.bf-card.is-back{ transform:translate3d(0,0,-320px) scale(.72); opacity:0; visibility:hidden; pointer-events:none; }


@media (max-width:30rem){
  .bf-stage{
    height:clamp(260px, 62vw, 360px);
    /* the centre card nearly fills a phone-sized stage, so fade less of it */
    mask-image:linear-gradient(180deg, transparent 0%, #000 4%, #000 96%, transparent 100%);
    -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 4%, #000 96%, transparent 100%);
  }
  .bf-card{ --ch:clamp(170px, 70%, 250px); }
}

/* ===================================================================
   6. Recently uploaded grid
   =================================================================== */
.explore{ padding-block:var(--sec-y); }
.explore__head{ display:flex; align-items:flex-end; justify-content:space-between; gap:1rem 2rem; flex-wrap:wrap; }
.explore__head h2{ font:800 clamp(1.9rem,3.6vw,2.7rem)/1.08 var(--f-sans); font-stretch:112%; letter-spacing:-.02em; }
.explore__sub{ margin-top:.6rem; color:var(--text-dim); max-width:56ch; }

/* The cards themselves are the site's shared _design_card.html partial,
   styled by style.css; only the grid they sit in is set here. */
.cards{ display:grid; grid-template-columns:minmax(0,1fr); gap:clamp(1rem,2vw,1.4rem); margin-top:clamp(1.8rem,3.5vw,2.6rem); }
.cards > .card-slot{ min-width:0; }
.cards > .card-slot > .card{ height:100%; }
@media (min-width:36rem){ .cards{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:1fr; } }
@media (min-width:62rem){ .cards{ grid-template-columns:repeat(3,minmax(0,1fr)); } }

/* Entrance: each card is printed. The plate drops onto the bed, the photo
   builds up from the bottom one layer at a time (stepped, not eased) under
   a glowing print head, then the label and the badges come on. --i is the
   card's place in the batch that scrolled in together, set by script. */
.card__image::after{
  content:""; position:absolute; z-index:2; left:0; right:0; bottom:0; height:2px; opacity:0; pointer-events:none;
  background:var(--orange-hi);
}
.rv-on .card-slot{ --d:calc(var(--i, 0) * 170ms); }
.rv-on .card-slot:not(.in) .card__image img{ clip-path:inset(100% 0 0 0); }
.rv-on .card-slot:not(.in) :is(.card__body, .badge, .card__tested){ clip-path:inset(0 100% 0 0); }
.rv-on .card-slot.in{ animation:plate-down .3s var(--d) var(--ease) both; }
.rv-on .card-slot.in .card__image img{ animation:print-up 1.15s calc(var(--d) + .25s) steps(13, end) both; }
.rv-on .card-slot.in .card__image::after{ animation:print-head 1.15s calc(var(--d) + .25s) steps(13, end) both; }
.rv-on .card-slot.in .card__body{ animation:label-on .45s calc(var(--d) + 1.35s) var(--ease) both; }
.rv-on .card-slot.in :is(.badge, .card__tested){ animation:label-on .3s calc(var(--d) + 1.5s) var(--ease) both; }
@keyframes plate-down{ from{ transform:scale(.985); } to{ transform:none; } }
@keyframes print-up{ from{ clip-path:inset(100% 0 0 0); } to{ clip-path:inset(0 0 0 0); } }
@keyframes print-head{ 0%{ bottom:0; opacity:1; } 99%{ opacity:1; } 100%{ bottom:calc(100% - 2px); opacity:0; } }
/* labels wipe on left to right, the way a label printer feeds them out */
@keyframes label-on{ from{ clip-path:inset(0 100% 0 0); } to{ clip-path:inset(-1em); } }

/* Section headings are plotted: the pen crosses the line once, left to
   right, and the type is there behind it. Width hugs the text, so the pen
   doesn't keep travelling across empty space. */
[data-plot]{ position:relative; width:fit-content; }
[data-plot]::after{
  content:""; position:absolute; top:-.1em; bottom:-.1em; left:0; width:2px; opacity:0; pointer-events:none;
  background:var(--orange); transform:translateX(-100%);
}
.rv-on .rv:not(.in) [data-plot]{ clip-path:inset(-.3em 100% -.3em -.2em); }
.rv-on .rv.in [data-plot]{ animation:plot .9s cubic-bezier(.55,.06,.3,1) both; }
.rv-on .rv.in [data-plot]::after{ animation:pen .9s cubic-bezier(.55,.06,.3,1) both; }
@keyframes plot{ from{ clip-path:inset(-.3em 100% -.3em -.2em); } to{ clip-path:inset(-.3em -.3em -.3em -.2em); } }
@keyframes pen{ 0%{ left:0; opacity:1; } 88%{ opacity:1; } 100%{ left:100%; opacity:0; } }
/* what follows a plotted heading comes on after the pen lifts */
.rv-on .rv:not(.in) [data-after]{ clip-path:inset(0 100% 0 0); }
.rv-on .rv.in [data-after]{ animation:label-on .5s var(--ease) both; animation-delay:calc(.75s + var(--after, 0) * 160ms); }

/* ===================================================================
   7. About, on an engineering-notebook page
   =================================================================== */
.notebook{
  --focus:var(--bg);
  position:relative; color:var(--paper-ink); background-color:var(--paper);
  background-image:
    linear-gradient(90deg, transparent calc(var(--gut) + 2.4rem), rgba(214,90,60,.35) calc(var(--gut) + 2.4rem), rgba(214,90,60,.35) calc(var(--gut) + 2.4rem + 1px), transparent calc(var(--gut) + 2.4rem + 1px)),
    var(--nb-grid);
  padding-block:var(--sec-y);
}
.notebook ::selection{ background:var(--bg); color:var(--text); }
.notebook__inner{ display:grid; gap:2rem 3rem; align-items:end; }
@media (min-width:56rem){ .notebook__inner{ grid-template-columns:minmax(0,1fr) auto; } }
.notebook h2{ font:800 clamp(1.7rem,3vw,2.35rem)/1.14 var(--f-sans); font-stretch:108%; letter-spacing:-.018em; max-width:24ch; }
.notebook p{ margin-top:1rem; color:var(--paper-dim); max-width:60ch; font-size:1.05rem; }
.notebook__note{ display:block; width:fit-content; margin-top:1.3rem; font:700 1.45rem/1.15 var(--f-hand); color:var(--note); rotate:-1.5deg; }
/* Entrance: the heading is plotted in the margin ink, the paragraph comes on,
   then the note is written in at hand speed, last of all the button */
.notebook [data-plot]::after{ background:var(--note); }
.rv-on .notebook__inner:not(.in) .notebook__note{ clip-path:inset(-.4em 100% -.4em -.4em); }
.rv-on .notebook__inner.in .notebook__note{ animation:write-in 1.4s 1.2s cubic-bezier(.35,.15,.55,.95) both; }
@keyframes write-in{ from{ clip-path:inset(-.4em 100% -.4em -.4em); } to{ clip-path:inset(-.4em -.4em -.4em -.4em); } }

/* ===================================================================
   Global reduced-motion floor
   =================================================================== */
@media (prefers-reduced-motion:reduce){
  .landing *, .landing *::before, .landing *::after{ animation-duration:.01ms !important; animation-delay:0s !important; animation-iteration-count:1 !important; transition-duration:.01ms !important; }
  .btn__arrow svg{ animation:none !important; opacity:0 !important; transform:none !important; }
  .btn__arrow svg:first-child{ opacity:1 !important; }
  .bf-card.is-prev, .bf-card.is-next{ filter:none; }
}
