/* ============================================================
   ARTIFEX — the motion and material engine
   One file. Every page in the prototype uses it and nothing else.

   Why no library. The reference sites (reactbits, getlayers, supahero) all
   build this on Three.js and React. This site's stated position — on its own
   privacy page — is that it makes no third-party requests at all, and its
   first screen already costs about 1 MB on a phone. Three.js is another
   150 KB before a single model loads, and there is no glTF of these buildings
   to load into it, so "3D" would mean decorative geometry: for an architect,
   the one thing worse than no 3D.

   So depth here is real CSS 3D — a perspective stage with layers on separate
   Z planes, moving at their own rates — and the particles are a canvas the
   size of the viewport. Together they are about 8 KB.
   ============================================================ */

:root{
  /* the brand, unchanged */
  --ink:#0A0A0B; --ink-2:#101012; --ink-3:#16161A;
  --line-d:#232327; --line-d2:#2C2C32;
  --paper:#F7F5F1; --text:#EDEBE7;
  --muted:#9B978F; --muted-d:#837F79;
  --gold:#C5A268; --gold-2:#8C6A2F; --gold-3:#E0C89B;

  /* One family, three widths. Archivo carries a width axis from condensed to
     expanded and a weight axis from thin to black, so the display face, the
     reading face and the label face are the same letters at different cuts
     rather than three typefaces arguing with each other. Headlines are set in
     capitals at the expanded width, which is how a drawing sheet is titled. */
  --fs:Archivo,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --fd:Archivo,-apple-system,"Segoe UI",sans-serif;
  --fm:Archivo,"Segoe UI",sans-serif;
  --w-wide:125;      /* display  — expanded */
  --w-text:100;      /* reading  — normal   */
  --w-narrow:78;     /* labels   — condensed */

  --wrap:1320px;
  --pad:clamp(20px,4.4vw,64px);

  /* glass, as three settings of one material rather than three materials */
  --glass-blur:22px;
  --glass-tint:rgba(255,255,255,.045);
  --glass-edge:rgba(255,255,255,.14);
  --glass-sheen:rgba(255,255,255,.26);

  color-scheme:dark;
}

/* The generated mobile menu exists at every width; only its styles were
   inside the narrow-screen query. Above 860px it therefore had no styles at
   all and rendered as a plain block — five nav links run together in one
   unspaced line across the top of every page, with a second "write to us"
   button beside them. Hidden by default, and the query below turns it on. */
.mob-menu[data-built]{display:none}

/* The `hidden` attribute has to actually hide.
   The browser's own rule is `[hidden]{display:none}`, which any author rule
   setting `display` on the same element beats — and that is not a hypothetical:
   `.fields{display:grid}` on the investor form and `.g-bar{display:flex}` on
   the gallery controls each silently cancelled it. The investor form stayed on
   screen behind its own "thank you", and the gallery shipped a row of dead
   buttons to anyone whose script did not run. One rule, once, for every page. */
[hidden]{display:none!important}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{background:var(--ink);color:var(--text);font:400 16px/1.65 var(--fs);
  overflow-x:hidden;-webkit-font-smoothing:antialiased}
img,video,canvas{max-width:100%;display:block}

/* An image inside a box with a fixed ratio must FILL that box and crop, not
   run out of the bottom of it.
   These rules used to arrive with `.reeded > img`, because every one of these
   containers was fluted. When the fluting was taken off the photographs, the
   sizing left with it — and the project renders drew at their full intrinsic
   height, a 1120x700 file standing a thousand pixels tall inside a 16:11 frame
   and spilling over everything under it. The sizing belongs to the container,
   not to the material that happened to be on it. */
.shot > img,.shot > video,
.shot-tall > img,.shot-main > img,.shot-inset > img,
.shotpane > img,.pane-shot > img,
.faces > img,.face > img,.portrait > img,
.plate > img,.g-pane > img,.g-pane > video,
.noshot > img,.hero-shot > img{
  width:100%;height:100%;object-fit:cover}
