/* ═══ COMPONENTS ═══ */

/* ── Buttons ── */
.ce-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 28px;
  font-family: var(--f-sans);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* Normalise native control styling so Safari does not override the
     custom radius/border with its own button appearance (Brave/Chromium
     already honour these). Keeps the 6px corner identical across engines. */
  -webkit-appearance: none;
  appearance: none;
  border: 1px solid currentColor;
  border-radius: var(--btn-radius, 6px);
  color: var(--fg);
  background: transparent;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.ce-btn:hover:not([disabled]) { background: var(--fg); color: var(--bg); }

/* Solid brass primary, lifted off the page with a soft shadow so it reads as the
   main action even over the darker section backgrounds. */
.ce-btn--primary { background: var(--accent); color: var(--ink); border-color: var(--accent); box-shadow: 0 8px 22px -10px rgba(0,0,0,0.6); }
.ce-btn--primary:hover:not([disabled]) { background: transparent; color: var(--accent); box-shadow: none; }

/* Ghost secondary brought forward: full-strength text + a visible border (was
   muted-on-muted, which nearly disappeared against the photo backgrounds). */
.ce-btn--ghost { border-color: var(--fg-muted); color: var(--fg); }
.ce-btn--ghost:hover:not([disabled]) { color: var(--accent); border-color: var(--accent); background: transparent; }

.ce-btn[disabled] { opacity: 0.4; cursor: not-allowed; }

.ce-btn__arrow { transition: transform var(--dur) var(--ease); }
.ce-btn:hover:not([disabled]) .ce-btn__arrow { transform: translateX(4px); }

.btn-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

/* ── Language switch (EN / NL) ── */
.lang-switch { display: inline-flex; align-items: center; gap: 2px; }
.lang-switch__opt {
  font-family: var(--f-sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-muted);
  padding: 6px 8px;
  border-radius: 4px;
  transition: color var(--dur) var(--ease);
}
.lang-switch__opt:hover { color: var(--fg); }
.lang-switch__opt.is-active { color: var(--accent); }
/* In-drawer switch on mobile */
.mobile-nav__lang { display: flex; gap: 6px; padding: 16px 24px; }
@media (max-width: 640px) {
  /* On phones the drawer carries the switch; keep the bar to the hamburger only. */
  .top-bar__actions .lang-switch { display: none; }
}
@media (min-width: 641px) {
  .mobile-nav__lang { display: none; }
}

/* ── Version badge (deploy/cache check, mirrors the configurator's cfg-version) ── */
.site-version {
  position: fixed;
  bottom: 10px; left: 12px;
  z-index: 60;
  font-size: 9px;
  letter-spacing: 0.14em;
  color: var(--fg-muted);
  opacity: 0.45;
  pointer-events: none;
  user-select: none;
}

