/* Incredible Creations — public site.
   Converted from "Incredible Creations.dc.html", which stays in the repo as the
   design source of truth. Values here are lifted from it directly so the two
   don't drift: the four palettes, the same spacing rhythm.

   TYPEFACES are the one deliberate departure: the design file's Newsreader/Karla
   pairing was replaced by Merriweather/Lato per the brand type guide. Sizes,
   weights and letter-spacing are unchanged from the design — only the families
   moved. Merriweather sets noticeably larger than Newsreader at the same px, so
   headings read bigger than they did without a single size having changed. */

/* Timber — the original palette, and the fallback set. */
:root {
  --paper: #F7F3ED;
  --paper2: #EFE8DE;
  --paperGlass: rgba(247, 243, 237, 0.92);
  --ink: #221F1B;
  --ink2: #4A433B;
  --ink3: #6B6259;
  --ink4: #8A7F72;
  --accent: #A9642B;
  --accentText: #FFF8F0;
  --input: #FFFDF9;
  --sel: #E3D3BF;
  --dark: #221F1B;
  --darkText: #F7F3ED;
  --darkAccent: #C9905C;
  --darkMuted: #A99B8B;
  --chip: #221F1B;
  /* The hand-drawn underline under the gallery footer line, in this palette’s accent. */
  --underline: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 12' preserveAspectRatio='none'%3E%3Cpath d='M2 7.6C24 4.9 41 9.6 63 7.1 92 3.9 122 8.4 198 6.2' fill='none' stroke='%23A9642B' stroke-width='2' stroke-linecap='round' opacity='.9'/%3E%3Cpath d='M7 10.4C35 8.3 63 11.1 97 9.4 141 7.3 171 10.2 193 8.7' fill='none' stroke='%23A9642B' stroke-width='1.1' stroke-linecap='round' opacity='.45'/%3E%3C/svg%3E");
  --hair: rgba(34, 31, 27, 0.14);
  --hair2: rgba(34, 31, 27, 0.08);
}

body[data-palette="garden"] {
  --paper: #F4F5EF; --paper2: #E4E9DC; --paperGlass: rgba(244,245,239,0.92);
  --ink: #1E241C; --ink2: #414A3C; --ink3: #5D6656; --ink4: #828C78;
  --accent: #4F7A3F; --accentText: #F6FBF1; --input: #FCFDF8; --sel: #D6E3C6;
  --dark: #1E2A1C; --darkText: #F4F5EF; --darkAccent: #A3C486; --darkMuted: #97A38E;
  --chip: #1E241C; --hair: rgba(30,36,28,0.14); --hair2: rgba(30,36,28,0.08);
  /* The hand-drawn underline under the gallery footer line, in this palette’s accent. */
  --underline: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 12' preserveAspectRatio='none'%3E%3Cpath d='M2 7.6C24 4.9 41 9.6 63 7.1 92 3.9 122 8.4 198 6.2' fill='none' stroke='%234F7A3F' stroke-width='2' stroke-linecap='round' opacity='.9'/%3E%3Cpath d='M7 10.4C35 8.3 63 11.1 97 9.4 141 7.3 171 10.2 193 8.7' fill='none' stroke='%234F7A3F' stroke-width='1.1' stroke-linecap='round' opacity='.45'/%3E%3C/svg%3E");
}
body[data-palette="coastal"] {
  --paper: #F2F4F7; --paper2: #E2E8EF; --paperGlass: rgba(242,244,247,0.92);
  --ink: #1B2129; --ink2: #3C4652; --ink3: #58636F; --ink4: #7C8794;
  --accent: #2F6690; --accentText: #F2F8FC; --input: #FBFCFE; --sel: #C9DBEA;
  --dark: #1A222B; --darkText: #F2F4F7; --darkAccent: #86B6D9; --darkMuted: #8E9AA6;
  --chip: #1B2129; --hair: rgba(27,33,41,0.14); --hair2: rgba(27,33,41,0.08);
  /* The hand-drawn underline under the gallery footer line, in this palette’s accent. */
  --underline: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 12' preserveAspectRatio='none'%3E%3Cpath d='M2 7.6C24 4.9 41 9.6 63 7.1 92 3.9 122 8.4 198 6.2' fill='none' stroke='%232F6690' stroke-width='2' stroke-linecap='round' opacity='.9'/%3E%3Cpath d='M7 10.4C35 8.3 63 11.1 97 9.4 141 7.3 171 10.2 193 8.7' fill='none' stroke='%232F6690' stroke-width='1.1' stroke-linecap='round' opacity='.45'/%3E%3C/svg%3E");
}
body[data-palette="playground"] {
  --paper: #FFFCF4; --paper2: #FFF0DA; --paperGlass: rgba(255,252,244,0.93);
  --ink: #2A2118; --ink2: #4E4133; --ink3: #6B5C4A; --ink4: #9A8874;
  --accent: #D2622A; --accentText: #FFFDF7; --input: #FFFFFF; --sel: #FFDFA8;
  --dark: #2B4A3F; --darkText: #FBF8EF; --darkAccent: #F2B23C; --darkMuted: #A8BDB2;
  --chip: #D2622A; --hair: rgba(42,33,24,0.14); --hair2: rgba(42,33,24,0.08);
  /* The hand-drawn underline under the gallery footer line, in this palette’s accent. */
  --underline: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 12' preserveAspectRatio='none'%3E%3Cpath d='M2 7.6C24 4.9 41 9.6 63 7.1 92 3.9 122 8.4 198 6.2' fill='none' stroke='%23D2622A' stroke-width='2' stroke-linecap='round' opacity='.9'/%3E%3Cpath d='M7 10.4C35 8.3 63 11.1 97 9.4 141 7.3 171 10.2 193 8.7' fill='none' stroke='%23D2622A' stroke-width='1.1' stroke-linecap='round' opacity='.45'/%3E%3C/svg%3E");
}

/* One fluid container width for every section, so the page fills the screen it
   is actually on instead of sitting in a fixed 1280px column with dead margin
   either side of a wide display.

   94vw keeps a breathing margin on small and mid screens. The ceiling is 1440
   rather than something larger because the hero is a composed two-column layout,
   not a fluid grid: past about this width the text and the photo drift apart and
   the hero reads sparse. Wider filled more of a 4K screen and looked worse for
   it. Prose is safe either way — the lede, gallery copy and card text all carry
   their own ch-based measures, so this grows the images and the gaps, not the
   line lengths. */
:root { --shell: min(1440px, 94vw); }

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  overflow-x: hidden;
  background: var(--paper);
  color: var(--ink);
  font-family: Lato, system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--ink); }
input, textarea, button { font: inherit; color: inherit; }
::selection { background: var(--sel); }
img { max-width: 100%; }

