/* REISSUED 10.08.2026. This file's published address contains a hash of its
   contents, and the previous one had to be abandoned: during a deploy an edge
   node answered that address with the homepage under a 200 and cached it as
   immutable, so the address could never serve this file again. Editing this
   comment changes the hash, which is what puts the theme back on the site. The
   cause is dealt with in scripts/build.js — a real 404.html — and the workflow
   now checks the live site rather than trusting a green deploy.
   ==========================================================================
   theme-final.css — the site's theme.

   Character lives here: type, rhythm, cards, the first screen, the section
   treatments. The shared foundation — tokens, reset, header, preloader, form,
   footer — is base.css.

   This began as one of three design directions the client compared. He chose
   this one and asked for a round of changes on top of it; the other two were
   removed once the build was accepted, and this is now the only theme.
   ========================================================================== */

/* --------------------------------------------------------------------------
   TYPEFACE
   Archivo for headings and for body text alike. It has no italic: the .italic
   class stays on the first-screen headline in the markup, but the slant is
   switched off here. A browser would otherwise synthesise one, and at that
   size a faked italic is immediately visible.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   PURE WHITE
   The client asked for the semi-transparent white to go everywhere. So the fix is
   in the tokens rather than in two dozen selectors: --muted and --muted-2 are
   no longer translucent. There is exactly one exception — the resting state of
   a form field's label, see the note at the bottom of this file — where being
   dimmed carries meaning rather than decoration.
   -------------------------------------------------------------------------- */

:root {
  --font-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --font-text: "Archivo", "Helvetica Neue", Arial, sans-serif;
}

/* Pure white, per the note above: these two tokens carry the change so that
   individual selectors do not have to. */
:root {
  --muted:   #ffffff;
  --muted-2: #ffffff;
}

body {
  font-family: var(--font-text);
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
  line-height: 1.6;
}

/* ⚠️ THE TYPE SCALE BELOW IS APPROVED. DO NOT "TIDY" IT.
   The client did not approve a direction in the abstract — he approved one
   specific rendered page, and these are its numbers. Sizes, weights, leading
   and letter-spacing were matched to it character by character. Any instinct
   to "adjust the scale now that it is a sans" produces a different layout from
   the one that was signed off, and the difference is measurable with a ruler
   in the inspector. The rule: change anything you like except the figures
   below. The single exception is the first-screen headline, see further down,
   where a separate requirement applies — the text must always fit on the
   first screen. */
h1, h2, h3, .display {
  font-family: var(--font-display);
  font-weight: 400; line-height: 1.05; letter-spacing: -0.015em;
}

.h1 { font-size: clamp(2.6rem, 6.4vw, 5.6rem); }
.h2 { font-size: clamp(2rem, 4.2vw, 3.6rem); }
.h3 { font-size: clamp(1.15rem, 1.7vw, 1.5rem); line-height: 1.2; }
/* Archivo has no italic. The .italic class stays in the markup, but the slant
   is switched off — the browser would otherwise synthesise a fake one. */
.italic { font-style: normal; }

/* --------------------------------------------------------------------------
   PRELOADER
   The client asked for the logo animation, and the company name especially, to
   be larger. Colour and the character of the drawing are left alone — those he
   was happy with.
   -------------------------------------------------------------------------- */
.preloader__mark { width: clamp(190px, 30vw, 340px); }
.preloader__word {
  margin-top: 2rem;
  font-size: clamp(1rem, 2.2vw, 1.6rem);
  letter-spacing: 0.38em; text-indent: 0.38em;
  color: var(--gold);
}

/* --------------------------------------------------------------------------
   HEADER
   -------------------------------------------------------------------------- */
.nav__link { color: var(--white); }
.nav__link.is-active { color: var(--gold); }

/* Mobile menu: the background is translucent, so the page shows through it,
   blurred. backdrop-filter alone is not enough — without a wash behind it the
   menu text sits straight on a photograph and stops being readable, so a light
   veil stays.

   ⚠️ TWO THINGS WITHOUT WHICH THE BLUR DISAPPEARS AFTER SCROLLING.
   The menu panel lives INSIDE the header, and a scrolled header (.nav.is-stuck)
   carries a backdrop-filter of its own. An element with backdrop-filter becomes
   a backdrop root for its descendants: the nested panel then has nothing left
   to blur and collapses into a flat translucent fill. At the very top of the
   page the header has no filter yet, so the blur worked there — which is why
   the bug was only visible after scrolling. Hence: (1) while the menu is open
   the header's filter and background are removed; (2) the panel spans the whole
   viewport and is pushed down by padding, or the strip beneath the header would
   stay unblurred. The brand and the burger stay above the panel by way of the
   header's own z-index. */