/* ── Tags ── */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  font-family: var(--f-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  border: 1px solid var(--rule);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.tag::before { content: ""; width: 6px; height: 6px; background: var(--accent); flex-shrink: 0; }
.tag:hover { color: var(--accent); border-color: var(--accent); }
.tag.is-active { color: var(--accent); border-color: var(--accent); }
.tag-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* ── Form fields ── */
.field { display: flex; flex-direction: column; gap: 8px; }

.field__label {
  font-family: var(--f-mono);
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.field__input,
.field__textarea {
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--btn-radius);
  padding: 12px 14px;
  font-family: var(--f-sans);
  font-size: 14px;
  font-weight: 300;
  color: var(--fg);
  width: 100%;
  outline: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.field__input::placeholder,
.field__textarea::placeholder { color: var(--fg-muted); }
.field__input:focus,
.field__textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
}

/* Form panel - gives the minimal underline inputs a readable backing
   over the section background image. */
.form-panel {
  background: color-mix(in srgb, var(--bg-card) 72%, transparent);
  border: 1px solid var(--rule-soft);
  border-radius: var(--card-radius);
  padding: var(--sp-4);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
/* Consistent vertical rhythm for the whole form (replaces per-field margins). */
.contact-form { display: flex; flex-direction: column; gap: 20px; }
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field__label { margin-bottom: 2px; }
/* Button on its own line, helper note stacked below, so the button never
   has to compete for width with the text beside it. */
.form-foot {
  display: flex; flex-direction: column; align-items: flex-start; gap: 14px;
  margin-top: 4px; padding-top: 22px; border-top: 1px solid var(--rule-soft);
}
.form-foot .ce-btn { align-self: flex-start; }
@media (max-width: 540px) {
  .form-panel { padding: var(--sp-3); }
  .field-pair { grid-template-columns: 1fr; gap: 20px; }
}
.field__textarea { resize: vertical; min-height: 130px; line-height: 1.6; }
.field__help { font-family: var(--f-mono); font-size: 9px; color: var(--fg-muted); letter-spacing: 0.12em; }

/* ── Alerts ── */
.alert {
  padding: 14px 18px;
  border: 1px solid;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
}
.alert--success { border-color: var(--accent); color: var(--accent); }
.alert--error   { border-color: #c97070;       color: #c97070; }
.alert ul { padding: 0; margin: 8px 0 0; list-style: none; }
.alert ul li::before { content: "→ "; }

/* ── Toast ── */
.toast {
  position: fixed;
  bottom: 24px; left: 50%;
  transform: translateX(-50%) translateY(12px);
  background: var(--fg);
  color: var(--bg);
  padding: 10px 20px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  z-index: 200;
  white-space: nowrap;
}
.toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ── Project card ── */
.project-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  background: var(--bg-card);
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  overflow: hidden;
}
.project-card:hover {
  transform: translateY(-4px);
  border-color: var(--accent);
  box-shadow: 0 18px 40px -22px rgba(0,0,0,0.7);
}

.project-card__img {
  aspect-ratio: 4 / 3;
  /* stripe shows until real image loads */
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--accent) 16%, var(--bg-card)) 0 6px,
      var(--bg-card) 6px 12px);
  position: relative;
  overflow: hidden;
}
.project-card__img img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 400ms var(--ease);
}
.project-card:hover .project-card__img img { transform: scale(1.03); }
.project-card__img::after {
  content: attr(data-label);
  position: absolute; left: 12px; bottom: 10px;
  z-index: 1;
  font-family: var(--f-mono);
  font-size: 9px;
  letter-spacing: 0.16em;
  color: var(--fg-muted);
  text-transform: uppercase;
}
.project-card__body { padding: 20px; display: flex; flex-direction: column; gap: 8px; }
.project-card__tag  { font-family: var(--f-mono); font-size: 9px; letter-spacing: 0.2em; color: var(--accent); text-transform: uppercase; }
.project-card__title { font-family: var(--f-display); font-size: 22px; font-weight: 300; line-height: 1.1; }
.project-card__meta  { font-family: var(--f-mono); font-size: 9px; letter-spacing: 0.16em; color: var(--fg-muted); text-transform: uppercase; }

/* ── Project grid ── */
.project-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }

/* ── Tagline rule (hero) ── */
.tagline-rule {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 20px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.tagline-rule__text {
  font-family: var(--f-sans);
  font-weight: 700;
  font-size: clamp(10px, 1.2vw, 14px);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .tagline-rule { display: none; }
}
.tagline-rule__text--accent { color: var(--accent); }
.tagline-rule__line { flex: 1; height: 1px; background: var(--rule); }

/* ── Stat row ── */
.stat-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  padding: 32px 0;
  border-top: 1px solid var(--rule);
}
.stat { display: flex; flex-direction: column; gap: 4px; }
.stat__label { font-family: var(--f-mono); font-size: 9px; letter-spacing: 0.2em; color: var(--fg-muted); text-transform: uppercase; }
.stat__value { font-family: var(--f-display); font-size: 36px; font-weight: 300; line-height: 1; color: var(--fg); letter-spacing: -0.01em; }
.stat__value em { color: var(--accent); font-style: normal; }

/* ── Photo placeholder ── */
.photo-placeholder {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--rule-soft);
  /* gradient shows until real image loads */
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--accent) 18%, var(--forge)) 0%,
      var(--forge) 100%);
}
.photo-placeholder img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* keep caption/tag labels above the image */
.photo-placeholder__caption,
.photo-placeholder__tag { z-index: 1; }
.photo-placeholder__caption {
  position: absolute; left: 12px; bottom: 10px; right: 12px;
  display: flex; justify-content: space-between;
  font-family: var(--f-mono); font-size: 9px;
  letter-spacing: 0.16em; color: var(--accent); text-transform: uppercase;
}
.photo-placeholder__tag {
  position: absolute; left: 12px; top: 10px;
  font-family: var(--f-mono); font-size: 9px;
  letter-spacing: 0.2em; color: var(--fg-muted); text-transform: uppercase;
}

