/* ==========================================================================
   KX1 Media — shared system
   Direction B's editorial structure, inverted to a blue-tinted near-black.
   Blue is the world (structure, links, atmosphere). Red is the decision
   (the highlight mark, the primary action). They never touch: the ground
   is always the buffer, which is what stops them vibrating against each other.
   ========================================================================== */

@font-face{font-family:"Archivo";src:url("/fonts/archivo.woff2?v=e3a28eade2") format("woff2");font-weight:400 800;font-stretch:100% 125%;font-style:normal;font-display:swap}
@font-face{font-family:"Newsreader";src:url("/fonts/newsreader-italic.woff2?v=2bf0dbb1a4") format("woff2");font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("/fonts/jetbrains-mono.woff2?v=83c005d49d") format("woff2");font-weight:400 800;font-style:normal;font-display:swap}

:root{
  /* ---- primitives ---- */
  --c-ink-900:#000000; --c-ink-800:#0F131A; --c-ink-700:#141821; --c-ink-600:#1C222E;
  --c-line-1:#232B39;  --c-line-2:#38455C;
  --c-cloud:#E7EAF2;   --c-slate:#98A2B8;   --c-slate-dim:#6B7688;
  --c-blue:#4DA3FF;    --c-blue-bright:#6FB4FF; --c-blue-deep:#2A3A55;
  --c-red:#FF4438;     --c-red-btn:#DC2A1E;  --c-red-btn-hi:#C9271B;

  /* ---- semantic ---- */
  --bg:var(--c-ink-900); --bg-recessed:var(--c-ink-800);
  --surface:var(--c-ink-700); --surface-raised:var(--c-ink-600);
  --hairline:var(--c-line-1); --border:var(--c-line-2);
  --text:var(--c-cloud); --text-muted:var(--c-slate); --text-faint:var(--c-slate-dim);
  --accent:var(--c-blue); --accent-hover:var(--c-blue-bright); --accent-quiet:var(--c-blue-deep);
  --focus:var(--c-blue-bright);
  /* Contrast is the decision signal now, not a hue: on pure black nothing
     outranks white. Red stays as --signal -- the hero rule, the active tour
     marker, the service dashes -- so it is a pure accent rather than a thing
     you are meant to press. The red button primitives are kept below as a
     record of what this was. */
  --signal:var(--c-red); --action:#FFFFFF; --action-hover:var(--c-cloud);
  --on-action:#000000;

  /* ---- type ---- */
  --f-display:"Archivo",Helvetica,Arial,sans-serif;
  --f-body:"Archivo",system-ui,sans-serif;
  --f-serif:"Newsreader",Georgia,serif;
  --f-mono:"JetBrains Mono",ui-monospace,monospace;
  --w-display:700; --w-h2:650;
  --fs-h1:clamp(2rem,1rem + 5.2vw,4.9rem);
  --fs-h2:clamp(1.7rem,1rem + 4.4vw,3.8rem);
  --fs-statement:clamp(1.9rem,1rem + 5.4vw,4.6rem);
  --fs-h3:clamp(1.05rem,1rem + .9vw,1.5rem);
  --fs-body:clamp(1rem,.96rem + .2vw,1.125rem);
  --fs-sm:.9375rem; --fs-micro:.6875rem;
  --track-body:.006em; --lh-body:1.6;

  --edge-mask:linear-gradient(90deg,rgba(0,0,0,.14) 0,rgba(0,0,0,.3) 4%,rgba(0,0,0,.52) 9%,rgba(0,0,0,.76) 15%,rgba(0,0,0,.93) 20%,#000 24%,#000 76%,rgba(0,0,0,.93) 80%,rgba(0,0,0,.76) 85%,rgba(0,0,0,.52) 91%,rgba(0,0,0,.3) 96%,rgba(0,0,0,.14) 100%);
  --cursor-arrow:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiIgdmlld0JveD0iMCAwIDU0MCA1NDAiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48cGF0aCBkPSJNMTMyIDg5TDQwOCAzMDdMMjY2IDMzM0wxNTYgNDUxWiIgc3Ryb2tlLXdpZHRoPSIxNjgiIG9wYWNpdHk9Ii4xMCIvPjxwYXRoIGQ9Ik0xMzIgODlMNDA4IDMwN0wyNjYgMzMzTDE1NiA0NTFaIiBzdHJva2Utd2lkdGg9IjEyNCIgb3BhY2l0eT0iLjE3Ii8+PHBhdGggZD0iTTEzMiA4OUw0MDggMzA3TDI2NiAzMzNMMTU2IDQ1MVoiIHN0cm9rZS13aWR0aD0iODYiIG9wYWNpdHk9Ii4yNiIvPjxwYXRoIGQ9Ik0xMzIgODlMNDA4IDMwN0wyNjYgMzMzTDE1NiA0NTFaIiBzdHJva2Utd2lkdGg9IjYwIiBvcGFjaXR5PSIuMzYiLz48L2c+PHBhdGggZD0iTTEzMiA4OUw0MDggMzA3TDI2NiAzMzNMMTU2IDQ1MVoiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSI0NiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");
  --sp-2:.5rem;--sp-3:.75rem;--sp-4:1rem;--sp-6:1.5rem;--sp-8:2rem;
  --sp-12:3rem;--sp-16:4rem;--sp-24:6rem;
  --ease:cubic-bezier(.16,1,.3,1);
  --wrap:1240px;
}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{background:var(--bg);color:var(--text);
  font:400 var(--fs-body)/var(--lh-body) var(--f-body);letter-spacing:var(--track-body);
  overflow-x:clip;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent}
img,svg,video{display:block;max-width:100%}
/* the display:block above beats the UA stylesheet's [hidden]{display:none},
   so hiding an img via the hidden attribute silently does nothing */
[hidden]{display:none!important}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--focus);outline-offset:3px}
.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;
  padding-inline:max(clamp(1.25rem,4vw,2.5rem),env(safe-area-inset-left))}
.mono{font:500 var(--fs-micro)/1.5 var(--f-mono);letter-spacing:.14em;text-transform:uppercase}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* Copy the panel has hidden ([hidden] itself is handled above). What is left
   around it goes too: a service caption with no service name, a question that
   opens to nothing, a list with no items, a statement band with no statement. */
.svc__line:has(> h3[hidden]),
.faq details:has(> p[hidden]),
.col ul:not(:has(> li:not([hidden]))),
.trail:not(:has(.trail__msg > :not([hidden]))),
section:has(> [data-kx-gallery][hidden]){display:none}
.skip{position:absolute;left:-9999px;background:var(--action);color:var(--on-action);
  padding:var(--sp-3) var(--sp-4);font-weight:700;z-index:200}
.skip:focus{left:var(--sp-4);top:var(--sp-4)}
.lede{font:400 clamp(1.05rem,1rem + .9vw,1.45rem)/1.45 var(--f-body);
  color:var(--text);max-width:36ch}

/* ---------- nav ---------- */
.nav{position:sticky;top:0;z-index:60}
/* The bar spans the window so its rule and blur reach both edges, but its
   contents share .wrap's box exactly -- same max-width, same padding -- so the
   wordmark lines up with the dateline and the links with the column edge
   rather than floating off at the viewport edge. */
.nav__in{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-6);
  width:100%;max-width:var(--wrap);margin-inline:auto;
  padding:var(--sp-4) max(clamp(1.25rem,4vw,2.5rem),env(safe-area-inset-left))}
/* blur on a pseudo-layer: backdrop-filter on the sticky element itself can
   collapse a contained fixed panel in Safari */
.nav::before{content:"";position:absolute;inset:0;z-index:-1;
  background:rgba(0,0,0,.72);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  transition:background .2s ease}
.nav.is-stuck::before{background:rgba(0,0,0,.9)}
.mark{font:800 clamp(1.15rem,.855rem + .77vw,1.62rem)/1 var(--f-display);font-stretch:112%;
  margin-left:-.7rem;
  letter-spacing:-.02em;text-transform:uppercase;
  display:inline-flex;align-items:center;gap:.44em}
/* The play glyph is the logo's own mark, sized off the wordmark so the two
   scale as one lockup. .82em matches the cap height rather than the em box --
   set to 1em it sits visibly taller than the letters beside it. */
.mark__play{flex:0 0 auto;height:.82em;width:auto;display:block}
.nav__right{display:flex;align-items:flex-start;gap:var(--sp-6)}
/* the list sits last, flush to the edge; items stay left-aligned inside it so
   the dash gutter reads consistently down the column */
.nav__right>ul{order:3;margin-left:var(--sp-2)}
/* Above 1100px the link column leaves the bar and becomes a rail fixed to the
   right gutter, centred on the viewport, so it stays in reach at any scroll
   depth. It floats over the page deliberately; the links already carry a
   text-shadow for the frames it crosses. Below 1100px the gutter is too narrow
   to hold it clear of the content column, so the MENU button takes over. */
