/* ============================================================================
   Studio portfolio
   ----------------------------------------------------------------------------
   DESIGN NOTE. The work on this page is loud: photoreal renders, saturated
   skies, heavy condensed type burned into every cover. So the page itself is
   deliberately almost colourless — a near-black ground with a single warm bone
   for text and one restrained accent used only for structure, never decoration.
   The thumbnails supply every bit of chroma on the page. A gallery is painted
   neutral so the paintings can sing; same reasoning here.

   Three sections, three rhythms, on purpose: a drifting wall (volume), a
   horizontal rail (motion), then full-bleed single cases (craft). Three
   identical grids would make varied work look like one thing.

   No webfonts, no build step, no dependencies — the same constraints as every
   site we hand to a client.
   ========================================================================== */

:root{
  /* Neutrals carry a slight warm bias so the near-black reads as ink, not as
     switched-off screen. Pure #000/#fff would fight the warm work. */
  --ink:#0b0c0f;
  --ink-2:#131519;
  --ink-3:#1c1f25;
  --bone:#f4f1ea;
  --bone-2:#b9b6ae;
  --bone-3:#7c7a75;
  --line:#262a31;
  --accent:#c8a86b;          /* structural only: rules, numerals, focus */
  --maxw:1240px;
  --gap:clamp(18px,3.2vw,40px);
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
  --mono:ui-monospace,"SF Mono",Menlo,monospace;
  --ease:cubic-bezier(.22,.61,.36,1);
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  margin:0;background:var(--ink);color:var(--bone);
  font-family:var(--sans);line-height:1.55;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  /* `clip` rather than `hidden`: overflow-x:hidden silently makes the body a scroll
     container, which lets a wide descendant establish a containing block wider than the
     viewport — the text then renders past the screen edge instead of wrapping into it.
     `clip` cuts the overflow without creating that scroll container. */
  overflow-x:clip;
}
/* Flex and grid children default to min-width:auto, so one long word or one wide image
   can refuse to shrink and push the whole column past the screen. Opting every layout
   child out of that is the single most effective guard against phone overflow. */
.hero__inner,.sec,.sec__head,.case,.case__meta,.feature,.feature__body,.hand{min-width:0}
/* height:auto is load-bearing, not tidiness. The images carry width/height attributes
   (correctly — they reserve space and stop layout shift), but with only max-width:100%
   the width shrinks while the HTML height stays pinned. The Dimble frame rendered
   354x506 against a true 900x506 — squeezed to 40% of its correct width. Reported from
   a real phone 2026-08-06. Images with their own sizing rules (.card__img, .compare)
   were unaffected, which is why it hid for so long. */
img{max-width:100%;height:auto;display:block}
h1,h2,h3{margin:0;text-wrap:balance;letter-spacing:-.03em;line-height:1.02;font-weight:800}
p{margin:0}

.skip{position:absolute;left:-9999px;top:0;background:var(--bone);color:var(--ink);
  padding:12px 18px;z-index:100;border-radius:0 0 8px 0;font-weight:700}
.skip:focus{left:0}
:focus-visible{outline:2px solid var(--accent);outline-offset:4px;border-radius:3px}

.eyebrow,.kicker{
  font-family:var(--mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--accent);font-weight:600;margin:0 0 18px;
}
.kicker{margin-bottom:12px}