@media (max-width: 900px) {
  .nav__links {
    inset: 0;
    padding-block-start: var(--nav-h);
    background: rgb(var(--ink-rgb) / 0.55);
    backdrop-filter: blur(22px) saturate(150%);
    -webkit-backdrop-filter: blur(22px) saturate(150%);
  }
  body.is-menu-open .nav,
  body.is-menu-open .nav.is-stuck {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-block-end-color: transparent;
  }
  /* The header's own z-index does not help here: the mark, the burger and the
     panel are siblings inside it, and the panel's z-index:49 would cover them.
     So they are lifted above it. */
  .brand, .nav__burger { position: relative; z-index: 50; }
}

/* --------------------------------------------------------------------------
   First screen
   The three principles from the brief moved here from a section of their own.
   That section was removed outright — otherwise the same text would be read
   twice in a row.
   -------------------------------------------------------------------------- */
.hero {
  position: relative; min-height: 100svh;
  display: flex; align-items: flex-end;
  padding-block: calc(var(--nav-h) + 3rem) clamp(3rem, 7vw, 6rem);
  overflow: hidden;
}
/* THE SEAM. On a phone with a fractional pixel ratio (Android Chrome, and above
   all its "desktop site" mode, where a 980px page is drawn at ~1.1 device
   pixels per CSS pixel) the video is composited on a layer of its own and
   snapped to device pixels separately from the veil above it. The two edges
   then land a device pixel or more apart, and a hairline of unveiled footage
   shows along the right and the bottom of the first screen — the client saw
   it on 22.09.2026. So nothing is left to line up exactly: the media box
   clips its own video (the parallax scale grows it past the box), the veil
   overhangs every edge and is cut back by the hero's clip, and the section
   below tucks one pixel under the hero's foot. */
.hero__media { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.hero + .section { position: relative; z-index: 1; margin-top: -1px; background: var(--ink); }
/* ⚠️ NO COLOUR FILTER ON THE VIDEO. The frame's subject is a bright red train
   crossing the city, and that is what the client wants seen. It used to be
   desaturated twice over — once baked into the encode (see scripts/media.sh)
   and once again here — and between the two the train disappeared. The footage
   now runs in its own colour; the only thing laid over it is the veil below,
   whose whole job is keeping the headline readable. */
.hero__media video, .hero__media img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(var(--hero-scale, 1));
  transition: transform 0.1s linear;
}
/* Lighter than it was, at the client's request: the picture shows through and
   the sunset keeps its colour. The bottom of the gradient stays dense — the
   headline and the three principles sit there, and they have to be readable
   over whatever frame is playing behind them. */
.hero__veil {
  position: absolute; inset: -4px; z-index: 1;
  background:
    linear-gradient(180deg, rgb(var(--ink-rgb) / 0.42) 0%, rgb(var(--ink-rgb) / 0.10) 34%, rgb(var(--ink-rgb) / 0.88) 100%),
    radial-gradient(90% 62% at 50% 100%, rgb(var(--ink-rgb) / 0.78), transparent 72%),
    rgb(var(--ink-rgb) / 0.14);
}
.hero__inner {
  position: relative; z-index: 2; width: var(--shell); margin-inline: auto;
  /* Centred, at the client's request. Everything on the first screen follows
     from this one line: the rule below the headline and the principles are
     centred as blocks further down, not each with a rule of its own. */
  text-align: center;
}
.hero__title { max-width: 16ch; margin-inline: auto; }
/* The gap between the two phrases, at the client's request — they read as
   "stuck together" at a line-height of 1.05, which is right for a headline that
   wraps and too tight for two separate statements. In em, so it scales with the
   type instead of opening up on a phone. */