h1, h2, h3 { font-family: Merriweather, Georgia, serif; font-weight: 300; margin: 0; text-wrap: pretty; }

.shell { max-width: var(--shell); margin: 0 auto; padding: 0 32px; }
.eyebrow {
  font-size: 10.5px; letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--accent);
}

@keyframes icFade { from { opacity: 0 } to { opacity: 1 } }
@keyframes icMarquee { from { transform: translate3d(0,0,0) } to { transform: translate3d(-50%,0,0) } }
@keyframes icWave {
  0%   { transform: translateY(30px)  rotate(-3.4deg) scale(0.90); }
  22%  { transform: translateY(-4px)  rotate(-1.2deg) scale(1.00); }
  50%  { transform: translateY(-30px) rotate(2.6deg)  scale(1.09); }
  78%  { transform: translateY(-2px)  rotate(1.2deg)  scale(1.00); }
  100% { transform: translateY(30px)  rotate(-3.4deg) scale(0.90); }
}

/* ---- Header ---- */

/* The logo's strapline is 324px of the header on its own — wider than the whole
   nav's link text. With the links no longer allowed to shrink (see nowrap
   below), that strapline is what pushes the WhatsApp pill off the right edge on
   anything under about 1340px. It is decorative, the same words are in the
   hero, and it was already hidden on phones; it now goes at the width where it
   stops being affordable rather than at an arbitrary phone breakpoint.
   1340 measured, not chosen: the row needs 1229px plus 64px of padding. */
@media (max-width: 1340px) {
  header.site .logo span { display: none; }
}

header.site {
  position: sticky; top: 0; z-index: 60;
  background: var(--paperGlass);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--hair);
}
header.site .inner {
  /* Padding grown with the logo so the bar stays proportioned rather than the
     mark crowding its own edges. */
  max-width: var(--shell); margin: 0 auto; padding: 14px 32px;
  display: flex; align-items: center; gap: 32px;
}
header.site .logo { display: flex; align-items: center; gap: 18px; flex: none; }
/* Height comes from --logo-h on <body>, set from Settings. 49px is the value
   the header padding and the hero's fold calculation were built around, so it
   stays as the fallback for a page rendered without the variable. */
header.site .logo img { height: var(--logo-h, 49px); width: auto; display: block; }
header.site .logo span {
  font-size: 9.5px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink4); border-left: 1px solid var(--hair);
  padding-left: 16px; line-height: 1; white-space: nowrap;
}
header.site nav { margin-left: auto; display: flex; align-items: center; gap: 28px; }
header.site .navlinks { display: contents; }

/* The hamburger. display:none here and turned on at the phone breakpoint, so
   the desktop bar is untouched. Three bars drawn in CSS rather than an SVG —
   it is three rectangles, and an icon file is a request. */
.navtoggle {
  display: none;
  /* 44px, and flex:none so the nav's flexbox cannot shrink it. Left to itself
     it settled at 35px wide — still above the WCAG minimum, but under the 44px
     both Apple and the WCAG AAA target size ask for, and this is the one
     control a phone visitor has to hit to navigate at all. */
  flex: none;
  width: 44px; height: 44px; padding: 0;
  background: none; border: 1px solid var(--hair); border-radius: 999px;
  cursor: pointer; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.navtoggle span {
  display: block; width: 16px; height: 1.5px; background: var(--ink2); border-radius: 2px;
  transition: transform 180ms ease, opacity 140ms ease;
}
.navtoggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.navtoggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.navtoggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
.navtoggle:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--sel); }
/* :not(.wa-btn) matters. The WhatsApp pill is a nav child, so a bare
   `header.site nav a` outscores `.wa-btn` on specificity and paints dark ink
   text onto the dark pill — 1.6:1 contrast, effectively invisible. Excluding it
   here lets .wa-btn own its own colour and size. */
/* nowrap because these are flex items with the default flex-shrink, and the
   logo beside them does not shrink. On anything narrower than about 1400px they
   were being squeezed until each one wrapped onto two, three, even four lines —
   "Who we make for" came out 34px wide at 1167px. Measured against the live
   site at the same width to be sure it was old rather than newly introduced. */
header.site nav a:not(.wa-btn) {
  font-size: 13.5px; letter-spacing: 0.04em; color: var(--ink2); white-space: nowrap;
}
header.site nav a:not(.wa-btn):hover { color: var(--ink); }

.wa-btn {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 16px; border-radius: 999px;
  background: var(--dark); color: var(--darkText);
  font-size: 13px; letter-spacing: 0.03em;
}
.wa-btn:hover { background: var(--accent); color: #fff; }
.wa-dot { width: 7px; height: 7px; border-radius: 50%; background: #8FBF6A; display: block; }

/* The strapline is the first thing to go when the header runs out of room.
   It was only hidden at 760px, but the bar actually stops fitting at about
   1060px: logo block 508 + nav 394 + gap 32 + padding 64 = 998px of content in
   a 94vw container. Below that the nav overflowed the header and the page grew
   a horizontal scrollbar.

   This was already true of the shorter strapline — it overflowed by 10px at
   900px — so the bug predates the Johannesburg wording. The longer line just
   made a 10px overlap into a 120px one, which is what made it visible.

   The logo and the navigation both have to be there; the strapline is the one
   part that can go without costing anything. */
@media (max-width: 1080px) {
  header.site .logo span { display: none; }
}

/* ---- Hero ---- */

.hero {
  max-width: var(--shell); margin: 0 auto; padding: 44px 32px 0;
  display: grid; grid-template-columns: 1.02fr 1fr; gap: 64px; align-items: end;
}
.hero .copy { padding-bottom: 16px; }
.hero .eyebrow { margin-bottom: 16px; }
.hero h1 {
  font-size: clamp(36px, 3.4vw, 56px); line-height: 1.04;
  letter-spacing: -0.02em; margin: 0 0 18px;
}
.hero .segs { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 0 0 18px; }
.hero .segs span { font-size: 14px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink3); }
.hero .segs i { color: var(--accent); font-style: normal; font-size: 14px; }
.hero p.lede { font-size: 16px; line-height: 1.58; color: var(--ink2); max-width: 46ch; margin: 0 0 24px; }
.hero .ctas { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 26px; }
/* Wraps rather than overflowing: four stats in a fixed-width column will not
   always fit on one line, and a wrapped row reads better than a squeezed one. */
.hero .stats {
  display: flex; flex-wrap: wrap; column-gap: 30px; row-gap: 16px;
  padding-top: 18px; border-top: 1px solid var(--hair);
}
.hero .stats b { font-family: Merriweather, Georgia, serif; font-size: 25px; font-weight: 300; line-height: 1; display: block; }
.hero .stats i {
  font-style: normal; font-size: 12px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink4); margin-top: 5px; display: block;
}
/* The design's 4/5 portrait, at the size and position it was always meant to be:
   right-hand side of the hero, roughly 570px wide.
   max-width holds it at that size now the page container is fluid — without it
   a wider container simply inflated the photo, and an earlier attempt to cap it
   by height instead fought the ratio and left dead space beside it. Capping the
   WIDTH lets the ratio do its job untouched. */