/* ── Contact aside ── */
.contact-layout { display: grid; grid-template-columns: 1fr 400px; gap: 80px; align-items: start; }
.contact-info { display: flex; flex-direction: column; gap: 32px; }
.contact-info__block { display: flex; flex-direction: column; gap: 8px; }
.contact-info__block .mono { color: var(--accent); }
.contact-info__block p { font-size: 13px; color: var(--fg-muted); margin: 0; }

/* ── Project grid: proof variant (demoted projects) ── */
.project-grid--proof { grid-template-columns: repeat(3, 1fr); }

/* ════════════════════════════════════════════════════════
   DESIGN TEASER - copy + colour swatch cloud (light bridge, not a configurator)
   ════════════════════════════════════════════════════════ */
.design-teaser {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-5);
  align-items: center;
}
.design-teaser__label {
  display: block;
  font-family: var(--f-mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--accent); margin-bottom: var(--sp-3);
}
.design-teaser__note {
  display: block; margin-top: var(--sp-3);
  font-family: var(--f-mono); font-size: 9px; letter-spacing: 0.12em; color: var(--fg-muted);
}

/* Colour swatch cloud - small circles showing the finish range, on a tidy grid */
.swatch-cloud {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 16px;
  max-width: 440px;
  justify-items: center;
}
.dot-swatch {
  width: 100%;
  max-width: 54px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: var(--swatch);
  /* one consistent hairline ring on every swatch, regardless of its colour
     (a per-colour border made the dark Obsidian look outlined and the rest not) */
  border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
  box-shadow: inset 0 1px 2px rgba(255,255,255,0.12);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.dot-swatch:hover {
  transform: translateY(-3px) scale(1.06);
  box-shadow: 0 10px 22px -10px rgba(0,0,0,0.65);
}

/* ── How it works - step grid ── */
.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.step {
  display: flex; flex-direction: column; gap: 12px;
  padding: var(--sp-4) var(--sp-3);
  border: 1px solid var(--rule-soft);
  border-top: 2px solid var(--accent);
  border-radius: var(--card-radius);
  background: var(--bg-card);
}
.step__num { font-family: var(--f-mono); font-size: 10px; letter-spacing: 0.2em; color: var(--accent); }
.step__title { font-family: var(--f-display); font-size: 26px; font-weight: 300; line-height: 1; }
.step__body { font-size: 13px; color: var(--fg-muted); margin: 0; }

/* ── Materials - feature cards ── */
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.feature-card {
  display: flex; flex-direction: column;
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  background: var(--bg-card);
  overflow: hidden;
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.feature-card:hover { transform: translateY(-4px); border-color: var(--accent); box-shadow: 0 18px 40px -22px rgba(0,0,0,0.7); }
.feature-card__img { aspect-ratio: 16 / 10; position: relative; overflow: hidden; }
.feature-card__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 500ms var(--ease); }
.feature-card:hover .feature-card__img img { transform: scale(1.04); }
.feature-card__video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 500ms var(--ease); }
.feature-card:hover .feature-card__video { transform: scale(1.04); }
.feature-card__body { padding: 22px; display: flex; flex-direction: column; gap: 10px; }
.feature-card__tag { font-family: var(--f-mono); font-size: 9px; letter-spacing: 0.2em; color: var(--accent); text-transform: uppercase; }
.feature-card__title { font-family: var(--f-display); font-size: 24px; font-weight: 300; line-height: 1; }
.feature-card__text { font-size: 13px; color: var(--fg-muted); margin: 0; }

/* ════════════════════════════════════════════════════════
   ABOUT US - portrait photo beside a short piece of copy
   ════════════════════════════════════════════════════════ */
/* Copy first, photo second - the source order carries the desktop layout, and about.php explains
   why the photo belongs on the right. Photo column the narrower of the two: the circle is capped
   anyway, so giving it half the row would only pad it with empty space. */
.about {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 4fr);
  gap: var(--sp-5);
  align-items: center;
}
/* A CIRCLE NEEDS A SQUARE FRAME. aspect-ratio 1/1 is what makes border-radius:50% a circle rather
   than an ellipse, and it is also the shape that costs the supplied photograph least: a near-square
   source shown in a 4/5 box loses about 17% of its width, and in the 3/2 box this used on phones it
   lost a third of its height, which is a crop straight through somebody's shoulders.
   Capped rather than fluid: a circle that scales with the column reaches an absurd size on a wide
   monitor, and a face does not need to be 600px across to be met. */