.hero__title .line2 { display: block; color: var(--gold); margin-block-start: 0.22em; }
.hero__line {
  width: min(420px, 60%); height: 1px; background: var(--gold);
  margin-block: clamp(1.4rem, 2.6vw, 2.2rem); margin-inline: auto;
  transform-origin: center;
  animation: grow 1.4s var(--ease) 1.2s backwards;
}
@keyframes grow { from { transform: scaleX(0); } }

/* Three paragraphs stacked, exactly where the single descriptive line used to
   sit. A column, with no rules between them: this is continuous prose, not
   three tiles.

   WHY THE SIZES DEPEND ON VIEWPORT HEIGHT (svh), NOT WIDTH ALONE
   The client's requirement is that the text always fits on the first screen.
   vw alone cannot deliver that: a wide but short viewport — a 1440×700 laptop,
   a phone held sideways — yields a large size at a small height, and the third
   paragraph slides below the fold. So both the headline and the paragraphs take
   the SMALLER of two measures, a fraction of the width and a fraction of the
   height. The clamp bounds stay: the text neither inflates on a very large
   screen nor shrinks past readability on a very small one. */
/* The same sizes as the approved page, but with a ceiling tied to viewport
   HEIGHT. Above 900 px tall the limit never engages and the sizes match the
   approved rendering exactly; it only comes into play where the third
   paragraph would otherwise fall past the bottom edge. */
/* The headline is about 11% smaller than the approved scale, at the client's
   request — roughly 73 px down to 65 px in his own window. The reduction is
   applied to all three measures at once (width fraction, height fraction and
   ceiling), or it would shrink on some screens and stay put on others. The
   site's other headings are left alone: they match the approved rendering
   character for character. */
/* ONE LINE PER PHRASE, at the client's request: "Worldwide Capital Raising" and
   "Investments and Market Expansion" each hold to a single line at every width.
   That is what the size below is for. The longer of the two runs to about 32
   characters, so the type can only be as large as the shell divided by that —
   hence 5vw rather than the 5.7vw it was, and a lower ceiling. Raise either
   figure and the subtitle starts to run off the side of the screen instead of
   wrapping, because nowrap forbids the wrap. */
.hero--creed .hero__title {
  font-size: clamp(1rem, min(5vw, 8svh), 4.3rem);
  max-width: none; white-space: nowrap;
}
.hero--creed .hero__line { margin-block: clamp(1rem, min(3vw, 4.3svh), 2.4rem); }
/* The block sits higher than it did, at the client's request — twice now, the
   second time to match where helarb.com sets its first line. It is bottom
   aligned, so "higher" is bottom padding — and the padding is measured in svh
   rather than vw: the room being given away is vertical, and on a short window
   there is none to give. */
.hero--creed { padding-block-end: clamp(3rem, 16svh, 11rem); }

.hero__creed { max-width: 62ch; margin-inline: auto; }
.hero__creed p {
  color: var(--white);
  font-size: clamp(0.9rem, min(1.3vw, 2.4svh), 1.2rem);
  line-height: 1.55;
}
.hero__creed p + p { margin-block-start: clamp(0.5rem, 1.3svh, 1rem); }

/* Laptops with a short viewport: the sizes are already at their height
   ceiling, so any further room comes out of spacing, not out of type. */
@media (max-height: 860px) {
  /* The floor is the scroll cue's room. Centring the cue put it directly under
     the last line of the principles instead of off in the corner, so from here
     on the bottom padding is not only "how high the text sits" but also "where
     the cue lives" — and on a short window the svh share alone is less than the
     cue is tall, which is how the two came to overlap. The cue itself also
     shortens here. */
  :root { --cue-h: 24px; }
  .hero--creed { padding-block: calc(var(--nav-h) + 1.5rem) max(7.5rem, clamp(2.5rem, 12svh, 7rem)); }
}

/* Narrow screens: lines run long relative to the width, so the slack comes out
   of the first screen's spacing rather than its type — the text is already at
   the lower bound of readability. */