@media(min-width:1101px){
  .nav__right>ul{position:fixed;isolation:isolate;top:50%;transform:translateY(-50%);
    right:max(clamp(1.25rem,4vw,2.5rem),env(safe-area-inset-right));
    margin-left:0}  /* It ran as a bounded box, so its top, bottom and right edges were visible
     over a bright frame -- the veil itself became a shape. It reaches past the
     window edge now so there is no right edge to see, fades in from the left
     rather than radiating from a point, and is masked away at top and bottom
     so it ends in nothing instead of stopping. */
  .nav__right>ul::before{content:"";position:absolute;
    inset:-4rem calc(-1 * (clamp(1.25rem,4vw,2.5rem) + 1rem)) -4rem -2.5rem;
    z-index:-1;pointer-events:none;
    background:linear-gradient(90deg,
      rgb(var(--rail-rgb,0 0 0)/0) 0%,
      rgb(var(--rail-rgb,0 0 0)/.40) 58%,
      rgb(var(--rail-rgb,0 0 0)/.40) 100%);
    -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 30%,#000 70%,transparent 100%);
    mask-image:linear-gradient(180deg,transparent 0,#000 30%,#000 70%,transparent 100%);
    transition:background .28s ease}
}
/* Above 1100px the links leave the bar for the side rail, which left the top
   bar holding a wordmark and nothing else. This is the one action worth having
   permanently in reach, and it is not a duplicate of CONTACT in the rail --
   you cannot tell those lead to the same place without clicking one. Outlined
   rather than filled: it is on screen at all times and a solid white block
   would shout the whole way down the page. */
/* Option C, with Victor's two changes: heavier type, and a grey ground at
   rest instead of nothing, so it reads as a control without shouting. The
   hover is unchanged -- white fill, black type. */
.navcta{display:inline-flex;align-items:center;min-height:28px;padding:0 var(--sp-3);
  border:1px solid var(--hairline);background:rgba(231,234,242,.08);
  color:var(--text);white-space:nowrap;
  font:700 10.5px/1 var(--f-mono);letter-spacing:.14em;text-transform:uppercase;
  transition:background .18s,color .18s,border-color .18s}
.navcta:hover,.navcta:focus-visible{background:var(--text);color:#000;border-color:var(--text)}
.nav__right>.burger{align-self:flex-start}
/* the stacked list, right-aligned. A dash slides in on hover and stays put on
   the current page, so position never shifts -- the dash occupies its gutter
   whether or not it is visible. */
.nav ul{display:grid;gap:2px;list-style:none;text-align:left}
.nav ul a{display:inline-block;position:relative;padding-left:18px;
  font:500 .75rem/1.85 var(--f-mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--text);transition:transform .09s linear,color .18s;
  transform-origin:right top;will-change:transform;
  /* One faint drop, not a halo. The three-layer shadow read as an outline
     around every letter; the dark veil behind the list is what actually
     keeps it legible over a bright frame, and it was already there. */
  text-shadow:0 1px 1px rgba(0,0,0,.35)}
.nav ul a::before{content:"";position:absolute;left:0;top:50%;width:10px;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:left center;
  transition:transform .22s var(--ease);opacity:.9}
.nav ul a:hover{color:var(--text)}
.nav ul a:hover::before{transform:scaleX(1)}
.nav ul a[aria-current=page]{color:var(--accent)}
.nav ul a[aria-current=page]::before{transform:scaleX(1)}
@media(max-width:1100px){.nav ul{display:none}}
/* M1. The edge fade is measured against a desktop strip, where there is a lot
   of frame either side of the middle. On a phone the whole strip is about two
   tiles wide, so the same gradient eats most of what there is: at 24% in from
   each edge, nearly half the picture was under it. Same shape, far less of it
   -- it still says the film continues past the window without taking the
   film with it. */
@media(max-width:720px){
  :root{--edge-mask:linear-gradient(90deg,
    rgba(0,0,0,.55) 0,rgba(0,0,0,.74) 4%,rgba(0,0,0,.9) 8%,#000 12%,
    #000 88%,rgba(0,0,0,.9) 92%,rgba(0,0,0,.74) 96%,rgba(0,0,0,.55) 100%)}
}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  font:700 var(--fs-sm)/1 var(--f-body);min-height:48px;padding:var(--sp-3) var(--sp-6);
  background:var(--action);color:var(--on-action);border:1px solid var(--action);cursor:pointer;
  transition:background .18s,border-color .18s}
.btn:hover{background:var(--action-hover);border-color:var(--action-hover)}
.btn--ghost{background:transparent;color:var(--text);border-color:var(--border)}
.btn--ghost:hover{background:var(--surface);border-color:var(--text)}
.btn--block{display:flex;width:100%}
@media(hover:none){
  .btn:active,.hit:active,.burger:active,.lb__close:active,.lb__nav:active,.talk__close:active,
  .filters label:active,.more:active{opacity:.8;transition:opacity .05s}
}

/* ---------- section shell ---------- */
section{padding-block:clamp(3rem,7vw,var(--sp-24))}
/* The form is the page's last stop, not a new chapter: it follows the section
   before it closely instead of a full section's air on both sides. */
section:has(+ #contact){padding-bottom:clamp(2rem,4vw,var(--sp-12))}
section#contact{padding-top:clamp(1rem,2vw,var(--sp-6))}
.head{display:grid;gap:var(--sp-4);
  padding-top:var(--sp-4);margin-bottom:clamp(2.5rem,5vw,5rem)}
@media(min-width:860px){.head{grid-template-columns:1fr 1fr;align-items:end}}
/* The two-column head assumes a description in the right cell. When the
   section only carries a headline, that cell is empty and the headline sits in
   half the page for no reason -- which is what the gap on the services block
   was. This releases it to the full width. */
@media(min-width:860px){.head--solo{grid-template-columns:1fr}}
/* inline-flex is not a block box, so margin-left:auto has nothing to push
   against; it needs a block-level box sized to its content. Written as
   .more.more--center because the base .more rule is further down the file and
   would otherwise win the display declaration on source order alone. */
.more.more--center{display:flex;width:fit-content;margin-inline:auto}
.head h2{font:var(--w-h2) var(--fs-h2)/1 var(--f-display);font-stretch:106%;letter-spacing:-.02em;
  text-transform:uppercase;max-width:16ch}
.head p{color:var(--text-muted);font-size:var(--fs-sm);max-width:42ch}
/* The moment someone reads themselves into the page, so it is not set at
   the size of a caption. */
.head p.head__lead{color:var(--text);font:400 var(--fs-h3)/1.45 var(--f-body);max-width:34ch}
/* Label, then sentence, each on its own line at the full width of the column
   -- so the opening line measures the same as the row of rooms underneath it
   rather than stopping half way, and the section number reads before anything
   else in the section instead of beside it. */
.head--stack{grid-template-columns:1fr}
@media(min-width:860px){.head--stack{grid-template-columns:1fr;align-items:start}}
.head--stack p.head__lead{max-width:none}
.eyebrow{color:var(--text-muted)}
.eyebrow b{color:var(--accent);font-weight:500}
.more{display:inline-flex;align-items:center;gap:.5rem;margin-top:var(--sp-6);
  font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--accent);border-bottom:1px solid var(--accent-quiet);padding-bottom:6px;min-height:44px}
.more:hover{color:var(--accent-hover);border-color:var(--accent)}
/* M11. The same control as the link it copies, because it does the same kind
   of thing -- it takes you to the rest. A button needs its own reset: the
   rule above draws the underline with border-bottom, and a button arrives
   carrying a border on all four sides. */
button.more{background:none;border-width:0 0 1px;border-style:solid;
  cursor:pointer;font-family:inherit}
/* Shown only where the capping happens. Written as .more.expander so it
   weighs the same as .more.more--center, which sets display, and later so it
   wins. */
.more.expander{display:none}
@media(max-width:720px){
  /* Four, then ask. Twelve full-width films before the photography gets a
     chance read as a page that is only films. */
  .is-capped:not(.is-open) > *:nth-child(n+5){display:none}
  .more.expander{display:flex}
}

/* ---------- page masthead ---------- */
.masthead{padding-block:clamp(2.5rem,6vw,var(--sp-16)) clamp(.6rem,1.2vw,var(--sp-4));
  border-bottom:1px solid var(--border)}
.masthead .eyebrow{font-size:clamp(.8rem,.7rem + .4vw,.95rem)}
/* The sentence the studio is built on, directly under the statement, and the
   standing line under that -- one is prose, the other is a fact, so they are
   not the same thing at the same size. */
/* On About it is one paragraph in a column of prose and wants a measure. On
   Services it opens the page and runs the full width the wrap allows. */
.masthead .mission{max-width:none}
.mission{max-width:56ch;margin-top:var(--sp-6);color:var(--text-muted);
  font:400 clamp(1.05rem,1rem + .9vw,1.45rem)/1.45 var(--f-body)}
/* On Services the standing paragraph belongs to the heading above it and
   introduces the cards below it, and it was set the wrong way round: a full
   section's air above and none at all beneath, so it read as the tail of the
   headline instead of the sentence the four services answer. */
.head:has(+ .mission){margin-bottom:clamp(1.25rem,2vw,1.75rem)}
.mission:has(+ div .cols){margin-bottom:clamp(2.75rem,5vw,4.5rem)}
.locale{margin-top:var(--sp-6);padding-top:var(--sp-4);border-top:1px solid var(--hairline);
  color:var(--text);display:inline-block}
.masthead h1{font:800 var(--fs-h1)/.94 var(--f-display);font-stretch:112%;letter-spacing:-.03em;
  text-transform:uppercase;text-wrap:balance;margin-block:var(--sp-4) var(--sp-3)}

/* ---------- hero ---------- */
.dateline{display:flex;flex-wrap:wrap;gap:var(--sp-4);justify-content:space-between;
  border-bottom:1px solid var(--border);padding-bottom:var(--sp-3);margin-bottom:var(--sp-8);
  color:var(--text-muted)}
.hero .lede{margin-block:var(--sp-6)}
/* A statement over a staircase. The headline runs the full measure, so the
   two sentences land one per line where there is room and balance keeps any
   other copy even-ragged. Three stills step from the top-right down to the
   middle -- a tall portrait, a wide frame under the type, a square -- and
   the two actions close the bottom-right, sharing their bottom edge with the
   square and their right edge with the portrait. Every image box is sized
   before its image arrives. The height a tall screen leaves over opens
   between the statement and the stills, never under the buttons. Phones
   stack statement, mosaic, buttons; the mosaic keeps the same three pieces. */
.hero{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
  gap:clamp(var(--sp-3),1.5vw,var(--sp-4));align-content:start;
  /* Six squares, two to a row on a phone. One gap everywhere -- the grid's own
     -- so nothing has to be nudged into line. */
  grid-template-areas:
    "h h h h h h h h h h h h"
    "a a a a a a b b b b b b"
    "c c c c c c d d d d d d"
    "e e e e e e f f f f f f"
    "t t t t t t t t t t t t";
  padding-block:clamp(2.5rem,6vw,var(--sp-16)) clamp(1.5rem,3vw,var(--sp-8))}
.hero h1{grid-area:h;align-self:start;
  font:var(--w-display) var(--fs-h1)/.94 var(--f-display);font-stretch:112%;
  letter-spacing:-.03em;text-transform:uppercase;text-wrap:balance;
  margin-bottom:clamp(var(--sp-4),3vw,var(--sp-8))}
/* Every frame is the same square with the same corner. The mosaic that stood
   here before gave each picture its own shape and span, which is what left the
   holes -- and closing those by stretching made the shapes disagree instead. */
.hero__fig{border-radius:10px;aspect-ratio:1/1;align-self:start}
.hero__fig--a{grid-area:a}
.hero__fig--b{grid-area:b}
.hero__fig--c{grid-area:c}
.hero__fig--d{grid-area:d}
.hero__fig--e{grid-area:e}
.hero__fig--f{grid-area:f}
/* The strip above is the monochrome one. These are the rooms someone is meant
   to recognise as their own, so they keep their colour. */
.hero__fig .frame img,.hero .hero__img{filter:contrast(1.02)}
.hero__cta{grid-area:t;display:flex;flex-wrap:wrap;gap:var(--sp-3);
  margin-top:clamp(var(--sp-4),3vw,var(--sp-8))}
.hero__cta .btn{flex:1 1 auto}
@media(min-width:640px){
  /* Three to a row from here up: 3 x 2, as asked for. */
  .hero{grid-template-areas:
    "h h h h h h h h h h h h"
    "a a a a b b b b c c c c"
    "d d d d e e e e f f f f"
    "t t t t t t t t t t t t"}
  .hero__cta{justify-content:flex-end}
  .hero__cta .btn{flex:0 0 auto}
}
@media(min-width:900px){
  /* The spare row is where a tall screen's extra height goes -- between the
     statement and the pictures, never inside them. */
  .hero{min-height:min(85svh,900px);
    grid-template-rows:auto minmax(var(--sp-4),1fr) auto auto;
    grid-template-areas:
      "h h h h h h h h h h h h"
      ". . . . . . . . . . . ."
      "a a a a b b b b c c c c"
      "d d d d e e e e f f f f"}
  .hero h1{margin-bottom:0}
}
@media(min-width:1101px){.hero__cta{flex-wrap:nowrap}}
/* the strip sits directly under the statement, so it needs no top padding of
   its own -- the hero's bottom padding is the whole gap */
/* The strip leads the page, so it sits just under the nav rather than a full
   section's breathing room away from it. */
.stripwrap{padding-block:clamp(1rem,2.4vw,var(--sp-8))}
/* --sp-10 is not in the scale. An undefined var() is invalid at computed-value
   time, which drops the property to its INITIAL value and still wins the
   cascade -- so this was silently zeroing the hero's own top padding rather
   than doing nothing. The tight gap it produced by accident is worth keeping,
   now stated on purpose: the caption under the strip already separates them. */
.stripwrap + .hero{padding-top:clamp(var(--sp-4),2.4vw,var(--sp-8))}

/* ---------- frames: the one hover language, everywhere ---------- */
.frame{position:relative;overflow:hidden;background:var(--bg-recessed)}
.frame img{width:100%;height:100%;object-fit:cover;
  filter:grayscale(1) contrast(1.06) brightness(.94);
  transform:scale(1.001); /* stops a 1px shimmer edge on iOS during the filter tween */
  transition:transform .45s var(--ease)}
/* filter repaints every frame, unlike transform/opacity. Only animate it where
   there is a real pointer; on touch the hover state fires from tap simulation
   at the same moment the tap is opening the lightbox. */
@media(hover:hover) and (pointer:fine){
  .frame img{transition:filter .45s var(--ease),transform .45s var(--ease)}
}
/* inset box-shadow, not a border and not an SVG rect: it rasterises inside the
   same clipped box as the image, so it is flush on all four sides at any aspect
   ratio or DPR, and it never takes layout space */
.hit:hover .frame img,.hit:focus-visible .frame img{filter:grayscale(0) contrast(1.02);transform:scale(1.03)}
/* M9. Colour is the reward for paying attention to a picture, and on a
   pointer that is hovering it. There is no hover on a phone, so every one of
   these stayed grey for the whole visit -- the treatment had no second half.
   Coming into the middle of the screen is the touch equivalent of looking at
   something, so that is what turns it: kx1.js marks a tile `.lit` while it
   sits in the middle band of the viewport.

   It is applied to every gallery rather than the two Victor named. The two he
   named are where he noticed it, but Work's photographs are the same tiles
   with the same rule, and leaving them grey on touch would be keeping a bug
   on purpose. Say the word and it narrows. */
@media(hover:none),(pointer:coarse){
  .hit .frame img{transition:filter .6s var(--ease)}
  .hit.lit .frame img{filter:grayscale(0) contrast(1.02)}
}
@media(prefers-reduced-motion:reduce){.hit .frame img{transition:none}}
.hit{display:block;cursor:pointer;background:none;border:0;padding:0;color:inherit;text-align:left;width:100%}

/* ---------- video gallery ---------- */
.gallery{display:grid;gap:var(--sp-4)}
.gallery__lead .frame{aspect-ratio:4/3;border-radius:10px}
.gallery__row{display:grid;gap:var(--sp-4);grid-template-columns:1fr}
@media(min-width:760px){.gallery__row{grid-template-columns:repeat(3,1fr)}}
.gallery__row .frame{aspect-ratio:4/3;border-radius:10px}
.gallery__row .is-vertical .frame{aspect-ratio:9/16}
.play{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}
.play span{position:relative;width:56px;height:56px;border-radius:50%;display:grid;place-items:center}
.play span::before{content:"";position:absolute;inset:0;border-radius:50%;
  background:rgba(0,0,0,.6);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);
  transition:background .18s}
.play svg{position:relative;width:18px;height:18px;fill:var(--signal);transition:fill .18s}
.hit:hover .play span::before,.hit:focus-visible .play span::before{background:var(--signal)}
.hit:hover .play svg,.hit:focus-visible .play svg{fill:#fff}
.cap{display:flex;flex-wrap:wrap;gap:var(--sp-3);justify-content:space-between;
  padding-top:var(--sp-3);color:var(--text-muted)}
.cap strong{color:var(--text);font:700 var(--fs-sm)/1.3 var(--f-body)}
/* the credit takes its own line: it is a third value in a row built for two,
   and squeezing it beside the format label makes both unreadable */
.cap .credit{flex:1 0 100%;font:400 var(--fs-micro)/1.5 var(--f-mono);
  letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint)}

/* ---------- contact sheet ---------- */
.sheet{display:grid;gap:var(--sp-4);background:var(--surface);border:1px solid var(--hairline);
  padding:var(--sp-4);grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))}