.about__media {
  position: relative;
  aspect-ratio: 1 / 1;
  width: 100%;
  max-width: 420px;
  justify-self: center;
  overflow: hidden;
  border-radius: 50%;
  /* One hairline ring, the same treatment .dot-swatch gives its circles, so the photo reads as a
     deliberate shape rather than as an image that happens to be round. */
  border: 1px solid var(--rule);
  background: var(--bg-card);
}
.about__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.about__body { display: flex; flex-direction: column; }
/*
 * EVERY PARAGRAPH THE SAME, and that is the point rather than a shortcut.
 *
 * There was a .about__lead here: the first paragraph set in the display face at clamp(20px, 2.1vw,
 * 28px), to give the section an opening line. It was invented for this section and existed nowhere
 * else on the site, which is exactly why it looked wrong - no other section has an emphasised lead,
 * so the eye read it as a mistake rather than as emphasis. The hierarchy is already carried by
 * .sec__title and .sec__sub above.
 *
 * Font size and line height are INHERITED on purpose, not restated: base.css sets the body to
 * 14px/1.75, so this is the same paragraph projects.php and materials.php write inline
 * (max-width: 58ch; color: var(--fg-muted)) - identical by construction instead of by coincidence.
 */
.about__text { color: var(--fg-muted); margin: 0 0 var(--sp-3); max-width: 58ch; }
.about__text:last-of-type { margin-bottom: 0; }

/* ════════════════════════════════════════════════════════
   PARTNER PAGE (partners.php) - code gate + photo rail
   Only used on the private /partners/{code} pages.

   The .ptr-deal rules that stood here are gone with the 20% band they styled (owner, 2026-09-11:
   the block was ugly). The figure itself moved into ptr_b2_text rather than going with them - it is
   the only commercial argument on that page and appeared nowhere else.
   ════════════════════════════════════════════════════════ */

/* ---- The code gate ---------------------------------------------------------------------------
   A card on the photograph, vertically centred, with the footer pinned to the bottom of the window
   (see body.ptr-locked in layout.css). Before this the section sat at its natural height and the
   footer floated in the middle of the screen with a field of empty brown underneath it. */
.ptr-gate { justify-content: center; align-items: center; min-height: 100%; }
.ptr-gate__card {
  width: 100%;
  max-width: 27rem;
  padding: var(--sp-5) var(--sp-4);
  text-align: center;
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  /* Opaque enough to lift the card off the photograph, or the type competes with the furniture
     behind it and the boxes stop reading as boxes. */
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(6px);
  box-shadow: 0 26px 60px -30px rgba(0,0,0,0.8);
}
.ptr-gate__title { font-size: clamp(26px, 3.6vw, 38px); margin: 0 0 var(--sp-3); }
.ptr-gate__help  { margin: 0 auto var(--sp-4); max-width: 32ch; color: var(--fg-muted); font-size: 14px; }
.ptr-gate__btn   { margin-top: var(--sp-4); }
.ptr-gate__err   { margin: var(--sp-3) 0 0; color: var(--accent); font-size: 13px; }

/* Four boxes, so the number of digits is visible before anything is typed. */
.ptr-pin { display: flex; justify-content: center; gap: 12px; }
.ptr-pin__box {
  width: 56px; height: 66px;
  text-align: center;
  font-family: var(--f-mono);
  font-size: 26px;
  color: var(--fg);
  background: var(--bg-card);
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.ptr-pin__box:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--rule)); }
/* A filled box stays visibly filled once focus has moved on, so you can see how far you are.
   IT DEPENDS ON placeholder=" " ON THE INPUT: :placeholder-shown matches nothing at all when there
   is no placeholder attribute, so :not() matched every box from page load and all four wore the
   filled border before a digit had been typed - the one thing this rule exists to say. */
.ptr-pin__box:not(:placeholder-shown) { border-color: color-mix(in srgb, var(--accent) 55%, var(--rule)); }
/* Declared AFTER the filled rule, because the two have equal specificity and source order is then
   the whole of it: the box you are in has to win over the boxes you have already filled. The
   outline is stated rather than removed - `outline: none` sat on the base rule and beat the site's
   own :focus-visible ring in base.css, so a keyboard had nothing to follow round the four boxes. */
.ptr-pin__box:focus { border-color: var(--accent); background: var(--bg); }
.ptr-pin__box:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 420px) {
  .ptr-pin { gap: 8px; }
  .ptr-pin__box { width: 48px; height: 58px; font-size: 22px; }
}

/* The photo rail. Native overflow scrolling with snap points, so a phone behaves the way it already
   expects and this needs no library; the buttons exist only because a desktop with hidden
   scrollbars gives no sign that a strip moves at all. */