/* ─────────────────────────  HERO  ───────────────────────── */
.hero{
  min-height:100svh;display:flex;flex-direction:column;justify-content:center;
  padding:clamp(80px,12vh,140px) var(--gap) 0;position:relative;
  /* A single soft bloom, off-centre. Not a gradient hero — just enough to stop
     the flat black reading as an unstyled page. */
  background:
    radial-gradient(120vw 80vh at 72% -10%, #1a1d24 0%, transparent 62%),
    radial-gradient(60vw 50vh at 8% 108%, #15171c 0%, transparent 60%);
}
.hero__inner{max-width:var(--maxw);margin:0 auto;width:100%;min-width:0;flex:1;display:flex;
  flex-direction:column;justify-content:center;padding-bottom:60px}
/* Measured at 390px: the headline wraps inside its 354px box at every size from 28px to
   52px, so the floor is a purely aesthetic choice rather than a safety one. 46px keeps the
   opening line strong on a phone without running to four lines. `max-width:100%` stays as a
   cheap guard for narrower devices than we can test. */
.hero h1{font-size:clamp(46px,9.2vw,132px);margin-bottom:26px;max-width:100%}
.hero__em{color:var(--bone-3)}
.hero__lede{max-width:min(56ch,100%);font-size:clamp(15px,1.9vw,21px);color:var(--bone-2);margin-bottom:38px}
.btn{
  align-self:flex-start;display:inline-block;text-decoration:none;
  font-family:var(--mono);font-size:12.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink);background:var(--bone);padding:16px 30px;border-radius:999px;font-weight:700;
  transition:transform .25s var(--ease),background .25s var(--ease);
}
.btn:hover{transform:translateY(-2px);background:#fff}

/* Ticker: states the operational facts without a stat-card row, which every
   agency site has. Duplicated content is aria-hidden. */
.ticker{border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  overflow:hidden;padding:16px 0;margin-top:auto}
.ticker__track{display:flex;gap:26px;white-space:nowrap;width:max-content;
  animation:slide 46s linear infinite;font-family:var(--mono);font-size:12px;
  letter-spacing:.11em;text-transform:uppercase;color:var(--bone-3)}
.ticker i{color:var(--accent);font-style:normal}
@keyframes slide{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  /* This previously set flex-wrap:wrap + white-space:normal, which turned the marquee
     into a wall of twelve stacked lines on a phone — reported from a real device with
     Reduce Motion enabled, which is common on iOS. Honour the preference by stopping the
     ANIMATION, not by re-flowing the layout: keep the single line and let it be scrolled
     by hand. */
  .ticker{overflow-x:auto;scrollbar-width:none}
  .ticker::-webkit-scrollbar{display:none}
  .ticker__track{animation:none;flex-wrap:nowrap;white-space:nowrap;width:max-content}
}

/* ─────────────────────────  SECTIONS  ───────────────────────── */
.sec{padding:clamp(90px,13vh,170px) var(--gap);max-width:var(--maxw);margin:0 auto}
.sec--social,.sec--video{border-top:1px solid var(--line)}
.sec__head{max-width:780px;margin-bottom:clamp(40px,6vw,72px)}
.num{font-family:var(--mono);font-size:12px;letter-spacing:.2em;color:var(--accent);margin:0 0 14px}
.sec h2{font-size:clamp(34px,6.2vw,74px);margin-bottom:22px}
.sec__lede{font-size:clamp(15.5px,1.75vw,19px);color:var(--bone-2);max-width:62ch}
.sec__foot{margin-top:44px;color:var(--bone-3);font-size:15px;max-width:60ch}
.sec__foot em{color:var(--bone-2);font-style:italic}

.stats{display:flex;flex-wrap:wrap;gap:32px 54px;margin:34px 0 0;padding:0}
.stats div{margin:0}
.stats dt{font-family:var(--mono);font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--bone-3);margin-bottom:6px}
.stats dd{margin:0;font-size:clamp(24px,3.4vw,38px);font-weight:800;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums}

/* ───────────  1 · the wall  ─────────── */
.wall{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(10px,1.4vw,20px);align-items:start}
.wall__col{display:flex;flex-direction:column;gap:clamp(10px,1.4vw,20px);will-change:transform}
.wall__col:nth-child(even){margin-top:clamp(24px,5vw,64px)}
.wall figure{margin:0;position:relative;overflow:hidden;border-radius:5px;background:var(--ink-2)}
.wall img{width:100%;height:auto;transition:transform .8s var(--ease),filter .5s var(--ease);
  filter:saturate(.94)}
.wall figure:hover img{transform:scale(1.035);filter:saturate(1.06)}
.wall figcaption{
  position:absolute;left:0;right:0;bottom:0;padding:26px 12px 10px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--bone);background:linear-gradient(transparent,rgba(6,7,9,.86));
  opacity:0;transform:translateY(6px);transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.wall figure:hover figcaption,.wall figure:focus-within figcaption{opacity:1;transform:none}

/* ───────────  2 · the rail  ─────────── */
.rail{
  display:flex;gap:clamp(12px,1.6vw,22px);overflow-x:auto;padding-bottom:22px;
  scroll-snap-type:x mandatory;scrollbar-width:thin;
  scrollbar-color:var(--line) transparent;
  /* break the max-width so the rail runs to the edge — motion work should feel
     like it continues past the frame */
  margin-inline:calc(var(--gap)*-1);padding-inline:var(--gap);
}
.rail::-webkit-scrollbar{height:6px}
.rail::-webkit-scrollbar-thumb{background:var(--line);border-radius:99px}
.card{margin:0;flex:0 0 clamp(190px,23vw,268px);scroll-snap-align:start}
.card__img{border-radius:6px;overflow:hidden;background:var(--ink-2);aspect-ratio:9/16}
.card__img img{width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease)}
.card:hover .card__img img{transform:scale(1.04)}
.card figcaption{padding-top:14px;display:flex;flex-direction:column;gap:3px}
.card b{font-size:14.5px;font-weight:700}
.card span{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--bone-3)}