.hero .shot {
  position: relative; aspect-ratio: 4/5;
  width: 100%; justify-self: end;
  /* 570px is the design's size and the ceiling. The second term keeps the photo
     inside a SHORT viewport without touching its shape or position: at 4/5 a
     570px-wide photo is 713px tall, which does not fit a 768px laptop, so on
     those screens the width steps down and the ratio carries the height with it.
     168px is the header (78 since the logo grew), the hero's top padding and a
     little breathing room. Grow this if the header ever gets taller again, or
     the hero starts overflowing the fold on short screens. */
  max-width: min(570px, calc((100vh - 168px) * 0.78));
}

.btn {
  display: inline-block; padding: 13px 26px; border-radius: 999px;
  font-size: 14px; letter-spacing: 0.03em; cursor: pointer; border: 1px solid transparent;
  background: var(--accent); color: var(--accentText);
}
.btn:hover { background: var(--ink); color: var(--accentText); }
.btn.ghost { background: transparent; border-color: var(--hair); color: var(--ink); }
.btn.ghost:hover { border-color: var(--ink); background: var(--paper2); color: var(--ink); }

/* Image slots. A slot with no image shows a quiet placeholder rather than a
   broken-image icon or a collapsed box.

   .slot carries SIZING (fill the parent) and is for slots whose parent already
   has a fixed shape — the hero wrapper, the who-we-make-for cards, marquee
   cards. Do NOT put it on anything that sizes itself from aspect-ratio: the
   height:100% wins over the ratio, and in a grid the parent has already been
   stretched to the row height, so the element swallows the row and any sibling
   caption is pushed outside it onto the next row. Use .ph for that case — it is
   APPEARANCE ONLY. */
/* No background when the slot holds an image. That grey is the EMPTY-state
   colour; painting it underneath meant a PNG with transparency showed grey
   through its see-through areas instead of the page. */
.slot { position: relative; width: 100%; height: 100%; border-radius: 4px; overflow: hidden; }
.slot.empty { background: var(--paper2); }
.slot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.slot.empty::after, .ph::after {
  content: attr(data-placeholder);
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  padding: 24px; text-align: center; font-size: 12.5px; line-height: 1.5;
  color: var(--ink4); border: 1px dashed var(--hair);
  border-radius: 4px;
}

/* ---- Hero transition layers ----
   Shared with the admin's Settings preview via public/hero-fx.js, so what is
   chosen there is exactly what renders here. isolation:isolate keeps the shard
   drop-shadows from bleeding onto the page during a tear. */
.slot[data-slot="hero"] { isolation: isolate; }
.ic-hero-fx-layer, .ic-hero-fx-shard { position: absolute; inset: 0; }
.ic-hero-fx-layer img, .ic-hero-fx-shard img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.ic-hero-fx-shard { will-change: transform, opacity; }

/* ---- Marquee ---- */

.marq {
  margin-top: 56px; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.marq .head {
  max-width: var(--shell); margin: 0 auto; padding: 0 32px 30px;
  display: flex; align-items: baseline; gap: 16px;
}
.marq .head hr { flex: 1; height: 1px; background: var(--hair); border: none; margin: 0; }
.marq .head .note { font-size: 12px; color: var(--ink4); }
.marq .rail { height: 330px; display: flex; align-items: center; }
/* --marq-secs and --marq-dir are set on the section by the renderer from the
   Settings values; the fallbacks here are the design file's originals and are
   what an install with nothing stored gets.

   Longhand rather than the `animation` shorthand on purpose: a var() inside a
   shorthand that fails to resolve takes the WHOLE declaration with it, so one
   bad custom property would stop the strip entirely rather than just ignoring
   the direction. */
.marq .track {
  display: flex; width: max-content; gap: 26px; padding: 0 13px;
  animation-name: icMarquee;
  animation-duration: var(--marq-secs, 46s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-direction: var(--marq-dir, normal);
}
.marq .card {
  flex: none; height: 235px; position: relative; will-change: transform;
  animation-name: icWave;
  animation-duration: var(--marq-secs, 46s);
  animation-timing-function: cubic-bezier(.45,.05,.55,.95);
  animation-iteration-count: infinite;
}
/* :not([...="0"]) rather than [...="1"], so a page rendered before this setting
   existed — a cached HTML, or the template opened directly — still pauses on
   hover. Absence means "as it always was", not "off". */
.marq:not([data-pause-hover="0"]):hover .track,
.marq:not([data-pause-hover="0"]):hover .card { animation-play-state: paused; }
/* The header note promises "hover to pause". With pausing off it would be a
   lie, so it goes away with the behaviour it describes. */
.marq[data-pause-hover="0"] .head .note { display: none; }

/* ---- Gallery ---- */

/* Bottom padding added so the divider on .process below is not flush against
   the last line of the gallery footer — the section had none, because until
   now the next section's own padding was the only gap. */
.gallery { max-width: var(--shell); margin: 0 auto; padding: 76px 32px; }
.gallery .head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 32px; border-bottom: 1px solid var(--hair); padding-bottom: 26px;
}
.gallery .head h2 { font-size: clamp(28px, 2.6vw, 38px); line-height: 1.06; letter-spacing: -0.015em; max-width: 22ch; }
.gallery .head .eyebrow { margin-bottom: 16px; }
/* This paragraph is the one place the page says the pieces are made to order
   rather than picked from a catalogue, and it ends with the instruction that
   makes the gallery work ("tap any photo to enquire"). At 15px in the lightest
   ink, sitting alone at the far right of a wide header, it read as a footnote.
   Raised to the type guide's Introductory Paragraph — 21px, Lato 300 — and
   darkened a step, with an accent rule to anchor it to something instead of
   floating in the white space. */
.gallery .head p {
  font-size: 21px; line-height: 1.6; font-weight: 300;
  color: var(--ink2); max-width: 36ch; margin: 0;
  border-left: 2px solid var(--accent); padding-left: 20px;
}

.chips { display: flex; flex-wrap: wrap; gap: 9px; padding: 26px 0 30px; }
.chips button {
  padding: 9px 18px; border-radius: 999px; border: 1px solid var(--hair);
  background: transparent; color: var(--ink2); font-size: 13px;
  letter-spacing: 0.03em; cursor: pointer; transition: background 140ms ease;
}
.chips button:hover { background: var(--paper2); }
.chips button.on { background: var(--chip); border-color: var(--chip); color: var(--paper); }
.chips button .n { font-size: 11.5px; margin-left: 8px; opacity: 0.6; }

/* align-items:start so a tile is never stretched to the row height. Stretched
   tiles are what let a percentage-height child override its aspect-ratio, and
   they also leave short tiles with dead space under the caption. */
/* Masonry, done with row spans rather than CSS columns.
   grid-template-rows: masonry is still not shipping in a browser you can rely
   on, and CSS columns would cost the per-tile WIDTH control (a column child
   cannot span two columns) and turn reading order into down-one-column-then-
   the-next. Spanning a fine row unit keeps the shapes, the widths, the focal
   points and the left-to-right order.

   4px rows with a 4px gap, so a tile spanning N rows is exactly N*8px minus the
   trailing gap — the arithmetic in site.js relies on those two numbers matching.
   The column gap stays the visual 20px; only the ROW gap is the fine unit.

   Without JS the spans are never set, every tile occupies one 4px row and they
   would overlap — so the default here is a normal grid, and site.js switches the
   .masonry class on. A visitor with no JavaScript keeps the tidy old layout. */
.tiles {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px; align-items: start;
}
.tiles.masonry {
  grid-auto-rows: 4px;
  row-gap: 4px;
  column-gap: 20px;
  align-items: stretch;
}
.tile { margin: 0; cursor: pointer; animation: icFade 260ms ease both; }
/* Tiles past the first page are in the markup so a crawler can read them, and
   hidden so a reader gets the "Show more" gallery they were promised. Spelled
   out rather than left to the browser default, because any `display` on .tile
   would beat [hidden] at equal specificity and quietly show all of them. */
.tile[hidden] { display: none; }
.tile .shot { position: relative; border-radius: 4px; overflow: hidden; }
.tile .shot.ph { background: var(--paper2); }
/* Piece tiles render .shot WITHOUT the .slot class (the placeholder styling is
   only wanted when there is no photo), so they need their own fill rule —
   otherwise the image sits at natural size in the corner of its box. */
.tile .shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile figcaption {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding: 13px 2px 0;
}
.tile .t { font-family: Merriweather, Georgia, serif; font-size: 18px; font-weight: 400; line-height: 1.25; }
.tile .p { font-size: 12.5px; letter-spacing: 0.02em; color: var(--ink4); white-space: nowrap; }
.tile .c {
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent); padding: 5px 2px 0;
}
/* --ink2, not --ink3. The highlighter lightens the background under this text,
   which took --ink3 down to 3.4:1 and through the floor of WCAG AA. A mark that
   makes a sentence harder to read is worse than no mark. */