.sheet--few{grid-template-columns:repeat(auto-fill,minmax(min(100%,340px),1fr))}
.cell .frame{aspect-ratio:4/3;border-radius:10px}
.sprocket{display:flex;justify-content:space-between;padding:6px 4px}
.sprocket i{width:9px;height:6px;background:var(--bg);border:1px solid var(--hairline)}
.cell__no{display:flex;justify-content:space-between;gap:var(--sp-2);padding:var(--sp-2) 0;
  color:var(--text-muted);font:500 var(--fs-micro)/1.4 var(--f-mono);letter-spacing:.14em;text-transform:uppercase}
.cell__no b{color:var(--accent);font-weight:500}
.cell h3{font:700 var(--fs-sm)/1.25 var(--f-body);padding-bottom:var(--sp-2)}
.filters{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-bottom:var(--sp-6)}
.filters input{position:absolute;opacity:0;width:0;height:0}
.filters label{font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.14em;text-transform:uppercase;
  min-height:44px;display:inline-flex;align-items:center;padding:0 var(--sp-4);
  border:1px solid var(--border);color:var(--text-muted);cursor:pointer;transition:all .18s}
.filters label:hover{border-color:var(--accent);color:var(--text)}
.filters input:focus-visible+label{outline:2px solid var(--focus);outline-offset:2px}
#f-all:checked~.filters [for=f-all],#f-food:checked~.filters [for=f-food],
#f-fit:checked~.filters [for=f-fit],#f-shop:checked~.filters [for=f-shop],
#f-stay:checked~.filters [for=f-stay]{background:var(--text);color:var(--bg);border-color:var(--text)}
#f-food:checked~.sheet .cell:not([data-c~=food]),#f-fit:checked~.sheet .cell:not([data-c~=fit]),
#f-shop:checked~.sheet .cell:not([data-c~=shop]),#f-stay:checked~.sheet .cell:not([data-c~=stay]){display:none}
.note{margin-top:var(--sp-6);font-size:var(--fs-sm);color:var(--text-muted);
  border-left:2px solid var(--signal);padding-left:var(--sp-4);max-width:72ch}
.note strong{color:var(--text)}

/* The services section used to sit on a lifted panel with a border above and
   below -- the only section on the site that did. It read as a box dropped
   onto the page rather than part of it, and it was the one treatment that
   neither blended nor committed. It is on the same black as everything else
   now; the K-X-1 rule and the column hairlines mark where it starts. */