.feature{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(24px,4vw,58px);
  align-items:center;margin-top:clamp(56px,8vw,96px);
  border-top:1px solid var(--line);padding-top:clamp(40px,6vw,68px)}
.feature__media{border-radius:8px;overflow:hidden;background:var(--ink-2)}
.feature h3{font-size:clamp(28px,4vw,44px);margin-bottom:16px}
.feature p{color:var(--bone-2);margin-bottom:14px;max-width:52ch}
.pills{list-style:none;display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:22px 0 0}
.pills li{font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--bone-2);border:1px solid var(--line);border-radius:999px;padding:7px 13px}

/* ───────────  3 · cases  ─────────── */
/* Stacked, not side-by-side. The comparison IS the case study — squeezing it into a half-
   width column made the single most persuasive thing on the page the smallest. Text above,
   slider full width beneath it, so the work is seen at something close to real size. */
.case{margin-bottom:clamp(72px,10vw,132px)}
.case__meta{max-width:62ch;margin-bottom:clamp(20px,3vw,34px)}
.case h3{font-size:clamp(28px,4.2vw,50px);margin-bottom:16px}
.case p{color:var(--bone-2);margin-bottom:14px}
.case__hint{font-family:var(--mono);font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--bone-3);margin-top:16px;text-align:center}

/* before/after — the single strongest proof a portfolio can carry */
/* Full-bleed to the section edges and a shallower ratio, so the comparison is large enough
   to actually read the old site against the new one. */
.compare{position:relative;border-radius:10px;overflow:hidden;background:var(--ink-2);
  cursor:ew-resize;touch-action:pan-y;user-select:none;aspect-ratio:16/10;
  box-shadow:0 24px 70px -30px rgba(0,0,0,.9);border:1px solid var(--line)}
.compare img{width:100%;height:100%;object-fit:cover;object-position:top center;pointer-events:none}
/* CLIP-PATH, not width. Two earlier attempts sized the "before" layer by changing its own
   width — first with `min-width:100%` (which squeezed the image as the handle moved) and
   then by pinning a pixel width from JS (which rendered it 0px wide, and therefore blank,
   whenever the script ran before layout had settled).
   Both were the same mistake: making the image's SIZE do the job of a MASK. With clip-path
   the layer is always full size and perfectly aligned with the layer beneath it; only the
   visible region changes. Nothing can squeeze, and nothing depends on JS for geometry. */
.compare__clip{position:absolute;inset:0;width:100%;overflow:hidden;
  clip-path:inset(0 50% 0 0);will-change:clip-path}
.compare__clip img{width:100%;height:100%;object-fit:cover;object-position:top center}
.compare__handle{position:absolute;top:0;bottom:0;left:50%;width:2px;background:var(--bone);
  transform:translateX(-1px);will-change:left;pointer-events:none}