a{color:inherit;text-decoration:none}
button{font:inherit;cursor:pointer;border:0;background:none;color:inherit}
::selection{background:var(--gold);color:#141414}

.wrap{max-width:var(--wrap);margin:0 auto;padding-inline:var(--pad)}

/* ============================================================
   1. THE PARTICLE FIELD
   A canvas pinned behind everything. The script draws it; this only places it
   and decides when it is allowed to exist at all.
   ============================================================ */
.field{position:fixed;inset:0;z-index:0;pointer-events:none;
  opacity:0;transition:opacity 1.2s ease}
.field.lit{opacity:1}
/* the field is decoration: a reader who asked for less motion, or a machine
   printing the page, never sees it */
@media (prefers-reduced-motion:reduce){.field{display:none}}
@media print{.field{display:none}}

/* ============================================================
   2. GLASS
   backdrop-filter over a tinted surface, plus a specular edge. The sheen is a
   separate layer so it can catch the cursor without the blur re-rendering.
   ============================================================ */
.glass{position:relative;isolation:isolate;
  background:var(--glass-tint);
  backdrop-filter:blur(var(--glass-blur)) saturate(1.35);
  -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(1.35);
  border:1px solid var(--glass-edge);
  box-shadow:0 24px 60px -30px rgba(0,0,0,.9),
             inset 0 1px 0 rgba(255,255,255,.1)}
/* the highlight that makes it read as a thick pane rather than a blurred box:
   a soft radial that follows the cursor, positioned by --mx/--my in the script */
/* Three stops, not two. A single stop fading straight to transparent spreads
   the light so thinly that the pane looks faintly lit rather than touched: the
   effect was there and nobody could see where the cursor was. A bright, tight
   core over a wide soft falloff reads as a real highlight travelling under the
   glass — the cursor's position is unmistakable, which is the whole point. */
.glass::after{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(420px circle at var(--mx,50%) var(--my,0%),
    var(--glass-sheen) 0%,
    rgba(255,255,255,.11) 26%,
    rgba(255,255,255,.035) 52%,
    transparent 74%);
  opacity:0;transition:opacity .4s ease}
.glass:hover::after,.glass:focus-within::after{opacity:1}
/* On a pane the reader is meant to move across — a newsletter box, a form —
   the light is brighter still and follows without the softening delay. */
.glass-lit{--glass-sheen:rgba(255,255,255,.5)}
.glass-lit::after{transition:opacity .18s ease}

.glass-quiet{--glass-blur:12px;--glass-tint:rgba(255,255,255,.03);
  --glass-edge:rgba(255,255,255,.09)}
.glass-strong{--glass-blur:34px;--glass-tint:rgba(255,255,255,.07);
  --glass-edge:rgba(255,255,255,.2)}

/* backdrop-filter is not universal. Without this, the panels become invisible
   boxes rather than degrading to solid ones. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .glass{background:rgba(16,16,20,.86)}
}

/* ============================================================
   3. REEDED GLASS
   Fluted glass: vertical flutes that refract what is behind them. Two layers —
   the image, blurred and sliced by a repeating gradient, over the sharp image —
   so the flutes read as thickness rather than as stripes drawn on top.
   ============================================================ */
.reeded{position:relative;overflow:hidden;isolation:isolate}
.reeded > img,.reeded > video{width:100%;height:100%;object-fit:cover}
.reeded::before{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  backdrop-filter:blur(7px) saturate(1.2);
  -webkit-backdrop-filter:blur(7px) saturate(1.2);
  /* every second flute is transparent, so the sharp image shows through and
     the blurred one fills the rest — that alternation is the whole effect */
  -webkit-mask-image:repeating-linear-gradient(90deg,
    #000 0 calc(var(--reed,13px) * .52), transparent calc(var(--reed,13px) * .52) var(--reed,13px));
  mask-image:repeating-linear-gradient(90deg,
    #000 0 calc(var(--reed,13px) * .52), transparent calc(var(--reed,13px) * .52) var(--reed,13px))}
/* the specular line down each flute, and the shadow in each valley */
.reeded::after{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  background:repeating-linear-gradient(90deg,
    rgba(255,255,255,.11) 0 1px,
    rgba(255,255,255,.03) 1px calc(var(--reed,13px) * .3),
    rgba(0,0,0,.14) calc(var(--reed,13px) * .52) calc(var(--reed,13px) * .62),
    transparent calc(var(--reed,13px) * .62) var(--reed,13px));
  mix-blend-mode:overlay}
.reeded-fine{--reed:8px}
.reeded-wide{--reed:22px}

/* --- when NOT to flute the whole image ---------------------------------
   Fluted glass at 13px or wider makes a building unrecognisable. Tested side
   by side against the plain image: at 22px the administrative building reads
   as an abstract pattern. For a practice whose photography is the strongest
   thing it owns — and the first reason a client makes contact — veiling every
   render in glass works against the page.

   So the material is used two ways instead, and full flutes are kept for
   atmosphere shots where the subject is a room rather than a building. */

/* 1. A FRAME: flutes hold the edges, the centre stays sharp. The building is
      seen; the glass is felt. This is the default for project images. */
.reeded-frame::before,.reeded-frame::after{
  -webkit-mask-image:
    repeating-linear-gradient(90deg,#000 0 calc(var(--reed,13px) * .52),
      transparent calc(var(--reed,13px) * .52) var(--reed,13px)),
    linear-gradient(90deg,#000 0%,transparent 26%,transparent 74%,#000 100%);
  mask-image:
    repeating-linear-gradient(90deg,#000 0 calc(var(--reed,13px) * .52),
      transparent calc(var(--reed,13px) * .52) var(--reed,13px)),
    linear-gradient(90deg,#000 0%,transparent 26%,transparent 74%,#000 100%);
  -webkit-mask-composite:source-in;
  mask-composite:intersect}
.reeded-frame::after{
  -webkit-mask-image:linear-gradient(90deg,#000 0%,transparent 26%,transparent 74%,#000 100%);
  mask-image:linear-gradient(90deg,#000 0%,transparent 26%,transparent 74%,#000 100%)}

/* 2. A REVEAL: the flutes retract when the pointer arrives, the way a hand
      clears a misted pane. The image is glass until someone looks at it. */
.reeded-reveal::before,.reeded-reveal::after{
  transition:opacity .55s cubic-bezier(.22,.8,.24,1)}
.reeded-reveal:hover::before,.reeded-reveal:focus-within::before{opacity:0}
.reeded-reveal:hover::after,.reeded-reveal:focus-within::after{opacity:.25}
@media (prefers-reduced-motion:reduce){
  .reeded-reveal::before,.reeded-reveal::after{transition:none}
}
/* on a touch screen there is no hover, so the reveal would trap the image
   behind glass permanently */
@media (hover:none){
  .reeded-reveal::before{opacity:0}
  .reeded-reveal::after{opacity:.3}
}

/* ============================================================
   4. DEPTH
   A perspective stage. Children sit on their own Z plane and the script moves
   them at different rates as the page scrolls, which is what makes the descent
   read as going INTO the page rather than down it.
   ============================================================ */
.stage{perspective:1400px;perspective-origin:50% 40%;transform-style:preserve-3d;
  position:relative}
.layer{will-change:transform;transform-style:preserve-3d}
.layer[data-z]{transition:transform .08s linear}

/* a card that turns toward the cursor. The angles come from --tx/--ty, which
   the script sets per element; the transition makes the return home soft
   without making the follow feel laggy. */
.tilt{transform:perspective(900px)
  rotateX(var(--ty,0deg)) rotateY(var(--tx,0deg)) translateZ(0);
  transition:transform .5s cubic-bezier(.22,.8,.24,1)}
.tilt.tracking{transition:transform .08s linear}

/* ============================================================
   5. ARRIVAL
   Everything enters once, upward, in sequence. Nothing loops: motion that
   repeats forever stops being information and becomes wallpaper.
   ============================================================ */
/* The hiding is scoped to html.motion, which the script adds as its very first
   statement. Without that class every block is simply visible.

   This is not defensive style for its own sake. The first version hid
   everything by default and revealed it from an IntersectionObserver, so any
   failure between the two — a script that does not load, an observer that
   delivers nothing, a browser extension that breaks it — left a page with
   nothing on it at all. That was observed: a page whose markup was complete
   rendered as a photograph and no words. Content must never wait on
   JavaScript to exist. */
html.motion [data-rise]{opacity:0;transform:translate3d(0,26px,0);
  transition:opacity .8s cubic-bezier(.22,.8,.24,1),
             transform .9s cubic-bezier(.22,.8,.24,1)}
html.motion [data-rise].in{opacity:1;transform:none}
[data-rise="2"]{transition-delay:.08s}
[data-rise="3"]{transition-delay:.16s}
[data-rise="4"]{transition-delay:.24s}
[data-rise="5"]{transition-delay:.32s}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  [data-rise]{opacity:1;transform:none;transition:none}
  .tilt{transform:none!important}
  .layer{transform:none!important}
}

/* ============================================================
   6. TYPE
   ============================================================ */
/* Labels take the condensed cut. Narrow letters at wide tracking read as a
   drawing annotation rather than as body text that happens to be small. */
.eyebrow{font:600 condensed 10px/1.6 var(--fm);letter-spacing:.24em;font-stretch:78%;
  text-transform:uppercase;color:var(--gold)}
/* The width runs opposite to the size, which is how a type family is meant to
   be used. A headline set large is already loud, so it takes the narrower cut
   and stays on three lines instead of six; a small heading has room to spread,
   so it takes the expanded one and reads as a title rather than as bold text.
   Same letters, three cuts — the hierarchy is the width, not just the size. */
h1,h2,h3{font-family:var(--fd);font-weight:700;font-stretch:112%;
  text-transform:uppercase;letter-spacing:-.012em;
  line-height:.98;color:#fff}
h1{font-weight:800;font-stretch:100%}
h2{font-stretch:118%}
h3{font-stretch:125%;font-weight:600;letter-spacing:.008em}
/* A sentence set in capitals loses the shape a reader recognises words by, so
   the second half of a headline drops to the normal width and a lighter
   weight. The line still reads as one sentence; it stops reading as a shout. */
h1 em,h2 em{font-style:normal;font-weight:400;font-stretch:100%;
  text-transform:none;letter-spacing:-.018em;color:var(--gold)}
/* Capitals need roughly a third less size than lowercase for the same presence
   — they have no descenders and no x-height to read past. At the old 86px a
   six-word headline ran to six lines and pushed the portrait off the screen. */
h1{font-size:clamp(29px,4.3vw,58px)}
h2{font-size:clamp(28px,4.4vw,58px);line-height:1.08}
h3{font-size:clamp(19px,2.2vw,27px);line-height:1.2}
.lede{font-size:clamp(15.5px,1.5vw,19px);line-height:1.72;color:#C9C5BE;
  max-width:56ch}
p{color:var(--muted);max-width:68ch}
.num{font-family:var(--fd);font-stretch:125%;font-size:clamp(30px,4.4vw,56px);color:var(--gold);
  line-height:1;font-variant-numeric:tabular-nums}

/* ============================================================
   7. THE SHELL — one header and one footer on every page
   ============================================================ */
.nav{position:fixed;top:0;left:0;right:0;z-index:60;
  padding-block:14px;transition:background .3s,border-color .3s;
  border-bottom:1px solid transparent}
.nav.stuck{background:rgba(10,10,11,.72);border-bottom-color:var(--line-d);
  backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px)}
.nav-in{max-width:var(--wrap);margin:0 auto;padding-inline:var(--pad);
  display:flex;align-items:center;gap:26px}
.brand{display:flex;align-items:center;gap:11px;flex:0 0 auto;
  font-family:var(--fd);font-stretch:125%;font-size:17px;letter-spacing:.05em;color:#fff}
.brand img{width:27px;height:27px}
.brand small{display:block;font:500 condensed 7.5px/1 var(--fm);letter-spacing:.24em;
  text-transform:uppercase;color:var(--muted-d);margin-top:5px}
.nav-links{display:flex;gap:22px;margin-left:auto;
  font:500 10.5px/1 var(--fs);letter-spacing:.15em;text-transform:uppercase}
.nav-links a{color:rgba(255,255,255,.7);padding-bottom:4px;
  border-bottom:1px solid transparent;transition:color .2s,border-color .2s}
.nav-links a:hover,.nav-links a[aria-current]{color:#fff;border-color:var(--gold)}
.nav-cta{flex:0 0 auto;font:600 10.5px/1 var(--fs);letter-spacing:.15em;
  text-transform:uppercase;border:1px solid rgba(255,255,255,.3);
  padding:12px 18px;color:#fff;transition:all .2s}
.nav-cta:hover{background:var(--gold);border-color:var(--gold);color:#141414}
.burger{display:none;flex-direction:column;gap:5px;margin-left:auto;padding:7px}
.burger span{width:22px;height:1.5px;background:#fff;transition:transform .22s}

.foot{position:relative;z-index:2;border-top:1px solid var(--line-d);
  background:var(--ink-2);padding-block:clamp(40px,6vw,72px) 30px;margin-top:0}
.foot-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr;
  gap:clamp(26px,4vw,60px)}
.foot h3{font-size:20px;margin-bottom:14px}
.foot a{color:var(--muted);font-size:14px;line-height:2.1;display:block;
  transition:color .2s}
.foot a:hover{color:var(--gold)}
.foot-base{margin-top:38px;padding-top:20px;border-top:1px solid var(--line-d);
  display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;
  font:500 condensed 10px/1 var(--fm);letter-spacing:.14em;color:var(--muted-d)}

/* ============================================================
   8. PIECES USED ON EVERY PAGE
   ============================================================ */
.btn{display:inline-flex;align-items:center;gap:11px;
  font:600 11px/1 var(--fs);letter-spacing:.17em;text-transform:uppercase;
  padding:17px 26px;background:var(--gold);color:#141414;
  transition:transform .3s cubic-bezier(.22,.8,.24,1),box-shadow .3s}
.btn:hover{transform:translateY(-2px);
  box-shadow:0 16px 34px -14px rgba(197,162,104,.6)}
.btn.ghost{background:transparent;color:#fff;
  border:1px solid rgba(255,255,255,.32)}
.btn.ghost:hover{background:rgba(255,255,255,.07);box-shadow:none}

.sec{position:relative;z-index:2;padding-block:clamp(70px,10vw,150px)}
.sec-head{display:flex;align-items:flex-end;justify-content:space-between;
  gap:24px;margin-bottom:clamp(28px,4vw,56px);flex-wrap:wrap}
.sec-head h2{max-width:20ch}

/* the panel that carries most content: glass over the moving background */
.pane{padding:clamp(22px,3vw,38px)}

/* a link that says where it goes rather than just being blue */
.go{display:inline-flex;align-items:center;gap:10px;
  font:500 condensed 10.5px/1 var(--fm);letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);transition:gap .25s}
.go:hover{gap:16px}

@media(max-width:860px){
  .nav-links,.nav-cta{display:none}
  .burger{display:flex}
  .foot-grid{grid-template-columns:1fr;gap:26px}
  :root{--glass-blur:14px}

  /* Four pages shipped a burger with nothing behind it, so below 860px — where
     the links are hidden — they had no navigation at all. The script now builds
     this panel out of the page's own .nav-links wherever one is missing, and
     marks it data-built. The styles live here so no page has to carry them, and
     a page that already defines its own .mob-menu is untouched. */
  .mob-menu[data-built]{display:flex;position:fixed;inset:64px 0 auto;z-index:60;
    flex-direction:column;
    padding:8px var(--pad) 20px;
    background:rgba(10,10,11,.95);
    backdrop-filter:blur(20px) saturate(1.3);
    -webkit-backdrop-filter:blur(20px) saturate(1.3);
    border-bottom:1px solid var(--line-d);
    transform:translateY(-10px);opacity:0;visibility:hidden;
    transition:transform .26s ease,opacity .26s ease,visibility 0s .26s}
  /* visibility, not transform alone: a panel parked out of sight by a transform
     keeps its links in the tab order, and a keyboard reader falls into a menu
     they cannot see. */
  .mob-menu[data-built].open{transform:none;opacity:1;visibility:visible;
    transition:transform .26s ease,opacity .26s ease,visibility 0s}
  .mob-menu[data-built] a{padding:15px 2px;border-bottom:1px solid var(--line-d);
    font:600 12px/1 var(--fs);letter-spacing:.14em;text-transform:uppercase;
    color:rgba(255,255,255,.84)}
  .mob-menu[data-built] a:last-child{border-bottom:0}
  .mob-menu[data-built] a[aria-current]{color:var(--gold)}
}

/* ============================================================
   THE LANGUAGE SWITCHER
   The markup is written into every page by ops/build_i18n.py, and the
   styles for it were only ever in site.css. These pages do not load
   site.css, so on the live site the switcher appeared as the browser's
   own disclosure triangle with EN stacked under it, outside the nav.
   Copied rather than rewritten: two switchers styled twice would drift.
   ============================================================ */
.flag-sprite{position:absolute;width:0;height:0;overflow:hidden}
.langs-nav{position:relative;flex:0 0 auto}
.langs-nav summary{display:flex;align-items:center;gap:6px;cursor:pointer;
  list-style:none;padding:7px 8px;color:rgba(255,255,255,.72);
  border:1px solid transparent;transition:color .16s,border-color .16s}
.langs-nav summary::-webkit-details-marker{display:none}
.langs-nav summary:hover{color:#fff;border-color:var(--line-d2)}
.langs-nav summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.langs-nav[open] summary{color:var(--gold);border-color:var(--line-d2)}
.lang-globe{width:17px;height:17px;flex:0 0 auto}
.lang-now{font:500 9.5px/1 var(--fm);letter-spacing:.12em}
.lang-list{position:absolute;top:calc(100% + 8px);right:0;z-index:80;
  min-width:186px;padding:7px;background:var(--ink-2);
  border:1px solid var(--line-d2);box-shadow:0 22px 44px -20px rgba(0,0,0,.8)}
.lang-row{display:flex;align-items:center;gap:11px;padding:9px 10px;
  font:400 13.5px/1.3 var(--fs);color:rgba(255,255,255,.78);font-weight:400}
.lang-row:hover{background:var(--ink-3);color:#fff}
.lang-row:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
.lang-row[aria-current]{color:var(--gold);background:rgba(197,162,104,.09)}
.flag{width:23px;height:15.3px;flex:0 0 auto;
  box-shadow:0 0 0 1px rgba(255,255,255,.22)}
.lang-name{flex:1 1 auto}
.lang-code{font:500 9px/1 var(--fm);letter-spacing:.12em;color:var(--muted-d)}
.lang-row[aria-current] .lang-code{color:var(--gold-2)}
@media(max-width:860px){
  .langs-nav{margin-left:auto}
  .langs-nav summary{padding:7px 4px;gap:4px}
  .lang-now{display:none}
}