.cols{display:grid;gap:var(--sp-6);grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
.col{border-top:2px solid var(--accent-quiet);padding-top:var(--sp-4)}
.col span{color:var(--accent);font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.16em}
.col h3{font:var(--w-h2) var(--fs-h3)/1.15 var(--f-display);text-transform:uppercase;
  letter-spacing:-.01em;margin-block:var(--sp-3) var(--sp-2)}
.col p{font-size:var(--fs-sm);color:var(--text-muted)}
.col ul{list-style:none;margin-top:var(--sp-4);display:grid;gap:var(--sp-2)}
.col li{font-size:var(--fs-sm);color:var(--text-muted);padding-left:var(--sp-4);position:relative}
.col li::before{content:"";position:absolute;left:0;top:.68em;width:8px;height:1px;background:var(--signal)}

/* ---------- photo essay ---------- */
.essay{display:grid;gap:var(--sp-6);grid-template-columns:1fr}
@media(min-width:860px){.essay{grid-template-columns:repeat(6,1fr)}
  /* Two wide, then three. The pattern repeats every five, so any number of
     photos reads as designed, not as a designed five and a row of thumbnails. */
  .essay:not(.essay--collage):not(.serve):not([data-kx-gallery=about]) figure:nth-child(5n+1){grid-column:1/4}
  .essay:not(.essay--collage):not(.serve):not([data-kx-gallery=about]) figure:nth-child(5n+2){grid-column:4/7}
  .essay:not(.essay--collage):not(.serve):not([data-kx-gallery=about]) figure:nth-child(5n+3){grid-column:1/3}
  .essay:not(.essay--collage):not(.serve):not([data-kx-gallery=about]) figure:nth-child(5n+4){grid-column:3/5}
  .essay:not(.essay--collage):not(.serve):not([data-kx-gallery=about]) figure:nth-child(5n+5){grid-column:5/7}}
.essay:not([data-kx-gallery=about]) .frame{aspect-ratio:4/5}
/* Collage: one wide frame across the top, two square ones beneath. The ratios
   are picked so the three together read as one square block -- 2:1 over two
   1:1 halves -- rather than three portraits in a row that happen to wrap. */
.essay--collage{grid-template-columns:1fr 1fr;gap:var(--sp-4)}
.essay--collage figure:nth-child(1){grid-column:1/-1}
.essay--collage figure:nth-child(1) .frame{aspect-ratio:2/1}
.essay--collage figure:nth-child(2) .frame,
.essay--collage figure:nth-child(3) .frame{aspect-ratio:1/1}
/* M8. On a phone the block stops being a composition and becomes a column.
   2:1 at 90vw is a letterbox about a finger tall, so the pair goes to 4:3;
   the two singles take the whole width one at a time instead of sharing it,
   which is the difference between seeing a face and seeing that there is a
   face. */
@media(max-width:720px){
  .essay--collage{grid-template-columns:1fr}
  .essay--collage figure:nth-child(1) .frame{aspect-ratio:4/3}
  .essay--collage figure:nth-child(2) .frame,
  .essay--collage figure:nth-child(3) .frame{aspect-ratio:1/1}
}
@media(min-width:860px){
  .essay--collage{grid-template-columns:1fr 1fr}
  .essay--collage figure:nth-child(1){grid-column:1/-1}
  .essay--collage figure:nth-child(2){grid-column:1/2}
  .essay--collage figure:nth-child(3){grid-column:2/3}
}
/* Who we serve. Recognition, not portfolio: a labelled grid of the kinds of
   room this studio shoots. It sits after the .essay rules on purpose -- the
   editorial five-up pattern and this one weigh the same, so order decides. */
.essay.serve{gap:var(--sp-4);grid-template-columns:repeat(2,minmax(0,1fr))}
/* The grid ends on a caption, which already carries space beneath it; a full
   section's padding on top of that read as a hole before the divider. */
section:has(> .essay.serve){padding-bottom:clamp(1rem,2vw,var(--sp-6))}
/* Same reasoning for the list of services: it ends on a hairline, and a full
   section's padding under a rule reads as a hole rather than as a break. */
section:has(> .svc){padding-bottom:clamp(1rem,2vw,var(--sp-6))}
.serve figure{margin:0}
/* The five-up editorial rhythm assumes five. The About collage is whatever is
   left showing that week, so it says its own shape instead.

   Two portraits open it at half the width each, and everything after them is
   a square at that same half width -- six photographs in three rows of two.
   They were quarters, and at a quarter the joke in each picture only landed
   once the gallery was open, which is not where anyone reads the page.

   The rule this replaces squared by position in a repeating three -- which
   squared the two big ones and left two of the four singles two-to-one wide,
   the opposite of what was asked for and what shipped marked as done.
   Counting from the front instead of in threes means the two openers are the
   only special case, however many photos follow them. */
@media(min-width:860px){
  /* Every selector here carries a :nth-child so it weighs the same as the
     five-up rules above and, coming later, wins. Without it three of the four
     squares kept the editorial rhythm's span and came out two-thirds the size
     of the fourth -- square, but not the same square. */
  [data-kx-gallery="about"]{grid-template-columns:repeat(12,minmax(0,1fr))}
  [data-kx-gallery="about"] figure:nth-child(n){grid-column:span 6}
  [data-kx-gallery="about"] figure:nth-child(-n+2){grid-column:span 6}
  [data-kx-gallery="about"] figure:nth-child(n) .frame{aspect-ratio:1/1}
  [data-kx-gallery="about"] figure:nth-child(-n+2) .frame{aspect-ratio:4/5}
}
/* M12. Nine rooms is a long scroll on a phone for a section whose job is
   recognition -- six is enough to find yourself in. Hidden rather than
   removed: the page is one document, and the other three are still there for
   anything reading it rather than looking at it. */
@media(max-width:720px){.serve figure:nth-child(n+7){display:none}}
.serve .frame{aspect-ratio:4/3;border-radius:10px}
.serve .hit{display:block;width:100%;text-align:left}
/* These are rooms someone should recognise as their own, so they arrive in
   colour. The zoom on hover still marks them as things you can open. */
.serve .frame img{filter:contrast(1.02)}
.serve .hit::after{content:attr(data-title);display:block;
  padding-top:var(--sp-3);color:var(--text-muted);
  font:500 var(--fs-micro)/1.4 var(--f-mono);letter-spacing:.14em;text-transform:uppercase}
.serve .hit:hover::after,.serve .hit:focus-visible::after{color:var(--text)}
@media(min-width:860px){
  .essay.serve{grid-template-columns:repeat(3,minmax(0,1fr))}
}

.essay figcaption{font:400 clamp(.95rem,.9rem + .3vw,1.1rem)/1.5 var(--f-body);color:var(--text-muted);
  padding-top:var(--sp-2);border-top:1px solid var(--hairline);margin-top:var(--sp-2)}

/* ---------- studio ---------- */
.two{display:grid;gap:var(--sp-12);grid-template-columns:1fr}
@media(min-width:900px){.two{grid-template-columns:1fr 1fr;align-items:center}
  /* With the credits gone there is nothing to sit beside, and half a row of
     empty space next to the picture is not a layout. On its own it takes the
     width. */
  .two:has(> figure:only-child){grid-template-columns:1fr}}
/* The studio's own opening picture: wider than a film frame, and the width
   of the column rather than half of it. */
/* M14. It was cropped to a 21:9 letterbox, which threw away a third of a 3:2
   photograph and left the opening picture the smallest thing on the page. At
   the file's own 3:2 nothing is cropped and it opens at the size it deserves.

   Stated here rather than left to the image, because every frame on this site
   reserves its box in CSS and none of the markup carries width and height.
   aspect-ratio:auto collapses the frame to nothing until a lazy image
   arrives, and then jumps the page down by its full height -- which is what
   the preview showed: 335x0 with the photograph not yet loaded. If this
   picture is ever replaced by one of another shape, this number moves with
   it. */
.two .frame{aspect-ratio:3/2}
/* The studio's own portrait, not a piece of work: in colour from the start,
   rather than grey until a pointer happens to cross it. */
.two .frame img{filter:contrast(1.02)}
blockquote{font:400 clamp(1.15rem,1rem + 1vw,1.6rem)/1.4 var(--f-body);
  border-left:3px solid var(--signal);padding-left:var(--sp-6);margin-top:var(--sp-8)}

/* ---------- faq ---------- */
.faq{border-top:1px solid var(--border)}
details{border-bottom:1px solid var(--border)}
summary{cursor:pointer;list-style:none;padding-block:var(--sp-4);display:flex;gap:var(--sp-4);
  align-items:baseline;font:700 var(--fs-h3)/1.3 var(--f-body);font-size:calc(var(--fs-h3) + 4px);min-height:48px}
summary::-webkit-details-marker{display:none}
summary::after{content:"+";margin-left:auto;color:var(--signal);font:400 1.6rem/1 var(--f-body);
  transition:transform .3s var(--ease)}
details[open] summary::after{transform:rotate(45deg)}
summary:hover{color:var(--accent)}
details p{padding-bottom:var(--sp-4);color:var(--text-muted);font-size:var(--fs-sm);max-width:none}

/* ---------- forms ---------- */
form{display:grid;gap:var(--sp-4)}
label.fl{display:block;font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.16em;
  text-transform:uppercase;color:var(--text-muted);margin-bottom:var(--sp-2)}
/* A line, not a box. The field is defined by the rule under it and by its
   own word sitting on that rule; the ground stays the page's. 16px because
   anything smaller makes iOS zoom the whole page on focus. */
input{width:100%;background:none;border:0;border-bottom:1px solid var(--border);
  color:var(--text);font:400 16px/1.5 var(--f-body);
  padding:var(--sp-3) 0;min-height:48px;border-radius:0;transition:border-color .18s}
input:hover{border-bottom-color:var(--text-muted)}
/* The label is the placeholder now, so it has to read as a label: the same
   mono caps the printed ones used, at the size the box can hold. */
input::placeholder{color:var(--text-muted);font:500 var(--fs-micro)/1 var(--f-mono);
  letter-spacing:.14em;text-transform:uppercase;opacity:1}
/* The outline was drawn on a box that no longer exists; the line itself is
   the focus indicator, thickened and brought to full white. */
input:focus{outline:0;border-bottom-color:var(--text);box-shadow:0 1px 0 0 var(--text)}
input:focus-visible{outline:2px solid var(--focus);outline-offset:4px}
.hp{position:absolute!important;left:-9999px!important;width:1px!important;height:1px!important;
  overflow:hidden!important;opacity:0!important}
.micro{font-size:var(--fs-sm);color:var(--text-muted)}
.micro a{color:var(--accent);border-bottom:1px solid var(--accent-quiet)}
.formmsg{display:none;border:1px solid var(--signal);padding:var(--sp-4);font-size:var(--fs-sm)}
.formmsg.on{display:block}
.contact{display:grid;gap:var(--sp-12);grid-template-columns:1fr;
  border-top:2px solid var(--border);padding-top:var(--sp-8)}
@media(min-width:900px){.contact{grid-template-columns:1fr 1fr}}
.contact h2{font:800 clamp(1.8rem,1rem + 4vw,3.6rem)/.98 var(--f-display);font-stretch:110%;
  text-transform:uppercase;letter-spacing:-.03em}

/* ---------- ticker ---------- */
.ticker{overflow:hidden;padding-block:var(--sp-4)}
.ticker__t{display:flex;gap:var(--sp-8);width:max-content;
  --half-gap:calc(var(--sp-8) / 2);animation:slide 40s linear infinite}
.ticker span{white-space:nowrap;color:var(--text);font-weight:600}
.ticker span::after{content:"\2014";margin-left:var(--sp-8);color:var(--text-muted);opacity:.55}
/* One full set along -- which is half the track PLUS half a gap.

   It was -50% alone. The gaps sit BETWEEN items, so the run from an item to
   its duplicate crosses n of them while the whole track holds only 2n-1:
   half the track lands half a gap short of the seam, and the animation snaps
   back over that distance once per cycle, forever. 16px on these bands and
   8px on the film strip -- the recurring hitch that survived giving every
   tile a clip cut to its own window, because it was never about the video.

   Kept as a percentage rather than a measured pixel figure on purpose: the
   track's width moves with the viewport (the tiles are sized in vh), and a
   percentage re-resolves itself where a number measured once goes stale. */
@keyframes slide{from{transform:translateX(0)}
  to{transform:translateX(calc(-50% - var(--half-gap, 0px)))}}

/* ---------- footer ---------- */
/* The last screen is revealed, not scrolled to.
 *
 * The contact block and the footer sit in a layer stuck to the bottom of the
 * viewport; the page above them carries its own opaque ground and rides up
 * over it. What you see is the form appearing from underneath the page rather
 * than arriving after it. The layer is only as tall as it needs to be, and the
 * page's last section still ends where it ends -- nothing overlaps, because
 * the spacer below reserves exactly the layer's height.
 *
 * z-index rather than source order: the nav is 60 and has to stay above both. */
.reveal{position:sticky;bottom:0;z-index:0}
/* M10. The reveal works by pinning this layer's BOTTOM to the bottom of the
   window and sliding the page off it. That only shows the whole layer while
   the layer fits: below 900px the contact block stacks into one column and
   the pair comes to 1275px in an 812px window, so pinning the bottom put the
   form 463px above the top of the screen and the footer was all anyone ever
   saw. Measured on a 375x812 viewport, which is where Victor found it.

   So on a phone it stops being pinned and simply follows the page. The
   transition is the same one -- the page travels up and this arrives -- and
   what arrives is the form first and the footer under it, in that order. */
@media(max-width:899px){.reveal{position:static}}
main{position:relative;z-index:1;background:var(--bg)}
footer{border-top:1px solid var(--border);padding-block:var(--sp-12);
  padding-bottom:max(var(--sp-12),env(safe-area-inset-bottom))}
/* Four columns: who we are, where to go, where else we are, how to reach us.
   One column used to carry the last three at once, stacked with <br>, which is
   what made the bottom right read as a dense block rather than as a footer. */
.fgrid{display:grid;gap:var(--sp-8);grid-template-columns:1fr}
@media(min-width:720px){
  .fgrid{grid-template-columns:minmax(0,1.5fr) auto auto auto;
    column-gap:clamp(1.75rem,4.5vw,4rem);align-items:start}
}
.fcontact{display:grid;gap:var(--sp-2);align-content:start}
.fcontact a,.fcopy{font:500 var(--fs-micro)/1.8 var(--f-mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-muted)}
.fcontact a:hover{color:var(--accent)}
/* The year is not a link and should not sit in the same rhythm as things that
   are -- it gets its own space and drops back a step in the hierarchy. */
.fcopy{margin-top:var(--sp-4);color:var(--text-faint)}
footer p{font-size:var(--fs-sm);color:var(--text-muted);max-width:52ch}
footer nav{display:grid;gap:var(--sp-2)}
footer nav a{font:500 var(--fs-micro)/1.8 var(--f-mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-muted)}
footer nav a:hover{color:var(--accent)}

/* ---------- menu panel (slides in from the right) ---------- */
.burger{display:inline-flex;align-items:center;gap:.5rem;min-height:44px;padding:0 var(--sp-3);
  background:none;border:1px solid var(--border);cursor:pointer;color:var(--text);
  font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.14em;text-transform:uppercase}
.burger:hover{border-color:var(--accent);color:var(--accent)}
/* One menu, two forms. Above 1100px the stacked list is the menu, so the MENU
   button is the same thing offered twice; below it the list is hidden and the
   button is the only way in. They are never both present. */
#burger{display:none}
@media(max-width:1100px){
  #burger{display:inline-flex}
  /* M2. The bar holds the wordmark and the way in, and nothing else. The
     call to action moves into the menu, where it has room to be a button
     rather than a 10px outline. */
  .navcta{display:none}
  /* M4. The menu button IS the close button: the bar is lifted over the
     panel so the control keeps its position and its design, and only the
     word on it changes. A second button somewhere else would have been a
     second thing to find. */
  .nav{z-index:80}
}
.menu{position:fixed;inset:0;z-index:70;pointer-events:none}
.menu[hidden]{display:none}
.menu__scrim{position:absolute;inset:0;background:rgba(6,8,12,.6);opacity:0;
  transition:opacity .32s var(--ease);pointer-events:auto}
.menu__panel{position:absolute;top:0;right:0;bottom:0;width:min(420px,86vw);
  background:var(--bg);border-left:1px solid var(--border);pointer-events:auto;
  display:flex;flex-direction:column;gap:var(--sp-8);
  padding:max(var(--sp-24),env(safe-area-inset-top)) clamp(1.5rem,4vw,3rem)
          max(var(--sp-8),env(safe-area-inset-bottom));
  transform:translateX(100%);transition:transform .36s var(--ease)}
.menu.is-open .menu__scrim{opacity:1}
.menu.is-open .menu__panel{transform:none}
.menu__links{display:grid;gap:var(--sp-2)}
.menu__links a{position:relative;padding-left:34px;transform-origin:left top;will-change:transform;
  font:400 clamp(1.35rem,4.2vw,2rem)/1.35 var(--f-mono);
  text-transform:uppercase;letter-spacing:.04em;color:var(--text);
  transition:font-weight .18s,color .18s}
.menu__links a::before{content:"";position:absolute;left:0;top:.62em;width:22px;height:2px;
  background:var(--signal);transform:scaleX(0);transform-origin:left center;
  transition:transform .24s var(--ease)}
.menu__links a:hover,.menu__links a:focus-visible{color:var(--accent);font-weight:700}
.menu__links a:hover::before,.menu__links a[aria-current=page]::before{transform:scaleX(1)}
.menu__foot{display:grid;gap:var(--sp-3);border-top:1px solid var(--border);
  padding-top:var(--sp-6);margin-top:auto}
.menu__foot a,.menu__foot span{font:500 var(--fs-micro)/1.8 var(--f-mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-muted)}
.menu__foot a:hover{color:var(--accent)}
/* The button belongs to the small-screen menu; above that the bar has it. */
.menu__cta{display:none}
/* M3 and M5. On a phone the drawer is the screen, so it stops behaving like a
   drawer: full width, the three places centred and set large, the call to
   action alone at the foot, and the same glass the Let's talk overlay uses so
   the page stays visible behind it rather than being replaced by a panel.
   Contact leaves the list -- it and the button underneath go to the same
   place, and there is no room here to make that distinction earn itself.

   Written AFTER the rules it overrides, not before them. Every selector here
   weighs exactly what the base one does, so source order is the whole
   argument -- placed above, the size and the weight and the centring were all
   simply ignored, which is what the preview showed. */
@media(max-width:1100px){
  .menu__panel{width:100%;border-left:0;
    background:rgba(0,0,0,.82);
    -webkit-backdrop-filter:blur(22px) saturate(115%);
    backdrop-filter:blur(22px) saturate(115%);
    justify-content:center;gap:var(--sp-12)}
  .menu__links{justify-items:center;gap:var(--sp-6)}
  .menu__links a{padding-left:0;text-align:center;
    font:700 clamp(2rem,9vw,3.25rem)/1.1 var(--f-mono);letter-spacing:-.01em}
  /* The dash marks the link on the left; centred there is no left to mark. */
  .menu__links a::before{display:none}
  .menu__links a[data-talk]{display:none}
  .menu__cta{display:inline-flex;align-self:center;
    width:min(320px,80%);margin-inline:auto}
  .menu__foot{margin-top:var(--sp-8);justify-items:center;text-align:center}
}

@media(prefers-reduced-motion:reduce){
  .menu__panel{transition:none}.menu__scrim{transition:none}
}

/* ---------- lightbox ----------
   Full screen, on the site's own black, one picture at a time.
   The picture is fitted to the window and the chrome sits over it: where you
   are in the set, bottom left; the arrows on the margins; the way out top
   right. Built to the reference the client asked for, down to the "1 - 4"
   counter, and the ends wrap so the set never dead-ends. */
.lb{position:fixed;inset:0;z-index:80;background:var(--bg)}
.lb[hidden]{display:none}
/* The figure IS the ground: it fills the window, so a click anywhere off the
   picture lands here and closes. object-fit does the framing -- a portrait
   fills the height edge to edge, a wide frame fills the width, neither is
   ever cropped. */
.lb__fig{position:absolute;inset:0;margin:0;display:flex;align-items:center;
  justify-content:center;cursor:zoom-out}
/* Sized to the picture, not to the window. object-fit would draw the same
   frame but leave the element covering the whole ground, and then a click on
   the black lands on the photo instead of closing -- which is what it did. */
.lb__img{display:block;width:auto;height:auto;max-width:100vw;max-height:100svh;
  cursor:pointer;opacity:0;transition:opacity .28s ease}
.lb__img.in{opacity:1}
video.lb__img{width:min(100vw,calc(100svh*16/9));height:auto;cursor:default}
.lb__meta{position:absolute;left:0;right:0;bottom:0;z-index:1;pointer-events:none;
  display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem 1.25rem;
  padding:clamp(14px,2.4vw,22px);
  padding-bottom:max(clamp(14px,2.4vw,22px),env(safe-area-inset-bottom));
  background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.62))}