.ptr-rail-wrap { position: relative; }
.ptr-rail {
  display: flex; gap: 18px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* Bleed to the section edge so a half-visible next frame says "there is more" without a caption
     having to. */
  padding-bottom: 6px;
  /* NO SCROLLBAR. A grey system bar under a row of photographs is the one piece of browser chrome
     on this page, and it reads as a control the visitor is supposed to operate. The strip advances
     by itself, drags with a finger and has two arrows on desktop, so nothing is lost by hiding it. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.ptr-rail::-webkit-scrollbar { display: none; }
.ptr-rail:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.ptr-rail__item {
  /* The ceiling here is coupled to the size the strip's photographs are generated at: see $RAIL in
     .claude/make-partner-photos.ps1, which must stay comfortably ahead of it or the frames go soft
     on a dense screen. Raising this without regenerating them is the failure that looks like
     nothing. (Briefly doubled to 1120px on 2026-09-11 and put back.) */
  flex: 0 0 clamp(260px, 46%, 560px);
  margin: 0;
  scroll-snap-align: start;
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  overflow: hidden;
  background: var(--bg-card);
}
.ptr-rail__item img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  transition: transform 600ms var(--ease);
}
.ptr-rail__item:hover img { transform: scale(1.03); }

/* The whole frame is the control. A real <button> so it is tabbable and works from a keyboard, with
   the browser's own button chrome removed - the picture is the affordance. */
.ptr-rail__open {
  display: block; width: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in; border-radius: inherit; overflow: hidden;
}
.ptr-rail__open:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ── The photograph overlay ────────────────────────────────────────────────────────────────────
   The rail carries a small copy of each photograph; this is where the large one is shown, and it is
   fetched only when something is actually clicked. */
.ptr-lightbox {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(16px, 4vw, 56px);
  background: color-mix(in srgb, var(--bg) 92%, black);
}
.ptr-lightbox[hidden] { display: none; }
.ptr-lightbox__stage {
  position: relative; display: flex; align-items: center; justify-content: center;
  max-width: 100%; max-height: 100%;
}
.ptr-lightbox__img {
  display: block; max-width: 100%; max-height: 86vh;
  object-fit: contain; border: 1px solid var(--rule);
  opacity: 1; transition: opacity .2s ease;
}
/* While it loads the frame is empty rather than half-painted, and the spinner is the only thing in
   it. A large photograph on a phone is a real wait, and a blank rectangle reads as a broken page. */
.ptr-lightbox.is-loading .ptr-lightbox__img { opacity: 0; }
.ptr-lightbox__spin {
  position: absolute; width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--rule); border-top-color: var(--accent);
  animation: ptr-spin .7s linear infinite;
  opacity: 0; pointer-events: none; transition: opacity .15s ease;
}
.ptr-lightbox.is-loading .ptr-lightbox__spin { opacity: 1; }
@keyframes ptr-spin { to { transform: rotate(360deg); } }

.ptr-lightbox__close {
  position: absolute; top: clamp(10px, 2vw, 24px); right: clamp(10px, 2vw, 24px);
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 26px; line-height: 1; color: var(--fg);
  background: var(--bg-2); border: 1px solid var(--rule); cursor: pointer;
}
.ptr-lightbox__close:hover { border-color: var(--accent); color: var(--accent); }
.ptr-lightbox__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The page behind must not scroll under the overlay, which on a phone is what makes a tap to close
   land somewhere unexpected.

   IT HAS TO BE ON <html> AS WELL, and on body alone it locked nothing. The viewport takes its
   overflow from the root element and only falls through to body while the root's own is visible -
   and base.css sets overflow-x: hidden on html for the whole site. So this rule clipped nothing
   (body's height is auto) and the document carried on scrolling under the picture. partners.php puts
   the class on both elements for that reason. */
html.ptr-noscroll,
body.ptr-noscroll { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  /* The spinner keeps turning: it is the only sign that a large file is on its way, and a still
     circle would say the opposite of what is happening. Only the fade goes. */
  .ptr-lightbox__img { transition: none; }
}

.ptr-rail__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 50%;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border: 1px solid var(--rule);
  color: var(--fg); font-size: 22px; line-height: 1;
  cursor: pointer; z-index: 2;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.ptr-rail__nav:hover { border-color: var(--accent); background: var(--bg); }
.ptr-rail__nav--prev { left: -10px; }
.ptr-rail__nav--next { right: -10px; }
/* Hidden when there is nothing to scroll to, set by the page script - a control that does nothing
   teaches people to stop trusting the ones that do. */
.ptr-rail__nav[hidden] { display: none; }
/* The hint is gone with the scrollbar: the strip moves on its own now, so telling somebody it can be
   dragged is explaining something they have already been shown. */
.ptr-rail__hint { display: none; }

/* Touch devices scroll the rail with a finger, so the buttons are dead weight and the hint is
   telling somebody what they already know how to do. */
@media (hover: none) {
  .ptr-rail__nav { display: none; }
}
@media (max-width: 640px) {
  .ptr-rail__item { flex-basis: 82%; }
}
@media (prefers-reduced-motion: reduce) {
  .ptr-rail { scroll-behavior: auto; }
  .ptr-rail__item img { transition: none; }
  .ptr-rail__item:hover img { transform: none; }
}

/* ════════════════════════════════════════════════════════
   FAQ - native <details> accordion
   ════════════════════════════════════════════════════════ */
.faq { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5) var(--sp-4); }
.faq__group { display: flex; flex-direction: column; }
.faq__group-title {
  font-family: var(--f-mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--accent); padding-bottom: 14px; margin-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}