.compare__handle span{position:absolute;top:50%;left:50%;width:42px;height:42px;
  transform:translate(-50%,-50%);border-radius:50%;background:var(--bone);
  box-shadow:0 4px 18px rgba(0,0,0,.5)}
.compare__handle span::before,.compare__handle span::after{
  content:"";position:absolute;top:50%;width:0;height:0;border:5px solid transparent}
.compare__handle span::before{left:8px;border-right-color:var(--ink);transform:translateY(-50%)}
.compare__handle span::after{right:8px;border-left-color:var(--ink);transform:translateY(-50%)}
.compare__tag{position:absolute;bottom:14px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.16em;text-transform:uppercase;background:rgba(8,9,12,.74);
  color:var(--bone);padding:6px 11px;border-radius:4px;pointer-events:none}
.compare__tag--l{left:14px}.compare__tag--r{right:14px}

/* min-width:0 is the whole fix here. A grid item defaults to min-width:auto, meaning it
   refuses to shrink below its content's intrinsic size — and this grid holds 1400px-wide
   screenshots. Two of them set an intrinsic floor of ~2800px, so the column blew past the
   viewport and the case study rendered enormous and cropped. Every grid/flex child that
   contains an image needs this, which is why the rule below is broad rather than targeted. */
.shots{display:grid;grid-template-columns:1fr 1fr;gap:clamp(10px,1.4vw,16px);min-width:0}
.shots img{width:100%;height:auto;border-radius:6px;background:var(--ink-2)}
.case>*,.feature>*,.wall__col,.rail>*{min-width:0}

/* ───────────  4 · handover  ─────────── */
.sec--hand{border-top:1px solid var(--line)}
.hand{max-width:900px}
.hand__list{list-style:none;counter-reset:h;padding:0;margin:44px 0 0;
  display:grid;grid-template-columns:1fr 1fr;gap:0}
.hand__list li{counter-increment:h;padding:22px 26px 22px 0;border-top:1px solid var(--line);
  display:flex;flex-direction:column;gap:5px}
.hand__list li::before{content:"0" counter(h);font-family:var(--mono);font-size:10.5px;
  letter-spacing:.16em;color:var(--accent);margin-bottom:4px}
.hand__list b{font-size:17px;font-weight:700}
.hand__list span{color:var(--bone-3);font-size:14.5px}

/* ─────────────────────────  FOOT  ───────────────────────── */
.foot{border-top:1px solid var(--line);padding:clamp(70px,10vh,120px) var(--gap);
  max-width:var(--maxw);margin:0 auto;text-align:center}
.foot .eyebrow{margin-bottom:16px}
.foot__big{font-size:clamp(26px,4.6vw,58px);font-weight:800;letter-spacing:-.03em;
  line-height:1.06;text-wrap:balance;margin-bottom:18px;max-width:100%}
.foot__lede{color:var(--bone-2);font-size:clamp(15px,1.7vw,18px);max-width:52ch;
  margin:0 auto clamp(28px,4vw,44px)}
/* The address is the call to action, so it is sized like one rather than tucked into small
   print. Underline offset keeps the descenders clear at this weight. */
.foot__mail{display:inline-block;font-size:clamp(20px,3.4vw,40px);font-weight:800;
  letter-spacing:-.02em;color:var(--bone);text-decoration:underline;
  text-decoration-color:var(--accent);text-decoration-thickness:2px;text-underline-offset:7px;
  word-break:break-word;transition:color .25s var(--ease),text-decoration-color .25s var(--ease)}
.foot__mail:hover{color:var(--accent);text-decoration-color:var(--bone)}
.foot__small{font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--bone-3);margin-top:clamp(34px,5vw,56px)}