/* Full white, not the muted grey the other small type uses: where you are in
   the set is the one thing this bar exists to say, and at micro size on a
   gradient the muted grey disappeared. */
.lb__count{font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.14em;
  color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}
.lb__title{font:700 var(--fs-sm)/1.2 var(--f-display);text-transform:uppercase;color:var(--text)}
.lb__note{font:400 var(--fs-micro)/1.5 var(--f-body);color:var(--text-muted);
  max-width:46ch}
.lb__tags{display:flex;gap:.4rem;flex-wrap:wrap;margin-left:auto}
.lb__tag{font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-muted);border:1px solid var(--border);padding:5px 8px}
.lb__nav{position:absolute;top:50%;transform:translateY(-50%);z-index:2;width:56px;height:56px;
  display:flex;align-items:center;justify-content:center;padding:0;
  background:none;border:0;color:var(--text);cursor:pointer}
.lb__prev{left:max(4px,env(safe-area-inset-left))}
.lb__next{right:max(4px,env(safe-area-inset-right))}
/* The arrows sit over the picture on a narrow screen, where a pale frame would
   swallow a plain white chevron. The shadow is what keeps them findable
   without putting a box around them. */
.lb__nav i{width:15px;height:15px;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;opacity:.62;transition:opacity .2s ease;
  filter:drop-shadow(0 0 3px rgba(0,0,0,.75))}