.gallery .foot { text-align: center; font-size: 14.5px; color: var(--ink2); padding-top: 46px; margin: 0; }

/* "Show more". Outside the masonry grid, because a full-width child of it would
   be handed a row span and a computed height along with the photographs. */
.more-wrap { display: flex; justify-content: center; padding-top: 34px; }
.more-wrap[hidden] { display: none; }

/* A hand-drawn underline, not a highlighter. Two strokes at slightly different
   heights and weights, the way someone underlining by hand goes over it twice
   and does not quite retrace the first pass.

   Still a BACKGROUND on the inline element rather than a pseudo-element: a
   pseudo-element would need absolute positioning and would then be one
   rectangle, which breaks the moment the sentence wraps. A background is
   painted once per line fragment for free, so a wrapped sentence gets a stroke
   under each line.

   --underline is per palette. The highlighter this replaces had the coastal
   blue baked into it as a literal rgba(), so it stayed blue on the timber,
   garden and playground palettes. Each SVG now carries its own accent.

   preserveAspectRatio='none' lets the line stretch to the width of the text.
   The stroke keeps its weight while it stretches because the path is nearly
   horizontal, and a stroke's thickness there runs vertically — the axis that is
   not being scaled. */
.gallery .foot .hl {
  background-image: var(--underline);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 0.5em;
  padding: 0 3px 0.3em;
  animation: icUnderline 11s cubic-bezier(.55, .05, .35, 1) infinite;
}
/* Draw, hold, lift, breathe — the rhythm the highlighter had, and the hold is
   still by far the longest phase. The point is the sentence being read, not the
   stroke arriving. 104% so the tail overshoots the last character, the way a
   hand does. */
@keyframes icUnderline {
  0%   { background-size: 0% 0.5em; }
  22%  { background-size: 104% 0.5em; }
  68%  { background-size: 104% 0.5em; }
  82%  { background-size: 0% 0.5em; }
  100% { background-size: 0% 0.5em; }
}

/* ---- Who ---- */

.who {
  margin-top: 84px; background: var(--paper2);
  border-top: 1px solid var(--hair2); border-bottom: 1px solid var(--hair2);
}
.who .inner { max-width: var(--shell); margin: 0 auto; padding: 68px 32px; }
.who h2 {
  font-size: clamp(28px, 2.6vw, 38px); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0 0 40px; max-width: 26ch;
}
.who .eyebrow { margin-bottom: 16px; }
.who .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; }
.who .cards .shot { position: relative; aspect-ratio: 4/5; margin-bottom: 18px; }
.who .cards h3 { font-weight: 400; font-size: 22px; margin: 0 0 9px; }
.who .cards p { font-size: 14.5px; line-height: 1.6; color: var(--ink2); margin: 0; }

.safety {
  margin-top: 76px; padding-top: 44px; border-top: 1px solid var(--hair);
  display: grid; grid-template-columns: 0.9fr 1.1fr; gap: 56px; align-items: start;
}
.safety h3 { font-size: clamp(26px, 2.4vw, 34px); line-height: 1.14; margin: 0; }
.safety .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 30px; }
.safety .cols .lbl {
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 10px;
}
.safety .cols p { font-size: 14.5px; line-height: 1.6; color: var(--ink2); margin: 0; }

/* ---- Process ---- */

/* A rule across the content width, marking where the gallery ends and this
   section begins. 1px --hair rather than the 2px the step rules just below
   use: a divider that matched them would compete with the row it is meant to
   introduce, and the moving highlight would look like one of four dividers
   rather than a marker walking along them. */
.process {
  max-width: var(--shell); margin: 0 auto; padding: 76px 32px;
  border-top: 1px solid var(--hair);
}
.process h2 {
  font-size: clamp(28px, 2.6vw, 38px); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0 0 44px; max-width: 24ch;
}
.process .eyebrow { margin-bottom: 16px; }
.process .steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 34px; }
/* The four steps walk themselves: the dark rule wipes in over step 1, its
   number fills with an accent disc, then the pair hands over to step 2, and so
   on round the loop. 14s a lap, 3.5s a step, which is slow enough to read as a
   sequence rather than a flicker.

   The rule used to be a static border-top-color on :first-child. That is now
   the ANIMATION's job, so the static rule is gone — otherwise step 1 would stay
   dark while the highlight was three steps away. */