/* ─────────────────────────  REVEAL  ─────────────────────────
   Content is VISIBLE BY DEFAULT. The hidden-then-reveal state only exists once JS has
   confirmed it is running, by putting .js-reveal on <html>.

   This is not defensive tidiness — it is the difference between a portfolio and a blank
   page. Hiding first and revealing later means any failure in between (JS blocked, a
   script error, an observer that evaluated against a zero-size viewport — which is exactly
   what happened here) leaves the client looking at nothing at all. Whatever goes wrong,
   the work should still be on screen. */
.js-reveal .reveal{opacity:0;transform:translateY(22px);
  transition:opacity .7s var(--ease),transform .7s var(--ease)}
.js-reveal .reveal.in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){
  .reveal{opacity:1;transform:none;transition:none}
  .wall__col{transform:none!important}
  .wall img,.card__img img{transition:none}
}

/* ─────────────────────────  RESPONSIVE  ───────────────────────── */
@media (max-width:900px){
  .feature,.case,.case--alt{grid-template-columns:1fr}
  .case--alt .case__meta{order:0}
  .wall{grid-template-columns:repeat(3,1fr)}
  .hand__list{grid-template-columns:1fr}
}
@media (max-width:620px){
  .wall{grid-template-columns:repeat(2,1fr)}
  .wall__col--hide-sm{display:none}
  .wall__col{transform:none!important}   /* no parallax on a small screen */
  .stats{gap:22px 34px}
  .compare{aspect-ratio:4/3}
  .shots{grid-template-columns:1fr}
}

/* ─────────────────────────  NAV  ─────────────────────────
   Added 2026-08-06. Same restraint as everything else on the page: mono caps
   at eyebrow scale, the numerals in --accent because that is the only job the
   accent has here, and no colour anywhere else.

   STICKY, NOT REVEALED (Danny 2026-08-06: "static and visible, not a dropdown").
   An earlier version slid in once you left the hero; a bar that appears out of
   nowhere reads as a dropdown and makes the page feel like it is reacting to
   you. `sticky` keeps it in normal flow at the top — so it occupies its own
   space instead of covering the headline — and then pins as you scroll. It is
   present from the first frame and never animates. */
.nav{
  position:sticky;top:0;z-index:80;
  background:color-mix(in srgb, var(--ink) 88%, transparent);
  border-bottom:1px solid var(--line);
}
@supports (backdrop-filter:blur(10px)){
  .nav{backdrop-filter:blur(14px) saturate(1.3)}
}

.nav__inner{
  max-width:var(--maxw);margin:0 auto;min-width:0;
  padding:0 var(--gap);height:60px;
  display:flex;align-items:center;justify-content:space-between;gap:var(--gap);
}
.nav__mark{
  font-family:var(--mono);font-size:12px;letter-spacing:.18em;text-transform:uppercase;
  font-weight:700;color:var(--bone);text-decoration:none;white-space:nowrap;flex:none;
}
.nav__mark span{border-bottom:1px solid var(--accent);padding-bottom:2px}