@media (max-width: 700px) {
  .hero { padding-block: calc(var(--nav-h) + 1.5rem) clamp(2rem, 6svh, 3.5rem); }
  .hero__creed p { line-height: 1.5; }

  /* THE PHONE IS NOT THE DESKTOP HERE, and deliberately so.
     The client asked for the headline on the phone to be much larger — the
     weight it has on helarb.com. On a 390 px screen the one-line-per-phrase
     rule and a large headline cannot both hold: "Investments and Market
     Expansion" is 32 characters, and forbidding the wrap caps the type at
     about 19 px. So the wrap comes back on narrow screens only — the desktop,
     where the client asked for one line per phrase, still has it — and the
     type roughly doubles. `balance` keeps the two lines of a wrapped phrase
     close to equal instead of leaving one word alone on the second. */
  .hero--creed .hero__title {
    white-space: normal;
    text-wrap: balance;
    font-size: clamp(1.5rem, min(7.6vw, 5svh), 2.4rem);
  }
  /* And much higher up the frame, also at the client's request: on the phone
     the block sat just off the bottom edge. The lift is bottom padding, capped
     in svh so that a short phone gives up less of it — the whole of the first
     screen's text still has to fit above the fold. */
  .hero--creed { padding-block-end: clamp(3rem, 22svh, 11rem); }
}

/* The hardest case is a phone held sideways: 390 px of height, 76 of which
   the header takes. Shrinking the type is not enough here — the headline also
   has to be let out in width (it holds to 16 characters by default and so runs
   to four lines) and the spacing has to go. Without that the third paragraph
   falls off the bottom. */
@media (max-height: 520px) {
  /* A 108 px header over 390 px of height is a quarter of the screen. Here, and
     only here, it steps back down — the first screen must still hold all of its
     text, and that requirement predates the taller header. */
  :root { --nav-h: 84px; }
  /* …and the enlarged mark steps back with it, or it would touch both edges
     of the shorter bar. */
  .brand svg { width: 56px; height: 56px; }
  .hero { padding-block: calc(var(--nav-h) + 0.5rem) 1rem; }
  /* The cue is switched off further down, where it is defined — a rule here
     would be overruled by it. What belongs here is the padding it would
     otherwise have needed. */
  .hero--creed { padding-block-end: 1rem; }
  .hero--creed .hero__title { font-size: clamp(1.1rem, min(3.9vw, 6.2svh), 2.3rem); }
  .hero--creed .hero__line { margin-block: 0.6rem; }
  .hero__creed p { font-size: clamp(0.78rem, 1.6svh, 0.9rem); line-height: 1.45; }
  .hero__creed p + p { margin-block-start: 0.45rem; }
}

/* The scroll cue. A link, not a caption, at the client's request: it says
   "scroll", so clicking it must scroll — to the band of figures directly
   below, which is the next thing on the page. The smooth motion and the
   header offset are already set on <html> (scroll-behavior, scroll-padding),
   so the anchor needs no script at all.

   It is centred, like everything else on the first screen. Centred means the
   rule cannot stay beside the word — the pair would be off-centre by half a
   rule — so the rule turns vertical and drops below the word, where it also
   points the way. */
.hero__scroll {
  position: absolute; inset-block-end: clamp(1.5rem, 3vw, 2.5rem); inset-inline: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 0.75rem;
  width: max-content; margin-inline: auto;
  font-size: 0.68rem; letter-spacing: 0.3em; text-indent: 0.3em;
  color: var(--gold-dim);
  transition: color 0.3s var(--ease);
}
.hero__scroll i {
  display: block; width: 1px; height: var(--cue-h, 42px); background: var(--gold-dim);
  transform-origin: top;
  animation: cue 2.4s var(--ease) infinite;
  transition: background 0.3s var(--ease);
}
/* The stroke draws itself downwards and fades out at the bottom, over and
   over: a hint of movement in a still corner, which is what makes the cue
   read as something to press. */
@keyframes cue {
  0%   { transform: scaleY(0); opacity: 0; }
  35%  { transform: scaleY(1); opacity: 1; }
  75%  { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1) translateY(var(--cue-h, 42px)); opacity: 0; }
}
.hero__scroll:hover, .hero__scroll:focus-visible { color: var(--gold); }
.hero__scroll:hover i, .hero__scroll:focus-visible i { background: var(--gold); }
/* Off on a phone, in either orientation: upright there is no room for it under
   the text, and sideways there is barely room for the text itself. */
@media (max-width: 700px), (max-height: 520px) { .hero__scroll { display: none; } }

/* --------------------------------------------------------------------------
   Figures
   -------------------------------------------------------------------------- */