.process .step { border-top: 2px solid var(--hair); padding-top: 20px; position: relative; }

/* The moving rule, laid over the existing hairline rather than replacing it,
   so an inactive step keeps its divider. */
.process .step::before {
  content: ''; position: absolute; left: 0; right: 0; top: -2px; height: 2px;
  background: var(--ink); transform: scaleX(0); transform-origin: left;
  animation: icStepBar 14s linear infinite;
}

.process .step .n {
  font-family: Merriweather, Georgia, serif; font-size: 14px; letter-spacing: 0.18em;
  color: var(--accent); margin-bottom: 14px;
  /* A circle rather than a bare number, so the disc has something to fill. */
  width: 42px; height: 42px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  /* Letter-spacing adds a trailing space INSIDE the inline box, so centring the
     box leaves the digits sitting half a letter-space left of true centre.
     0.09em is that half, put back. */
  text-indent: 0.09em;
  position: relative; z-index: 0;   /* contains the disc below */
  transition: none;
  animation: icStepNum 14s linear infinite;
}
/* The disc. A separate element animating OPACITY rather than the .n's own
   background-color, because background-color would have to interpolate to and
   from `transparent`, and the accent is a per-palette custom property with no
   transparent counterpart to name. */
.process .step .n::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: 50%; background: var(--accent); opacity: 0;
  animation: icStepDisc 14s linear infinite;
}

/* One shared cycle, quarter-turn stagger. Positive delays, not negative: a
   negative delay would start every step part-way through, so on first paint
   three of the four would already be lit. Starting from cold reads as progress
   beginning at step 1, which is what the section is describing. */
.process .step:nth-child(2)::before,
.process .step:nth-child(2) .n,
.process .step:nth-child(2) .n::before { animation-delay: 3.5s; }
.process .step:nth-child(3)::before,
.process .step:nth-child(3) .n,
.process .step:nth-child(3) .n::before { animation-delay: 7s; }
.process .step:nth-child(4)::before,
.process .step:nth-child(4) .n,
.process .step:nth-child(4) .n::before { animation-delay: 10.5s; }

/* Wipes in from the left, holds its quarter, then fades rather than retracting
   — shrinking back would read as undoing the step it just completed. The last
   keyframe restores opacity while the bar is still scaled to nothing, so the
   loop point is invisible. */
@keyframes icStepBar {
  0%   { transform: scaleX(0); opacity: 1; }
  7%   { transform: scaleX(1); opacity: 1; }
  25%  { transform: scaleX(1); opacity: 1; }
  31%  { transform: scaleX(1); opacity: 0; }
  32%  { transform: scaleX(0); opacity: 0; }
  99%  { transform: scaleX(0); opacity: 0; }
  100% { transform: scaleX(0); opacity: 1; }
}
/* --paper, not `transparent`: the digits are painted in the page's own colour
   so the disc reads as having them punched out of it. A real knockout would
   need an SVG mask, and would buy nothing here because the section background
   is flat. If this section ever gains a photo or gradient, this is the line
   that stops being a lie. */
@keyframes icStepNum {
  0%   { color: var(--accent); }
  7%   { color: var(--paper); }
  25%  { color: var(--paper); }
  32%  { color: var(--accent); }
  100% { color: var(--accent); }
}
@keyframes icStepDisc {
  0%   { opacity: 0; }
  7%   { opacity: 1; }
  25%  { opacity: 1; }
  32%  { opacity: 0; }
  100% { opacity: 0; }
}
.process .step h3 { font-weight: 400; font-size: 21px; margin: 0 0 9px; }
.process .step p { font-size: 14.5px; line-height: 1.6; color: var(--ink2); margin: 0; }

/* ---- About us ----
   Sits between "How it works" and the dark testimonials band, and takes the
   tinted band treatment the who-we-make-for section uses — same --paper2, same
   hairline top and bottom — so "this is a new section" is said the way this
   design already says it, rather than with a third shade nobody has seen.

   Two tinted bands on one page is fine here because gallery and process sit
   between them; the earlier worry about them reading as one long stripe was
   about an order the page no longer has.

   The WIDTH moved to .inner so the tint can run edge to edge. A background on
   a max-width element would have drawn a 1440px stripe with paper either side,
   which reads as a mistake rather than a band. */
.about {
  background: var(--paper2);
  border-top: 1px solid var(--hair2); border-bottom: 1px solid var(--hair2);
}
/* 76px top and bottom, matching .process above it. The 8px it had was far too
   tight: the last line of card text sat 8px off the dark testimonials band and
   read as though it had been cut off by it. */
.about .inner { max-width: var(--shell); margin: 0 auto; padding: 76px 32px; }
.about .head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 32px; margin-bottom: 40px;
}
.about .eyebrow { margin-bottom: 16px; }
.about h2 {
  font-size: clamp(28px, 2.6vw, 38px); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0; max-width: 20ch;
}
/* Same standfirst treatment as the gallery's, so the two read as the same kind
   of thing rather than two different ideas of an intro paragraph. */
.about .head .lede {
  font-size: 19px; line-height: 1.6; font-weight: 300;
  color: var(--ink2); max-width: 42ch; margin: 0;
  border-left: 2px solid var(--accent); padding-left: 20px;
}
.about .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 30px; }
/* 4/3 rather than the who-cards' 4/5: a workshop, a bench and a person at work
   are landscape subjects, and a portrait crop of a workbench loses the bench. */
.about .cards .shot { position: relative; aspect-ratio: 4/3; margin-bottom: 18px; }
.about .cards h3 { font-weight: 400; font-size: 22px; margin: 0 0 9px; }
.about .cards p { font-size: 14.5px; line-height: 1.6; color: var(--ink2); margin: 0; }

/* ---- Testimonials ---- */

.says { background: var(--dark); color: var(--darkText); }
.says .inner { max-width: var(--shell); margin: 0 auto; padding: 68px 32px; }
.says .eyebrow { color: var(--darkAccent); margin-bottom: 48px; }
.says .quotes { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 44px; }
.says blockquote { margin: 0; }
.says blockquote p {
  font-family: Merriweather, Georgia, serif; font-weight: 300; font-size: 20px;
  line-height: 1.42; margin: 0 0 22px; text-wrap: pretty;
}
.says blockquote footer { font-size: 13px; letter-spacing: 0.06em; color: var(--darkMuted); }

/* ---- Contact ---- */