.nav__list{
  list-style:none;margin:0;padding:0;min-width:0;
  display:flex;align-items:center;gap:clamp(14px,2.4vw,30px);
  /* On a narrow screen the titles scroll sideways instead of collapsing into a
     hamburger: one tap fewer, no JS, and the section names stay legible — which
     is the entire point of the bar. */
  overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.nav__list::-webkit-scrollbar{display:none}
.nav__list a{
  display:inline-flex;align-items:baseline;gap:7px;white-space:nowrap;
  font-family:var(--mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--bone-3);text-decoration:none;padding:6px 0;
  border-bottom:1px solid transparent;
  transition:color .25s var(--ease),border-color .25s var(--ease);
}
.nav__list i{font-style:normal;color:var(--accent);font-size:10px;letter-spacing:.08em}
.nav__list a:hover{color:var(--bone)}
.nav__list a[aria-current="true"]{color:var(--bone);border-bottom-color:var(--accent)}

/* The pinned bar would otherwise cover the top of whatever a link jumps to. */
.sec,.hero{scroll-margin-top:60px}
/* The bar now takes 60px of real layout at the top, so a 100svh hero would push
   the page just past one screen and invent a scrollbar on the first view. */
.hero{min-height:calc(100svh - 60px)}

@media (max-width:640px){
  .nav__inner{height:54px;gap:14px}
  .nav__mark{font-size:11px;letter-spacing:.14em}
  /* The numerals are the first thing to go when space is tight — the words carry
     the meaning, the numbers are decoration at this size. */
  .nav__list i{display:none}
  .sec,.hero{scroll-margin-top:54px}
  .hero{min-height:calc(100svh - 54px)}
}

/* ─────────────────────────  LEAD FORM  ─────────────────────────
   Added 2026-08-06. Deliberately plain: dark fields, one accent line on focus, nothing that
   looks like a marketing funnel. The page argues for restraint; the form has to agree with it.

   Inputs are 16px on purpose. Anything smaller makes iOS Safari zoom the whole page on focus,
   which on a phone reads as the site breaking the moment you try to contact someone. */
.lead{margin:34px 0 26px;max-width:640px;display:flex;flex-direction:column;gap:16px}
.lead__row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.lead__f{display:flex;flex-direction:column;gap:7px;min-width:0}
.lead__f span{font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--bone-3)}
.lead input,.lead textarea{
  font:400 16px/1.5 var(--sans);color:var(--bone);background:var(--ink-2);
  border:1px solid var(--line);border-radius:8px;padding:13px 14px;width:100%;
  transition:border-color .2s var(--ease),background .2s var(--ease);
  -webkit-appearance:none;appearance:none;
}
.lead textarea{resize:vertical;min-height:110px}
.lead input:focus,.lead textarea:focus{outline:none;border-color:var(--accent);background:var(--ink-3)}
.lead input:user-invalid,.lead textarea:user-invalid{border-color:var(--crit,#c8686b)}
.lead__send{align-self:flex-start;margin-top:4px;border:0;cursor:pointer;font-family:var(--mono)}
.lead__send[disabled]{opacity:.55;cursor:default}
.lead__note{font-size:13px;color:var(--bone-2);min-height:1.2em}
.lead__note.is-bad{color:#e0908f}
.lead__note.is-good{color:#8fd6a8}
/* Off-screen rather than display:none — some bots skip hidden fields but not positioned ones. */
.lead__hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.foot__or{font-size:13.5px;color:var(--bone-3)}
.foot__or .foot__mail{font-size:inherit}

@media (max-width:620px){
  .lead__row{grid-template-columns:1fr}
  .lead__send{align-self:stretch;text-align:center}
}

/* ─────────────────────  CLICKABLE WORK  ─────────────────────
   Added 2026-08-06. Every cover, frame and episode still links to the channel it came from, so
   the portfolio proves itself: a visitor can go and see the account rather than take our word.

   `display:block` matters — an inline <a> around a block image adds a phantom descender and
   knocks the wall columns out of alignment. All three wrappers are blocks. */
.wall__link,.card__link,.feature__link{display:block;text-decoration:none;color:inherit;
  border-radius:inherit;position:relative;-webkit-tap-highlight-color:transparent}
.wall__link{overflow:hidden;border-radius:4px}
.feature__link{overflow:hidden;border-radius:8px}

/* The affordance is deliberately quiet: the work is the hero, not the interaction. */
.wall__link img,.card__link img,.feature__link img{transition:transform .55s var(--ease),
  filter .35s var(--ease)}
.wall__link:hover img,.card__link:hover img,.feature__link:hover img{transform:scale(1.03)}
.feature__link:hover img{transform:scale(1.02)}

/* Focus must be obvious for keyboard users even though hover is subtle. */
.wall__link:focus-visible,.card__link:focus-visible,.feature__link:focus-visible{
  outline:2px solid var(--accent);outline-offset:3px}

@media (prefers-reduced-motion:reduce){
  .wall__link img,.card__link img,.feature__link img{transition:none}
  .wall__link:hover img,.card__link:hover img,.feature__link:hover img{transform:none}
}
/* On touch there is no hover, so the scale would fire on tap and look like a glitch. */
@media (hover:none){
  .wall__link:hover img,.card__link:hover img,.feature__link:hover img{transform:none}
}