.figures__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--hairline-2); }
.figure { background: var(--ink); padding: clamp(1.5rem, 2.6vw, 2.4rem); }
.figure__value {
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.4vw, 3rem);
  color: var(--gold); line-height: 1; margin-block-end: 0.6rem;
}
.figure__label { font-size: 0.72rem; letter-spacing: 0.22em; text-transform: uppercase; color: var(--white); margin-block-end: 1.2rem; }
.figure__items li { font-size: 0.86rem; color: var(--white); padding-block: 0.3rem; }
.figure__items li + li { border-block-start: 1px solid var(--hairline-2); }
@media (max-width: 1000px) { .figures__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .figures__grid { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   Services (10)
   No hover at all: the border starts at the colour it used to travel to on
   hover, the heading is gold and the description white.
   -------------------------------------------------------------------------- */
.services__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(1rem, 1.8vw, 1.6rem); }
.service {
  position: relative; padding: clamp(1.75rem, 3vw, 2.75rem);
  border: 1px solid var(--hairline);
}
.service__num { font-size: 0.7rem; letter-spacing: 0.24em; color: var(--gold-dim); }
.service h3 { margin-block: 1.1rem 0.9rem; color: var(--gold); }
.service p { color: var(--white); line-height: 1.7; }
@media (max-width: 800px) { .services__grid { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   About — sits BEFORE Sectors; the frame is landscape
   -------------------------------------------------------------------------- */
/* The text aligns to the top rather than the centre: the columns differ in
   height, and centring left half a screen of emptiness above the first
   paragraph. */
.about__grid { display: grid; grid-template-columns: 1fr 1.1fr; gap: clamp(2rem, 5vw, 5rem); align-items: start; }
.about__text p { color: var(--white); line-height: 1.8; }
.about__text p + p { margin-block-start: 1.4rem; }
.about__media { position: relative; }
.about__pic { aspect-ratio: 3 / 2; }
.about__media img { filter: saturate(0.7) brightness(0.9); }
.about__media::after { content: ""; position: absolute; inset: 0; border: 1px solid var(--hairline); transform: translate(14px, 14px); pointer-events: none; }
@media (max-width: 900px) { .about__grid { grid-template-columns: 1fr; } .about__media { order: -1; } }

/* --------------------------------------------------------------------------
   Sectors — the effect turned inside out
   It was: a dim photograph that brightened and grew on hover.
   It is now: a bright photograph that dims on hover, so the text over it gains
   contrast. The zoom is gone — the client asked for the opposite movement, not
   for the same movement in another direction.
   -------------------------------------------------------------------------- */
.sectors__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--hairline-2); }
.sector {
  position: relative; overflow: hidden; background: var(--ink);
  padding: clamp(1.75rem, 3vw, 2.6rem); min-height: 300px;
  display: flex; flex-direction: column; justify-content: flex-end;
}
.sector__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0.72; filter: saturate(0.8) contrast(1.02);
  transition: opacity 0.5s var(--ease), filter 0.5s var(--ease);
}
/* The veil over the photograph is permanent, or the text on it cannot be read.
   On hover it gains density along with the dimming photograph. */
.sector::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(0deg, rgb(var(--ink-rgb) / 0.92) 10%, rgb(var(--ink-rgb) / 0.35));
  transition: background 0.5s var(--ease);
}
.sector:hover .sector__img { opacity: 0.22; filter: saturate(0.5) contrast(1); }
.sector:hover::after {
  background: linear-gradient(0deg, rgb(var(--ink-rgb) / 0.96) 10%, rgb(var(--ink-rgb) / 0.78));
}

/* :not(.sector__img) is required. A bare `.sector > *` of equal specificity
   later in the file would rewrite the image's position from absolute to
   relative, dropping it into the flow at 14 px tall. */