.faq__item { border-bottom: 1px solid var(--rule-soft); }
.faq__q {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 16px 0;
  font-family: var(--f-sans); font-weight: 400; font-size: 14px; color: var(--fg);
  transition: color var(--dur) var(--ease);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: "+"; flex-shrink: 0;
  font-family: var(--f-display); font-size: 22px; font-weight: 300; line-height: 1; color: var(--accent);
  transition: transform var(--dur) var(--ease);
}
.faq__item[open] .faq__q { color: var(--accent); }
.faq__item[open] .faq__q::after { transform: rotate(45deg); }
.faq__a { padding: 0 0 18px; font-size: 13px; line-height: 1.7; color: var(--fg-muted); max-width: 56ch; }
.faq__steps { margin: 10px 0 10px; padding-left: 1.4em; display: flex; flex-direction: column; gap: 4px; }
.faq__steps li { padding-left: 4px; }
.faq__steps li::marker { color: var(--accent); font-family: var(--f-mono); font-size: 11px; }

/* ── Closing CTA ── */
.sec--cta { align-items: center; text-align: center; }
.cta-block { max-width: 760px; }

/* ════════════════════════════════════════════════════════
   MOTION - scroll reveal + configurator magnet
   ════════════════════════════════════════════════════════ */
/* Hidden state only applies when JS is present (progressive enhancement):
   without JS, the .js class is never added and content stays visible. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.js [data-reveal].is-revealed { opacity: 1; transform: none; }

/* Configurator magnet - soft brass breathing glow on the key CTA */
@keyframes ceMagnet {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  50%      { box-shadow: 0 0 28px 2px color-mix(in srgb, var(--accent) 32%, transparent); }
}
.ce-btn--magnet { animation: ceMagnet 3.4s var(--ease) infinite; }
.ce-btn--magnet:hover { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .ce-btn--magnet { animation: none; }
}

/* ── Responsive ── */
@media (max-width: 960px) {
  .project-grid    { grid-template-columns: repeat(2, 1fr); }
  .project-grid--proof { grid-template-columns: repeat(2, 1fr); }
  .stat-row        { grid-template-columns: repeat(2, 1fr); }
  .contact-layout  { grid-template-columns: 1fr; gap: 48px; }
  .design-teaser   { grid-template-columns: 1fr; gap: var(--sp-4); }
  .steps           { grid-template-columns: repeat(2, 1fr); }
  .feature-grid    { grid-template-columns: 1fr; }
  .faq             { grid-template-columns: 1fr; gap: var(--sp-4); }
  .about           { grid-template-columns: 1fr; gap: var(--sp-4); }
  /* Photo above the copy once they stack: a section that opens on a face is warmer than one that
     opens on a paragraph, and the heading is already above both. `order` rather than a second copy
     of the markup - see about.php on why the source order is text-first.
     Smaller here than the desktop cap, or the circle is most of a phone screen before a word of it
     has been read. */
  .about__media    { order: -1; max-width: 260px; }
}
@media (max-width: 540px) {
  .project-grid    { grid-template-columns: 1fr; }
  .project-grid--proof { grid-template-columns: 1fr; }
  .stat-row        { grid-template-columns: 1fr 1fr; }
  .steps           { grid-template-columns: 1fr; }
  .swatch-cloud    { gap: 12px; }
}