.contact {
  max-width: var(--shell); margin: 0 auto; padding: 76px 32px 0;
  display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 72px; align-items: start;
}
.contact .eyebrow { margin-bottom: 16px; }
.contact h2 {
  font-size: clamp(28px, 2.6vw, 38px); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0 0 14px;
}
.contact > div > p { font-size: 16px; line-height: 1.6; color: var(--ink2); max-width: 48ch; margin: 0 0 38px; }
.contact form { display: grid; gap: 20px; }
.contact .two { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.contact label { display: grid; gap: 8px; }
.contact label > span {
  font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink3);
}
.contact input, .contact textarea {
  padding: 13px 15px; border: 1px solid var(--hair); border-radius: 3px;
  background: var(--input); font-size: 15px; width: 100%;
}
.contact textarea { line-height: 1.55; resize: vertical; }
.contact input:focus, .contact textarea:focus { outline: none; border-color: var(--accent); }
.contact .submit { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; padding-top: 4px; }
.contact .submit button { border: none; }
.contact .submit a { font-size: 14px; color: var(--ink3); border-bottom: 1px solid var(--hair); }
.contact .submit a:hover { color: var(--ink); }

aside.chat { background: var(--paper2); border-radius: 4px; padding: 38px 34px; }
aside.chat h3 { font-family: Merriweather, Georgia, serif; font-size: 24px; font-weight: 400; margin-bottom: 22px; }
aside.chat p { font-size: 14.5px; line-height: 1.6; color: var(--ink2); margin: 0 0 26px; }
aside.chat .wa-big {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 15px 24px; border-radius: 999px; background: var(--dark);
  color: var(--darkText); font-size: 14.5px; letter-spacing: 0.03em; margin-bottom: 30px;
}
aside.chat .wa-big:hover { background: var(--accent); color: #fff; }
aside.chat dl { display: grid; gap: 18px; padding-top: 26px; border-top: 1px solid var(--hair); margin: 0; }
/* --ink3, not --ink4. At 11px these labels need 4.5:1 to meet WCAG AA and
   --ink4 gives between 2.84 and 3.22 depending on the palette — grey on grey,
   and the worst contrast on the page. --ink3 lands between 4.85 and 5.75 on all
   four palettes while still reading as a quiet label beside the value below it.
   Measured, not guessed: every palette was checked, because the ink and paper
   both move together and a fix that only works on the one you can see is not a
   fix. */
aside.chat dt {
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink3); margin-bottom: 5px;
}
aside.chat dd { margin: 0; font-size: 14.5px; color: var(--ink2); }

/* ---- Footer ---- */

footer.site { max-width: var(--shell); margin: 0 auto; padding: 64px 32px 36px; }
footer.site .inner {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 28px; padding-top: 34px; border-top: 1px solid var(--hair);
}
footer.site img { height: 50px; width: auto; display: block; margin-bottom: 16px; }
footer.site .tag { font-size: 12.5px; color: var(--ink4); }
footer.site .links { display: flex; gap: 24px; }
footer.site .links a { font-size: 13px; color: var(--ink3); }
footer.site .links a:hover { color: var(--ink); }
/* A button, because it opens a dialog rather than going anywhere — but it has
   to sit in a row of links without looking like the odd one out. */
footer.site .links .linkish {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--ink3);
}
footer.site .links .linkish:hover { color: var(--ink); }

/* The honeypot. Off-screen rather than display:none — some bots skip fields
   that are display:none or type=hidden, and the whole point is that this one
   looks ordinary to a script and is unreachable for a person. Paired with
   aria-hidden and tabindex="-1" in the markup, which is what keeps it away from
   screen readers and the keyboard. */
.hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

/* ---- Cookie consent ---- */
/* Bottom-left, above the fold's worth of nothing, and deliberately NOT a
   full-screen scrim: it asks about analytics, not about anything that should
   stop somebody reading the page. */
.consent {
  position: fixed; left: 16px; bottom: 16px; z-index: 90;
  max-width: 380px; padding: 18px 20px;
  background: var(--paper); border: 1px solid var(--hair); border-radius: 6px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
}
.consent[hidden] { display: none; }
.consent p { margin: 0 0 14px; font-size: 13.5px; line-height: 1.55; color: var(--ink2); }
.consent-acts { display: flex; gap: 10px; }
/* Same size, same weight, same prominence. See the note in the markup. */
.consent-acts .btn { font-size: 13px; padding: 9px 18px; }

@media (max-width: 760px) {
  /* Full width so the two buttons are proper targets rather than a squeezed
     pair — and lifted clear of the staff cog, which is bottom-right and 38px
     tall. At bottom:12px the banner covered it completely, which would put
     Grant's own way into the admin behind a dialog every time he opened the
     site on a phone he had not yet answered it on. */
  .consent { left: 12px; right: 12px; bottom: 64px; max-width: none; }
  .consent-acts .btn { flex: 1; justify-content: center; }
}

/* ---- Staff cog ---- */
/* Bottom-right, low contrast until hovered. It has to be findable by Grant
   without reading as a feature to customers, so it sits at 30% opacity and
   only resolves into a button on hover or keyboard focus. */
.cog {
  position: fixed; right: 16px; bottom: 16px; z-index: 70;
  width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--paper2); border: 1px solid var(--hair);
  opacity: 0.3; transition: opacity 160ms ease, background 160ms ease, transform 160ms ease;
}
.cog svg { width: 19px; height: 19px; fill: var(--ink3); display: block; }
.cog:hover, .cog:focus-visible {
  opacity: 1; background: var(--dark); transform: rotate(35deg);
  outline: none;
}
.cog:hover svg, .cog:focus-visible svg { fill: var(--darkText); }
/* Keyboard users get a visible ring, since the hover cue is unavailable. */
.cog:focus-visible { box-shadow: 0 0 0 3px var(--sel); }

/* ---- The staff bar ----
   Replaced a 38px pencil that said "edit" and nothing else — not whether you
   were signed in, not as whom, and not how to reach the admin or get back out.

   Bottom-centre rather than tucked in the corner with the cog: it is four
   controls wide, and pinned to the right it would run off a phone. Centred it
   also reads as a toolbar belonging to the page rather than a badge stuck on
   top of it. Colours are its own, not the palette's — this is scaffolding
   around the site, and the four palettes must not repaint it into something
   that looks like part of the design. */
.staffbar {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 16px; z-index: 72;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px 7px 16px; border-radius: 999px;
  background: #1E2A38; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
  max-width: calc(100vw - 24px);
}
/* Green because it reports a live session, which is the one thing the pencil
   never told you. */