.sector > *:not(.sector__img) { position: relative; z-index: 2; }
.sector h3 { color: var(--gold); margin-block-end: 1rem; }
.sector li { font-size: 0.86rem; color: var(--white); padding-block: 0.28rem; border-block-start: 1px solid var(--hairline-2); }
@media (max-width: 980px) { .sectors__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .sectors__grid { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   Funding — photograph above the text
   -------------------------------------------------------------------------- */
.funding__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(1.25rem, 2.2vw, 2rem); }
.fund { padding: clamp(1.25rem, 2.2vw, 1.75rem) 0 0; border-block-start: 1px solid var(--gold); }
.fund__img {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  filter: saturate(0.75) brightness(0.92);
  margin-block-end: clamp(1rem, 1.8vw, 1.4rem);
}
.fund h3 { font-size: clamp(1.05rem, 1.5vw, 1.35rem); margin-block-end: 0.9rem; color: var(--gold); }
.fund p { font-size: 0.92rem; color: var(--white); line-height: 1.7; }
@media (max-width: 1000px) { .funding__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .funding__grid { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   Projects — 3 × 4, with the same treatment as Sectors
   There is no horizontal scrolling on desktop; the brief requires that.
   -------------------------------------------------------------------------- */
.projects__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--hairline-2); }
.project {
  position: relative; overflow: hidden; background: var(--ink);
  padding: clamp(1.4rem, 2.2vw, 2rem); min-height: 300px;
  display: flex; flex-direction: column; gap: 0.9rem; justify-content: flex-end;
}
.project__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0.62; filter: saturate(0.8) contrast(1.02);
  transition: opacity 0.5s var(--ease), filter 0.5s var(--ease);
}
.project::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(0deg, rgb(var(--ink-rgb) / 0.94) 14%, rgb(var(--ink-rgb) / 0.40));
  transition: background 0.5s var(--ease);
}
.project:hover .project__img { opacity: 0.20; filter: saturate(0.5) contrast(1); }
.project:hover::after {
  background: linear-gradient(0deg, rgb(var(--ink-rgb) / 0.97) 14%, rgb(var(--ink-rgb) / 0.82));
}
.project > *:not(.project__img) { position: relative; z-index: 2; }
.project__type { font-size: 0.66rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--gold); }
.project__client { font-family: var(--font-display); font-size: clamp(1.02rem, 1.35vw, 1.25rem); line-height: 1.25; }
.project__results {
  font-size: 0.86rem; color: var(--white); line-height: 1.65;
  border-block-start: 1px solid var(--hairline-2); padding-block-start: 0.9rem; margin-block-start: auto;
}
@media (max-width: 1000px) { .projects__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .projects__grid { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   Contact — laid out like About: photograph left, text and form right
   -------------------------------------------------------------------------- */
/* The photograph's height is not set by an aspect ratio but stretches to the
   neighbouring column: text and a form sit beside it, and any fixed ratio
   either leaves a gap below the picture or pushes it past the form. */
.contact__grid { display: grid; grid-template-columns: 0.85fr 1.15fr; gap: clamp(2rem, 5vw, 5rem); align-items: stretch; }
.contact__media { position: relative; }
.contact__pic { height: 100%; min-height: 460px; }
.contact__media img { filter: saturate(0.7) brightness(0.9); }
.contact__media::after { content: ""; position: absolute; inset: 0; border: 1px solid var(--hairline); transform: translate(14px, 14px); pointer-events: none; }
.contact__text p { color: var(--white); line-height: 1.8; }
.contact__text p + p { margin-block-start: 1.4rem; }
.contact__text .form { margin-block-start: clamp(2rem, 4vw, 3rem); }
@media (max-width: 900px) {
  .contact__grid { grid-template-columns: 1fr; }
  .contact__media { order: -1; }
  .contact__pic { height: auto; min-height: 0; aspect-ratio: 4 / 3; }
}

/* The button sizes to its content rather than the column: .form is a grid, and
   its cells stretch to full width by default. */
.form .btn { justify-self: start; }

/* The one exception to "everything white": a field's label at rest. Pure white
   here reads as text already typed in — the dimming is not decoration but the
   difference between an empty field and a filled one. */
.field label { color: rgba(255, 255, 255, 0.6); }

/* --------------------------------------------------------------------------
   Footer — the mark instead of a line naming the city
   -------------------------------------------------------------------------- */
.footer__inner { color: var(--white); }
.footer__brand { display: flex; align-items: center; gap: 0.6rem; color: var(--gold); }
.footer__brand svg { width: 30px; height: 30px; flex: none; }
.footer__brand .brand__word { font-size: 0.7rem; letter-spacing: 0.28em; text-indent: 0.28em; color: var(--white); }