.lb__prev i{transform:rotate(135deg);margin-left:5px}
.lb__next i{transform:rotate(-45deg);margin-right:5px}
.lb__nav:hover i,.lb__nav:focus-visible i{opacity:1}
/* The two ways out -- a photo's and a film's -- are one rule, so they cannot
   drift apart again. No box and no disc: the mark is the control, and the
   shadow is what keeps it findable over a pale frame. */
.lb__close,.player__close,.talk__close{width:44px;height:44px;display:grid;place-items:center;
  cursor:pointer;background:none;border:0;border-radius:0;padding:0;
  color:var(--text);font:300 1.75rem/1 system-ui,sans-serif;opacity:.72;
  filter:drop-shadow(0 0 4px rgba(0,0,0,.8));transition:opacity .18s,color .18s}
.lb__close:hover,.lb__close:focus-visible,
.player__close:hover,.player__close:focus-visible,
.talk__close:hover,.talk__close:focus-visible{opacity:1;color:var(--signal)}
.lb__close{position:absolute;z-index:2;top:max(10px,env(safe-area-inset-top));
  right:max(10px,env(safe-area-inset-right))}
/* The global reduce block only shortens durations; the fade between pictures
   is the one thing here that should not happen at all. */
@media(prefers-reduced-motion:reduce){.lb__img{transition:none}}

/* ---------- let's talk overlay ---------- */
dialog.talk{width:100vw;max-width:100vw;height:100svh;max-height:100svh;padding:0;border:0;
  background:rgba(0,0,0,.82);color:var(--text);overscroll-behavior:contain;
  -webkit-backdrop-filter:blur(22px) saturate(115%);backdrop-filter:blur(22px) saturate(115%)}
/* Both layers are translucent so the page stays visible behind the form -- the
   dialog reads as something laid over the site rather than a second page. The
   blur is what keeps the type legible over whatever film happens to be under
   it; without it, moving footage behind the fields is unreadable. */
dialog.talk::backdrop{background:rgba(0,0,0,.55);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
dialog.talk .inner{height:100%;overflow:auto;display:flex;flex-direction:column;justify-content:center;
  /* a 4:3 column, matching the film frames, so the form stops sprawling the
     full width of a wide screen and reads as one block */
  width:100%;max-width:min(var(--wrap),calc((100svh - 6rem) * 4 / 3));margin-inline:auto;
  padding:max(var(--sp-16),env(safe-area-inset-top)) clamp(1.25rem,4vw,2.5rem)
          max(var(--sp-12),env(safe-area-inset-bottom))}
dialog.talk h2{font:800 clamp(1.6rem,1rem + 3.6vw,3.4rem)/1.02 var(--f-display);font-stretch:110%;
  letter-spacing:-.03em;text-transform:uppercase;max-width:25ch;margin-block:var(--sp-4) var(--sp-8)}
dialog.talk .lede{display:none}
/* Aligned to the form's own column rather than the window. The dialog centres
   a 4:3 column, so a close pinned to the viewport edge floats far out in empty
   space on a wide screen instead of belonging to the thing it closes. Same
   expression as .inner's max-width, halved. */
.talk__close{position:fixed;top:var(--sp-4);z-index:2;
  right:max(clamp(1.25rem,4vw,2.5rem),
            calc(50% - min(var(--wrap),(100svh - 6rem) * 4 / 3) / 2));
  width:44px;height:44px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;color:var(--text-muted);cursor:pointer;
  font:400 1.6rem/1 var(--f-body);letter-spacing:0;transition:color .18s}
.talk__close:hover{color:var(--signal)}
.talk__rule{display:flex;align-items:center;gap:var(--sp-4);color:var(--text-faint);
  font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.2em;text-transform:uppercase;
  margin-bottom:var(--sp-8)}
.talk__rule::before,.talk__rule::after{content:"";flex:1;height:1px;background:var(--border)}
/* underline fields rather than boxes, per the reference */
dialog.talk form{gap:var(--sp-6)}
dialog.talk .row{display:grid;gap:var(--sp-6)}
@media(min-width:640px){dialog.talk .row{grid-template-columns:1fr 1fr}}
dialog.talk input{background:none;border:0;border-bottom:1px solid var(--border);padding:var(--sp-3) 0;
  font:400 16px/1.5 var(--f-body);color:var(--text)}
dialog.talk input:focus{outline:0;border-bottom-color:var(--accent)}
dialog.talk label.fl{margin-bottom:0}
dialog.talk .btn{justify-self:stretch;width:100%}
/* right-justified only where there is room to be right of something;
   on one column a 280px button floated right just reads as misaligned */
@media(max-width:639px){dialog.talk .btn{justify-self:stretch;width:100%}}

/* ---------- reveal ---------- */
.js .rv{opacity:0;transition:opacity .45s var(--ease)}
.js .rv.in{opacity:1}
/* The two statements take their time. Everything else on the page is a section
   arriving; these are the sentences the page is built around, and at .45s they
   were present before they were read. */
.js .hero h1.rv,.js .trail__msg.rv{transition-duration:1.15s}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important}
  .ticker__t{animation:none}
  .js .rv{opacity:1}
}

/* ---------- pointer trail ----------
   Moving the pointer across this band leaves frames behind it. Desktop
   pointers only: on touch there is no pointer path to trail, and the spawned
   nodes would just be litter. Transform and opacity only, so it composites
   without touching layout. */
/* The trail reveals photographs as the pointer crosses it, but nothing said
   so -- the cursor is the only affordance a section like this can carry.
   Gated to exactly what the script binds on, so it never promises an effect
   that is not running. `default` and not `pointer` as the fallback: it
   responds, but there is nothing here to click. */
@media(hover:hover) and (pointer:fine){
  .trail{cursor:var(--cursor-arrow) 21 15, default}
}
@media(prefers-reduced-motion:reduce){.trail{cursor:auto}}
/* No background of its own. It used to paint the same black main already
   paints -- harmless while the grain sat on top of everything, and a
   1160x702 hole in it the moment the grain moved underneath. This was the
   only element on the site doing it, and it is the one Victor could see. */
.trail{position:relative;overflow:hidden;
  min-height:min(78svh,720px);display:grid;place-items:center;isolation:isolate}
.trail__msg{position:relative;z-index:2;text-align:center;pointer-events:none;
  padding-inline:clamp(1.25rem,4vw,2.5rem)}
.trail__msg h2{font:var(--w-display) var(--fs-statement)/.98 var(--f-display);
  font-stretch:112%;letter-spacing:-.03em;text-transform:uppercase;max-width:16ch;margin-inline:auto}
.trail__msg p{margin-top:var(--sp-6);color:var(--text-muted)}
.trail__img{position:absolute;z-index:1;width:clamp(140px,13vw,220px);aspect-ratio:3/4;
  object-fit:cover;translate:-50% -50%;pointer-events:none;
  filter:contrast(1.04);opacity:0;background:var(--bg-recessed)}
/* M6. Sized by its padding it came out roughly square, which on a tall screen
   reads as a band rather than as a place. 4:5 gives the frames somewhere to
   travel and the sentence somewhere to sit. */
@media(hover:none),(pointer:coarse){
  .trail{min-height:auto;aspect-ratio:4/5;padding-block:clamp(2rem,6vw,var(--sp-12))}
}
@media(prefers-reduced-motion:reduce){.trail__img{display:none}}

/* ---------- media strip ----------
   A continuously travelling filmstrip. The track holds the tiles twice so the
   -50% translate lands exactly on the duplicate and the loop is seamless. */
.strip{position:relative;overflow:hidden;padding-block:var(--sp-4);
  -webkit-mask-image:var(--edge-mask);
  mask-image:var(--edge-mask)}