.staffbar .sb-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: #3FBF6B; box-shadow: 0 0 0 3px rgba(63, 191, 107, 0.22);
}
.staffbar .sb-hi {
  color: #E8EFF6; font-size: 14px; white-space: nowrap;
  margin-right: 4px;
}
/* The secondary buttons get a BORDER rather than a lighter fill, and that is
   not a style choice — it is the only way out of a contradiction. A fill 3:1
   lighter than this bar is already light enough that no near-white label can
   reach 4.5:1 on it. Searched the whole space to be sure: with the bar this
   dark, no single colour satisfies both. So the label owns the text contrast
   (9.5:1) and the border owns the boundary (3.05:1), which is what WCAG asks
   of each of them separately. */
.staffbar .sb-btn {
  appearance: none; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 8px 15px; border-radius: 999px; white-space: nowrap;
  font: inherit; font-size: 13.5px;
  background: #35424F; color: #fff; border: 1px solid #63748A;
  transition: background 140ms ease;
}
.staffbar .sb-btn:hover { background: #44525F; }
/* Searched, not picked. The blue in the reference carries white text at about
   3.5:1 — failing on the one control here anybody presses. This is the
   brightest blue that holds BOTH 4.5:1 for the white label (4.79) and 3:1
   against the bar (3.04), and it is the same blue as the admin's active nav
   item, so "primary" is one colour across the whole product. */
.staffbar .sb-btn.primary { background: #4C74AA; border-color: transparent; }
.staffbar .sb-btn.primary:hover { background: #5881B8; }
.staffbar .sb-btn:focus-visible { outline: 2px solid #8FC0EC; outline-offset: 2px; }
.staffbar .sb-btn[aria-busy="true"] { opacity: 0.55; cursor: progress; }
.staffbar .sb-btn:disabled { opacity: 0.55; cursor: progress; }

@media (max-width: 560px) {
  /* The greeting is the one thing here that is courtesy rather than a control,
     so it is what goes when there is not room for everything. */
  .staffbar { gap: 6px; padding: 7px; bottom: 12px; }
  .staffbar .sb-hi { display: none; }
  .staffbar .sb-dot { margin-left: 6px; }
  /* Taller here than on desktop: 35px is fine under a mouse and short of the
     44px a thumb needs, and the room freed by dropping the greeting is exactly
     what pays for it. */
  .staffbar .sb-btn { padding: 0 14px; min-height: 44px; font-size: 13px; }
}

/* Out of the way of the lightbox, which covers the whole viewport. */
body:has(.lightbox) .cog,
body:has(.lightbox) .staffbar { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cog { transition: opacity 160ms ease; }
  .cog:hover, .cog:focus-visible { transform: none; }
}

/* ---- Lightbox ---- */

.lightbox {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(22, 20, 17, 0.9); backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  padding: 5vh 4vw; animation: icFade 180ms ease both; cursor: zoom-out;
}
/* The box takes the PHOTO's shape, rather than forcing the photo into a fixed
   two-column proportion.

   It was a grid with 1.5fr/0.9fr columns and the image stretched to fill its
   column. That crops: a tall portrait in a wide panel loses its top and bottom,
   which on a workshop's own gallery means losing the thing being photographed.

   So the IMAGE is now the thing that has a size, and everything else follows.
   The photo renders at whatever fits both caps while keeping its ratio, .shot
   shrink-wraps it, the info column is a fixed width beside it, and the box is
   the sum. A portrait ends up tall and narrow, a landscape wide and short, and
   in both cases the whole picture is visible.

   min-height: 0 on the flex children is still needed for the same reason it was
   needed on the grid rows: without it the info column refuses to shrink below
   its content and pushes the buttons out of a box that is already capped. */
.lightbox .box {
  display: flex; align-items: stretch;
  max-width: min(1180px, 92vw); max-height: 90vh;
  background: var(--paper); border-radius: 4px; overflow: hidden; cursor: default;
}
.lightbox .box > * { min-height: 0; min-width: 0; }
.lightbox .box .shot {
  position: relative; flex: 0 0 auto;
  display: flex; background: var(--dark);
  /* Centre, do NOT stretch. The box is as tall as the taller of the photo and
     the details column, so when the text is the taller side a flex default of
     align-items:stretch scales the photo up to match — a 250px image was being
     drawn at 418px and going soft. Centring keeps every photo at its true size
     and lets the surround take up the slack. */
  align-items: center; justify-content: center;
}
/* The two caps that decide the size. Height is the binding one for portraits,
   width for landscapes; whichever bites first, the ratio is kept because only
   one dimension is ever set and the other stays auto.
   58vw leaves room for the info column on a wide screen; 800px stops a
   panorama from squeezing the text to nothing. */
.lightbox .box .shot img {
  display: block; width: auto; height: auto;
  max-height: 90vh; max-width: min(800px, 58vw);
  object-fit: contain;
}
/* No image: there is nothing to take a shape from, so the panel keeps the old
   fixed proportions. */
.lightbox .box .shot.ph { flex: 0 0 min(520px, 50vw); min-height: 340px; }
.lightbox .box .shot.ph { background: var(--paper2); }
.lightbox .box .info {
  flex: 0 0 380px; max-width: 46vw;
  padding: 40px 38px; display: flex; flex-direction: column; overflow: auto;
}
.lightbox .box .info .eyebrow { font-size: 11px; letter-spacing: 0.16em; margin-bottom: 14px; }
.lightbox .box .info h3 { font-size: 32px; line-height: 1.1; margin: 0 0 16px; }
.lightbox .box .info > p { font-size: 14.5px; line-height: 1.62; color: var(--ink2); margin: 0 0 22px; }
.lightbox .facts {
  display: grid; gap: 12px; padding: 20px 0;
  border-top: 1px solid var(--hair); border-bottom: 1px solid var(--hair); margin-bottom: 26px;
}
.lightbox .facts div { display: flex; justify-content: space-between; gap: 16px; font-size: 13.5px; }
.lightbox .facts span.k { color: var(--ink4); }
.lightbox .acts { margin-top: auto; display: grid; gap: 10px; }
.lightbox .acts .btn { text-align: center; }
/* Three actions now, so they need a hierarchy rather than three equal buttons:
   WhatsApp is the primary, the web form the alternative, Close the way out. */
.btn.plain {
  background: transparent; border-color: transparent; color: var(--ink3);
}
.btn.plain:hover { background: var(--paper2); color: var(--ink); }

/* ---- Enquiry form ----
   Reuses .lightbox for the backdrop and .info for the column styling, so it
   inherits the same type, spacing and scrolling behaviour rather than being a
   second modal with its own opinions. */
.lightbox .box.enq { max-width: min(520px, 92vw); display: block; }
.lightbox .box.enq .info { flex: none; max-width: none; width: 100%; }
.lightbox .box.enq label { display: grid; gap: 7px; margin-bottom: 16px; }
.lightbox .box.enq label > span {
  font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink3);
}
.lightbox .box.enq input,
.lightbox .box.enq textarea {
  padding: 12px 14px; border: 1px solid var(--hair); border-radius: 3px;
  background: var(--input); font-size: 15px; width: 100%; font: inherit;
}
.lightbox .box.enq textarea { line-height: 1.55; resize: vertical; }
.lightbox .box.enq input:focus,
.lightbox .box.enq textarea:focus { outline: none; border-color: var(--accent); }
.lightbox .box.enq .enq-note { font-size: 13px; color: var(--accent); margin: 0 0 14px; min-height: 18px; }
.lightbox .box.enq .acts { margin-top: 6px; }

/* ---- Responsive. The design file had no breakpoints; these are new. ---- */

@media (prefers-reduced-motion: reduce) {
  .marq .track, .marq .card { animation: none !important; }
  /* Left drawn rather than removed. The mark is emphasis, and someone who has
     asked for less motion still wants the sentence to stand out — they asked
     for stillness, not for the page to say less. */
  .gallery .foot .hl { animation: none !important; background-size: 104% 0.5em; }
  /* Frozen on step 1, which is exactly how the section looked before it moved.
     Same principle as the highlighter: stop the motion, keep the meaning. */
  .process .step::before,
  .process .step .n,
  .process .step .n::before { animation: none !important; }
  .process .step:first-child::before { transform: scaleX(1); }
  .process .step:first-child .n { color: var(--paper); }
  .process .step:first-child .n::before { opacity: 1; }
}

@media (max-width: 1000px) {
  /* The nav becomes a panel HERE rather than at 760px, because the row does not
     survive to 760: below about 940px the logo, five links and the WhatsApp
     pill overflow the header outright and push the whole page sideways. That
     band — small laptops and every tablet — was as broken as the phone was, it
     just failed by overflowing instead of by hiding.

     Only the nav moves to this breakpoint. The padding and logo caps below stay
     at 760px, where they were tuned. */
  .navtoggle { display: flex; }
  header.site nav { gap: 12px; }
  header.site .navlinks {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--paper); border-bottom: 1px solid var(--hair);
    box-shadow: 0 14px 28px rgba(0, 0, 0, 0.09);
    padding: 6px 20px 14px;
  }
  header.site .navlinks.open { display: flex; }
  /* 15px and 48px tall: a touch target, not a shrunken desktop link. */
  header.site .navlinks a:not(.wa-btn) {
    display: flex; align-items: center; min-height: 48px;
    font-size: 15px; border-bottom: 1px solid var(--hair2);
  }
  header.site .navlinks a:not(.wa-btn):last-child { border-bottom: none; }

  .hero { grid-template-columns: 1fr; gap: 40px; padding-top: 48px; }
  /* Stacked: the photo spans the column, so the desktop width cap and the
     viewport-height guard both stop applying. */
  .hero .shot { max-width: none; justify-self: stretch; min-height: 320px; }
  .contact { grid-template-columns: 1fr; gap: 44px; }
  .safety { grid-template-columns: 1fr; gap: 28px; }
  /* Stacked. The photo keeps its own shape here too — the old rule forced
     aspect-ratio 4/3 on it, which cropped a portrait exactly the way the
     desktop layout used to. Capped at 55vh so the details below it are on
     screen without scrolling for them. */
  .lightbox .box { flex-direction: column; overflow: auto; max-width: 92vw; }
  .lightbox .box .shot { flex: 0 0 auto; justify-content: center; }
  .lightbox .box .shot img { max-width: 100%; max-height: 55vh; }
  .lightbox .box .shot.ph { flex: 0 0 auto; width: 100%; min-height: 240px; }
  .lightbox .box .info { flex: 0 0 auto; max-width: none; }
}

@media (max-width: 760px) {
  header.site .inner { padding: 12px 20px; gap: 16px; }
  header.site .logo span { display: none; }
  /* The panel is styled at the 1000px breakpoint above — it has to start there,
     not here. What is left at this width is only the padding and the logo cap. */
  /* The Settings slider goes to 120px, which is right on a laptop and too wide
     for a phone: a 446x212 mark at 120px tall is 252px, and that plus the
     WhatsApp pill and the padding overflows a 375px screen. On small screens
     the chosen height becomes a CEILING, and the width is bounded too.

     Both, because one does not imply the other: a tall narrow mark and a long
     thin one need different limits, and only capping both keeps any logo inside
     the bar. height:auto is what lets the ratio survive when the width cap is
     the one that bites. */
  header.site .logo img {
    height: auto;
    max-height: var(--logo-h, 49px);
    max-width: 45vw;
  }
  .shell, .hero, .gallery, .process, .contact, footer.site, .who .inner, .says .inner, .marq .head, .about .inner {
    padding-left: 20px; padding-right: 20px;
  }
  .hero .stats { gap: 22px; }
  .hero .stats b { font-size: 24px; }
  .contact .two { grid-template-columns: 1fr; }
  .marq .rail { height: 320px; }
  .marq .card { height: 220px; }
  .gallery { padding-top: 72px; padding-bottom: 72px; }
  /* A "wide" tile cannot be wide when only one column fits: grid-column: span 2
     forces an implicit SECOND column, the tile renders at ~460px inside a 344px
     grid, and the page gains a horizontal scrollbar. Pre-existing — it happens
     with or without masonry — and it needs !important because the span is an
     inline style set by the renderer. */
  .tiles .tile { grid-column: span 1 !important; }
  .who { margin-top: 72px; }
  .who .inner, .says .inner { padding-top: 64px; padding-bottom: 64px; }
  .process { padding-top: 72px; padding-bottom: 72px; }
  .about .inner { padding-top: 72px; padding-bottom: 72px; }
}

/* Destination popup on the existing staff entry. */
.owner-access-menu{position:fixed;bottom:66px;right:16px;width:min(255px,calc(100vw - 32px));padding:8px;background:var(--paper);border:1px solid var(--hair);border-radius:10px;box-shadow:0 10px 35px #0003;z-index:90;display:grid;gap:4px}
.owner-access-menu[hidden]{display:none}
.owner-access-menu a{display:block;padding:12px 14px;color:var(--ink);font-size:14px;letter-spacing:0;border-radius:6px;text-decoration:none}
.owner-access-menu a:hover,.owner-access-menu a:focus-visible{background:var(--paper2);color:var(--accent)}
.owner-access-menu span{display:block;color:var(--ink3);font-size:11px;margin-top:5px}
.header-owner{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;border-radius:50%;opacity:.45;transition:opacity .16s,background .16s}
.header-owner svg{width:18px;height:18px;fill:var(--ink3)}
.header-owner:hover,.header-owner:focus-visible,.header-owner[aria-expanded="true"]{opacity:1;background:var(--paper2)}
.header-owner:focus-visible{outline:2px solid var(--accent);outline-offset:3px}