.strip__track{display:flex;gap:var(--sp-4);width:max-content;
  --half-gap:calc(var(--sp-4) / 2);
  animation:strip 68s linear infinite;will-change:transform}
/* The frames and the two edge bands are one piece of film, so they stop
   together -- stopping a frame while its bands kept running read as two
   unrelated marquees that happened to be stacked. Their speeds are matched in
   JS: the tracks are different widths, so a shared duration is different
   px/sec, which is the thing the eye actually reads as out of sync. */
/* Ordering against its neighbours: the strip's edge bands overlap the
   sections either side of it. Keeping the strip clear of the grain used to be
   this rule's job as well, and it is the grain's own now -- see .grain. */
.filmstrip{position:relative;z-index:41}
/* Hover no longer stops the film -- it names the piece, on the left, opposite
   the menu. Keyboard focus still pauses: a keyboard user cannot hold a moving
   target still any other way, and reading the title is the whole point. */
.filmstrip:focus-within .strip__track,
.filmstrip:focus-within .ticker__t{animation-play-state:paused}
.ticker--edge{-webkit-mask-image:var(--edge-mask);mask-image:var(--edge-mask)}
/* Same correction as @keyframes slide, and the same reason -- see there. */
@keyframes strip{from{transform:translateX(0)}
  to{transform:translateX(calc(-50% - var(--half-gap, 0px)))}}
.strip__tile{--h:clamp(260px,54vh,560px);flex:0 0 auto;position:relative;z-index:0;
  height:var(--h);width:calc(var(--h)*4/3);background:none;border:0;padding:0;display:block;
  cursor:var(--cursor-arrow) 21 15, pointer}
.strip__tile .frame{position:absolute;top:0;left:0;height:100%;width:100%;aspect-ratio:auto;
  border-radius:10px;overflow:hidden;transition:width .45s var(--ease),left .45s var(--ease)}
/* the source frames are 16:9; 4:3 is a centre crop, so hover gives the shot
   back rather than zooming into it */
.strip__tile:hover,.strip__tile:focus-visible{z-index:2}
.strip__tile video{width:100%;height:100%;object-fit:cover;display:block;filter:none}
.strip__tile .frame::after{border-radius:10px}
@media(prefers-reduced-motion:reduce){.strip__tile .frame{transition:none}}
@media(max-width:700px){.strip__tile{--h:clamp(180px,30vh,270px)}}
.strip__cap{text-align:center;padding-top:var(--sp-6);min-height:3.2em;
  transition:opacity .5s ease}
.strip__cap.is-swapping{opacity:0}
@media(prefers-reduced-motion:reduce){.strip__cap{transition:none}}
.strip__cap strong{display:block;font:700 var(--fs-sm)/1.4 var(--f-body);
  letter-spacing:.02em;text-transform:uppercase}
.strip__cap span{display:block;font:500 var(--fs-micro)/1.6 var(--f-mono);letter-spacing:.16em;
  text-transform:uppercase;color:var(--text-muted)}
@media(prefers-reduced-motion:reduce){
  .strip{overflow-x:auto}
  .strip__track{animation:none}
}

/* ---------- section divider ---------- */
/* The monogram taken apart and laid along the rule: K, X and 1 mark the thirds
   instead of a plain hairline. Decorative only -- aria-hidden, because the
   heading directly below already names the section, and a screen reader
   announcing "K X 1" before every one of them is noise, not branding. */
.kxrule{display:flex;align-items:center;gap:clamp(.7rem,2vw,1.5rem);width:100%;
  margin-bottom:var(--sp-6)}
.kxrule b{flex:0 0 auto;font:800 .8125rem/1 var(--f-display);font-stretch:112%;
  letter-spacing:.02em;color:var(--text-faint);user-select:none}
.kxrule i{flex:1 1 0;height:1px;background:var(--hairline)}
/* A divider with no section under it -- it separates the hero from the trail,
   which is full-bleed and cannot host one. Its own margin is the section's
   top spacing, so it does not need the rule's usual bottom gap. */
.rulewrap{padding-block:clamp(2rem,5vw,var(--sp-16)) 0}
.rulewrap .kxrule{margin-bottom:0}
/* The divider separates sections, and a section is the whole page -- so the
   rule is page-width, not column-width, and has to break out of .wrap. Body
   clips horizontal overflow, so the vw math here cannot raise a scrollbar.
   It stops short of the edge rather than running flush: above 1101px the
   fixed nav rail owns the right margin, and a rule running under it reads as
   a collision. */
@media(min-width:900px){
  /* vw counts the scrollbar but the containing block does not, so the naive
     50% - 50vw lands half a scrollbar too far out on each side. --sbw carries
     the measured difference; without JS it falls back to 0 and the rule is
     simply 7px wider, which is harmless. */
  /* width:auto is load-bearing. The base rule sets width:100%, and a negative
     margin cannot widen an element whose width is fixed -- it only shifts it.
     With the explicit width still in place this rule moved left and right in
     equal measure and never got any wider. */
  .kxrule{width:auto;margin-inline:calc(50% - 50vw + var(--sbw,0px) / 2 + clamp(1.5rem,4vw,4rem))}
}
@media(min-width:1101px){
  /* The fixed nav rail owns the right margin above this width, and it carries
     a radial veil wider than itself. The rule stops clear of both -- running
     a hairline under the menu reads as a mistake, not as a full-bleed. */
  /* Both sides, not only the right. The right margin makes room for the side
     nav; leaving the left at the narrow value put the X ~60px left of centre
     on a wide screen, visibly off against a centred heading. Symmetric margins
     centre the X and take about 7% off the rule width. */
  .kxrule{margin-inline:calc(50% - 50vw + var(--sbw,0px) / 2 + clamp(8rem,10vw,14rem))}
}
/* Names and one line each. The three bullets per service now only exist on
   /services/, so the homepage needs a way through to them. */
.cols--brief .col p{margin-top:var(--sp-2)}

/* ---------- film grain ---------- */
/* Jittered in discrete steps, the way a projector gate moves the frame --
   interpolated motion reads as video static, which is the opposite effect.
   The tile itself is generated at load; see kx1.js. */
/* It lives inside <main> at a negative z-index, which is what makes it a
   ground rather than a veil: a stacking context paints its own background
   first, then its negative-z children, then everything else it contains. So
   the grain lands on main's black, and every photograph, every frame of video
   and every line of type sits on top of it -- structurally, rather than by a
   list of exceptions the next gallery would fall out of.

   It was z-index:40 over the whole document, which put it over the lot. The
   rule further down that claimed to lift the strip clear of it stopped working
   the day main became a stacking context of its own at z-index:1, and nothing
   showed regardless because .055 over black is not something you can see. */
/* Fixed rather than absolute, so it sits still while the page moves past it,
   the way a gate does. The cost is that it reaches past main and lies over the
   contact layer revealed underneath -- which holds type and a mark, no
   photographs, so the rule Victor set still holds. Sizing it to main instead
   would stop it there and make the grain scroll with the content, which is
   the wrong trade. */
.grain{position:fixed;inset:-64px;z-index:-1;pointer-events:none;
  background-repeat:repeat;opacity:.09;will-change:transform;
  animation:grain .8s steps(1) infinite}
@keyframes grain{
  0%{transform:translate(0,0)}
  12.5%{transform:translate(-14px,9px)}
  25%{transform:translate(11px,-13px)}
  37.5%{transform:translate(-18px,-6px)}
  50%{transform:translate(7px,15px)}
  62.5%{transform:translate(-9px,-16px)}
  75%{transform:translate(16px,5px)}
  87.5%{transform:translate(-6px,12px)}
}
/* The texture stays, the flicker goes -- the grain is the point, not the
   movement, and a full-screen strobe is the wrong thing to insist on. */
@media(prefers-reduced-motion:reduce){.grain{animation:none}}

/* ---------- work reel ---------- */
/* Big frames, nothing around them. No border, no play badge, no credit line --
   the frame is doing the selling and anything drawn on top of it competes.
   Two columns, breaking the content column, because a 4:3 film at half the
   page is the point of the page. */
.reel{display:grid;gap:clamp(2.5rem,5vw,4rem) clamp(1rem,2vw,1.75rem);grid-template-columns:1fr}
@media(min-width:820px){.reel{grid-template-columns:1fr 1fr}}
@media(min-width:1101px){
  .reel{width:auto;margin-inline:calc(50% - 50vw + var(--sbw,0px) / 2 + clamp(1.5rem,4vw,4rem))
        calc(50% - 50vw + var(--sbw,0px) / 2 + clamp(8rem,10vw,14rem))}
}
.reel__item{display:block;width:100%;padding:0;border:0;background:none;color:inherit;
  text-align:left;cursor:pointer;font:inherit}
/* Every tile is 4:3, whatever shape the file is. A row of matching frames is
   the point of the grid; a vertical film picks the slice it shows in the
   reframer rather than bending the row around itself. */
.reel__frame{display:block;position:relative;aspect-ratio:4/3;overflow:hidden;
  background:var(--bg-recessed)}
.reel__frame video{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .5s var(--ease)}
.reel__item:hover .reel__frame video,
.reel__item:focus-visible .reel__frame video{transform:scale(1.02)}
.reel__item:focus-visible{outline:2px solid var(--focus);outline-offset:6px}
.reel__cap{display:block;margin-top:var(--sp-4)}
.reel__cap strong{display:block;font:var(--w-h2) var(--fs-h3)/1.15 var(--f-display);
  text-transform:uppercase;letter-spacing:-.01em}
.reel__cap span{display:block;margin-top:.4em;font:500 var(--fs-micro)/1.6 var(--f-mono);
  letter-spacing:.14em;text-transform:uppercase;color:var(--text-muted)}

/* ---------- full-screen player ---------- */
/* The film fills the window and everything else is a thin layer over it: a
   title, a time, a hairline of progress. Native controls would put a grey
   browser chrome bar across the bottom of a black site. */
.player{position:fixed;inset:0;z-index:95;background:#000;display:grid;
  /* One row, and it may not grow past the window. Without this the row is
     sized by its content: the video takes the full width and, with no
     definite height to resolve 100% against, falls back to its own aspect.
     Every film here was 16:9, which fits, so nothing showed it -- the first
     3:2 film came out 1440x960 in a 900px window with the bottom cut off,
     which reads as the picture being wrong rather than the box being. */
  grid-template-rows:minmax(0,1fr);
  opacity:0;transition:opacity .28s ease}
.player[hidden]{display:none}
.player.is-on{opacity:1}
/* The stage is the window, and the film is pinned to it rather than asked to
   work out 100% of something. A percentage height here resolved against an
   auto row, so the film sized itself by its own aspect and the row grew to
   match -- 960px of picture in a 900px window. inset:0 is a definite box,
   which is what object-fit:contain needs to letterbox against. */
.player__stage{grid-area:1/1;display:grid;place-items:center;min-height:0;
  position:relative;overflow:hidden}
/* Both children share the one cell, so the mark sits ON the film rather
   than in a row of its own underneath it. */
.player__stage > *{grid-area:1/1}
/* Fill the window and letterbox, rather than sitting at the file's natural
   size. max-width/max-height only ever shrink: a 720p film on a 1600px screen
   would have floated small in the middle of all that black. */
.player__video{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  display:block;background:#000;cursor:pointer}
/* The play mark, centred on the film. Outlined rather than filled, and large,
   because on a full-window picture a small solid triangle reads as a watermark
   and this has to read as the control. It steps aside while the film runs and
   comes back the moment it stops. */
.player__play{place-self:center;z-index:2;width:88px;height:88px;
  padding:0;background:none;border:0;color:var(--text);cursor:pointer;
  opacity:.92;transition:opacity .22s ease,transform .22s ease,visibility .22s}
.player__play svg{width:100%;height:100%;fill:none;stroke:currentColor;
  stroke-width:2.2;stroke-linejoin:round;stroke-linecap:round;
  filter:drop-shadow(0 0 10px rgba(0,0,0,.55))}
.player__play:hover,.player__play:focus-visible{opacity:1;transform:scale(1.04)}
.player__glyph--pause{display:none}
/* A plain circle going round, only while the film is waiting for picture. It
   takes the play mark's place rather than sitting beside it. */
.player__wait{grid-area:1/1;place-self:center;z-index:2;width:44px;height:44px;
  border:2px solid rgba(231,234,242,.28);border-top-color:var(--text);border-radius:50%;
  display:none;animation:pspin 900ms linear infinite}
.player.is-waiting .player__wait{display:block}
.player.is-waiting .player__play{opacity:0;visibility:hidden}
@keyframes pspin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){.player__wait{animation-duration:2.4s}}
.player.is-playing .player__play{opacity:0;visibility:hidden}
.player.is-playing .player__play:focus-visible{opacity:1;visibility:visible;transform:none}
.player.is-playing .player__glyph--play{display:none}
.player.is-playing .player__glyph--pause{display:inline}
@media(prefers-reduced-motion:reduce){.player__play{transition:none}}

/* Positioned, and above the film. A positioned box paints over every
   non-positioned sibling whatever the source order says, so the moment the
   film was pinned to the stage with inset:0 this bar -- which was static --
   went under the picture. Title, clock, sound and full screen were all still
   in the document and none of them could be seen or clicked:
   elementFromPoint over the middle of the mute button returned the video. */
.player__bar{grid-area:1/1;align-self:end;position:relative;z-index:2;
  padding:0 clamp(1rem,4vw,2.5rem) clamp(1rem,3vw,1.75rem);
  display:grid;gap:var(--sp-3);
  background:linear-gradient(180deg,rgba(0,0,0,0) 0%,rgba(0,0,0,.72) 62%,rgba(0,0,0,.88) 100%)}
.player__title{margin:0;font:var(--w-h2) var(--fs-h3)/1.2 var(--f-display);
  text-transform:uppercase;letter-spacing:-.01em;color:var(--text)}
.player__title span{display:block;margin-top:.35em;font:500 var(--fs-micro)/1.5 var(--f-mono);
  letter-spacing:.14em;color:var(--text-muted)}
/* Full width at the very foot of the window, the way the reference has it,
   rather than inset inside the bar's padding. Its hit area is taller than the
   line so it can actually be grabbed. */
.player__track{position:fixed;left:0;right:0;bottom:0;z-index:4;height:3px;
  background:rgba(231,234,242,.2);cursor:pointer}
.player__track::after{content:"";position:absolute;left:0;right:0;top:-9px;height:9px}
.player__fill{position:absolute;inset:0 auto 0 0;width:0;background:var(--text)}
.player__meta{display:flex;justify-content:space-between;align-items:center;gap:var(--sp-4);
  font:500 var(--fs-micro)/1 var(--f-mono);letter-spacing:.14em;color:var(--text-muted)}
/* Elapsed against total, as one figure -- the slash is the only thing between
   them, so the pair reads as a position rather than two separate numbers. */
.player__clock{display:inline-flex;align-items:center;gap:.5em;font-variant-numeric:tabular-nums}
.player__clock i{font-style:normal;opacity:.5}
.player__tools{display:inline-flex;align-items:center;gap:var(--sp-3)}
.player__tool{width:34px;height:34px;display:grid;place-items:center;background:none;border:0;
  color:var(--text-muted);cursor:pointer;line-height:1;padding:0;transition:color .18s}
.player__tool svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
/* The speaker keeps its cone either way; the arcs and the cross are what say
   which state it is in, so the two never both show. */
.player__mute svg .player__cross{display:none}
.player.is-muted .player__mute svg .player__wave{display:none}
.player.is-muted .player__mute svg .player__cross{display:inline}
.player__tool:hover,.player__tool:focus-visible{color:var(--text)}
/* The way out was a grey glyph on black over a moving picture, which is the
   one control that must never be hunted for. */
/* Size and style come from the shared rule above; only where it sits is its
   own, because this one is fixed to the window rather than to a figure. */
.player__close{position:fixed;top:var(--sp-4);right:clamp(1.25rem,4vw,2.5rem);z-index:3}
/* Neighbours sit in the margins as thumbnails, so the next film is a click
   rather than a trip back to the grid. */
.player__step{position:fixed;top:50%;transform:translateY(-50%);z-index:3;
  display:flex;align-items:center;gap:var(--sp-3);max-width:min(38vw,420px);
  background:none;border:0;padding:0;cursor:pointer;
  /* Full strength. Held back at .82 they read as disabled rather than as the
     quieter thing beside the film, and a thumbnail this small has nothing to
     spare. Hover now speaks through the name, below. */
  opacity:1;transition:opacity .24s ease,visibility .24s}
/* While it plays, the film has the screen to itself. */
.player.is-playing .player__step{opacity:0;visibility:hidden}
.player.is-playing .player__step:focus-visible{opacity:1;visibility:visible}
.player__thumb{flex:0 0 auto;width:clamp(92px,11vw,176px);border-radius:8px;overflow:hidden;
  display:block;background:var(--bg-recessed)}
.player__thumb video{width:100%;aspect-ratio:16/9;object-fit:cover;display:block}
/* A step up in size and weight, and no shadow, as asked. The shadow was doing
   real work -- on a wide film the name lies over the picture, not over black,
   and a bright sky behind it is the hard case -- so the size and the weight
   are carrying that job now instead. */
.player__step b{font:700 .8125rem/1.3 var(--f-mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--text-muted);text-align:left}
.player__step:hover b,.player__step:focus-visible b{color:var(--text)}
.player__step--prev{left:clamp(.75rem,2vw,1.5rem)}
/* Mirrored, so each neighbour's name sits between it and the film. */
.player__step--next{right:clamp(.75rem,2vw,1.5rem);flex-direction:row-reverse}
.player__step--next b{text-align:right}
@media(max-width:900px){.player__step{display:none}}
@media(prefers-reduced-motion:reduce){.player,.player__step,.reel__frame video{transition:none}}

/* A per-film focal point is written as inline object-position; it only
   moves the crop if the element is cropping. */
.strip__tile .frame video{object-fit:cover}

/* Services as typography. Four equal columns read as a template because they
   repeat one shape at one size; the section now sets the four names as the
   graphic -- large, stacked, alternating full and muted so they read as one
   composition -- with the one-liners as mono captions on the same baseline.
   Sized a step below the mockup at Victor's request. */
.head--quiet h2{font-size:clamp(1.5rem,1rem + 2vw,2.6rem);color:var(--text);
  font-weight:600;letter-spacing:-.01em;text-transform:uppercase;max-width:none}
.svc{margin-top:var(--sp-6);border-top:1px solid var(--hairline)}
.svc__line{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-4);
  padding:.5em 0 .6em;border-bottom:1px solid var(--hairline)}
/* The name is the way in: the same blue the questions use on hover, so one
   gesture means "this responds" everywhere on the site. */
.svc__go{text-decoration:none;color:inherit;min-width:0}
.svc__line h3{font:var(--w-h2) clamp(1.7rem,.9rem + 4vw,4rem)/.95 var(--f-display);
  font-stretch:106%;letter-spacing:-.04em;text-transform:uppercase;margin:0;color:var(--text)}
/* All four the same white at rest. The alternating muted line was set
   AFTER the hover rule at equal weight, so on the even lines it won and only
   two of the four ever went blue -- the symptom Victor reported. One colour
   at rest and one on hover removes the argument rather than reordering it. */
.svc__go:hover h3,.svc__go:focus-visible h3{color:var(--accent)}
.svc__line p{margin:0;flex:0 0 auto;color:var(--text-muted);font:500 clamp(.8rem,.7rem + .5vw,1rem)/1.4 var(--f-mono);
  letter-spacing:.14em;text-transform:uppercase;text-align:right}
@media(max-width:820px){
  .svc__line{flex-direction:column;align-items:flex-start;gap:.35em}
  .svc__line p{text-align:left}
}
