/* ==========================================================================
   Rui Charnock Studio
   One grid, one left edge, one vertical rhythm. Two typefaces. Nine colours,
   all of them sampled out of the drawings themselves.
   Mobile-first: every media query below adds, none takes away.
   ========================================================================== */

/* --- fonts ------------------------------------------------------------- */
/* Variable, instanced to the axes actually used (tools/subset-fonts.py).
   latin-ext carries the Portuguese diacritics; it only downloads on pages
   that contain them, which is what unicode-range is for. */
@font-face {
  font-family: 'Newsreader';
  src: url('/assets/fonts/newsreader-latin.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader';
  src: url('/assets/fonts/newsreader-latin-ext.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  src: url('/assets/fonts/inter-latin.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  src: url('/assets/fonts/inter-latin-ext.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- tokens ------------------------------------------------------------ */
:root {
  /* Sampled from the artwork. Do not add a colour that is not in this list. */
  --paper: #ffffff;
  --paper-warm: #fbf9f4;
  --ink: #12100e;
  --wash-blue: #5592e7;
  --wash-blue-dp: #194cad;
  --wash-blue-lt: #b3d0f4;
  --wash-yellow: #f0e878;
  --wash-ochre: #e8c24a;
  --wash-rose: #f4e6ea;
  --wash-crimson: #d6336c;

  /* Derived, still inside the palette. */
  --ink-60: color-mix(in srgb, var(--ink) 60%, var(--paper-warm));
  --ink-40: color-mix(in srgb, var(--ink) 40%, var(--paper-warm));
  --rule: color-mix(in srgb, var(--ink) 14%, var(--paper-warm));

  /* A rule with a hand in it. Same ink, drawn once as a 240px tile and
     repeated — a straight 1px border on a page made of brush marks reads as
     a different document. Used under table rows, section heads and list items
     wherever a plain border-bottom used to be. */
  --rule-drawn: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='3'%3E%3Cpath d='M0 1.7c31-.8 47 .6 78 .1s52-1 79-.2 51 .9 83 .5' fill='none' stroke='%2312100e' stroke-width='.9' stroke-linecap='round' opacity='.34'/%3E%3C/svg%3E");

  /* The edge of a wash. A section sitting on colour is masked with this, so the
     colour stops on an irregular painted edge instead of a ruled one. It is a
     mask, not a gradient: the pigment inside stays flat, which is what a
     watercolour ground actually looks like. Stretched to the section, so a tall
     section gets a longer, slower wave. */
  --wash-edge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 300' preserveAspectRatio='none'%3E%3Cpath d='M0 8C150 1 300 14 450 7S750 1 900 11 1130 3 1200 7v286c-150 7-300-6-450 1s-300 6-450-4-130 6-300 1z' fill='%23000'/%3E%3C/svg%3E");

  --display: 'Newsreader', ui-serif, Georgia, 'Times New Roman', serif;
  --body: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* One obvious jump between levels. Not five near-identical sizes. */
  --t-h1: clamp(2.5rem, 1.6rem + 4.5vw, 4.75rem);
  --t-h2: clamp(1.625rem, 1.25rem + 1.9vw, 2.375rem);
  --t-h3: clamp(1.125rem, 1.02rem + 0.5vw, 1.3125rem);
  --t-body: clamp(1.0625rem, 1.03rem + 0.16vw, 1.125rem);
  --t-cap: 0.875rem;

  --gap: clamp(1.25rem, 0.9rem + 1.6vw, 2rem);
  --rhythm: clamp(3.5rem, 2.2rem + 5.4vw, 7rem);
  --measure: 34rem;
  --shell: 76rem;

  --stage-h: 100svh;
}

/* --- reset -------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, p, ul, ol, figure, blockquote, dl, dd { margin: 0; }
ul[class], ol[class] { list-style: none; padding: 0; }
img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
/* Any class that sets `display` outranks the UA sheet's [hidden] rule, which
   is how the scroll cue leaked into the no-JS page. Make hidden mean hidden. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: var(--body);
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--ink);
  background-color: var(--paper-warm);
  /* Same sheet of paper under the canvas and under the DOM. That continuity is
     most of why the hand-off out of the canvas reads as one page. */
  background-image: url('/assets/derived/paper-grain.webp');
  background-size: 512px 512px;
  background-blend-mode: multiply;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background: var(--paper-warm);
  opacity: 0.97; /* grain shows through at ~3% */
  z-index: -1;
  pointer-events: none;
}

/* --- typography --------------------------------------------------------- */
h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); line-height: 1.12; }
h3 { font-size: var(--t-h3); font-family: var(--body); font-weight: 500; line-height: 1.35; }
p { text-wrap: pretty; }
p + p { margin-top: 0.85em; }

a { color: inherit; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
a:hover { text-decoration-color: var(--wash-crimson); }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 1px;
}

.lede {
  font-size: clamp(1.125rem, 1.02rem + 0.7vw, 1.375rem);
  line-height: 1.45;
  max-width: 32rem;
}
.cap { font-size: var(--t-cap); color: var(--ink-60); }
.measure { max-width: var(--measure); }
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* --- shell -------------------------------------------------------------- */
.shell {
  width: min(100% - (var(--gap) * 2), var(--shell));
  margin-inline: auto;
}
section { padding-block: var(--rhythm); }

.skip {
  position: absolute; left: var(--gap); top: -4rem; z-index: 100;
  background: var(--ink); color: var(--paper);
  padding: 0.65rem 1rem; text-decoration: none;
  transition: top 140ms ease;
}
.skip:focus { top: 0.6rem; }

/* --- header ------------------------------------------------------------- */
.masthead {
  position: relative; z-index: 20;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--gap);
  padding-block: 1.1rem;
}
.wordmark {
  font-family: var(--display);
  font-size: 1.125rem; letter-spacing: 0.01em;
  text-decoration: none; white-space: nowrap;
}
.wordmark b { font-weight: 600; }
.masthead nav ul {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1.15rem;
  list-style: none; margin: 0; padding: 0;
  font-size: var(--t-cap);
}
.masthead nav a { text-decoration: none; color: var(--ink-60); }
.masthead nav a:hover, .masthead nav a[aria-current='page'] { color: var(--ink); }
.masthead nav a[aria-current='page'] { text-decoration: underline; text-decoration-color: var(--wash-crimson); }
/* Below 48em the same list becomes one quiet scrolling line. It used to be
   display:none, which on a site whose brief says most visitors arrive on a
   phone meant most visitors had no navigation at all. No disclosure widget, no
   JavaScript, no state that can get stuck open. */
@media (max-width: 47.99em) {
  .masthead { flex-wrap: wrap; row-gap: .55rem; }
  .masthead nav {
    order: 3; width: 100%;
    overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none;
    /* The fade is the affordance: it says there is more to the right without
       adding a control that has to be styled, labelled and tested. */
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 2.2rem), transparent);
  }
  .masthead nav::-webkit-scrollbar { display: none; }
  .masthead nav ul { flex-wrap: nowrap; gap: 1.15rem; padding-bottom: .15rem; }
  .masthead nav li { white-space: nowrap; }
  /* Measured at 17px tall, which is a third of the 44px touch minimum and the
     only navigation a phone visitor has. The text does not get bigger; the
     tappable box does. */
  .masthead nav a,
  .foot a { display: inline-block; padding-block: .7rem; }
  .masthead nav ul { padding-bottom: 0; }
  .foot ul { gap: 0 1.1rem; }
}
/* One drawn rule under the masthead, on every page. */
.masthead {
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left bottom;
  background-size: 240px 3px;
}

/* --- buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5rem;
  min-height: 3rem; padding: 0.75rem 1.4rem;
  font-size: 1rem; font-weight: 500; line-height: 1;
  text-decoration: none; cursor: pointer;
  border: 1px solid var(--ink); background: var(--ink); color: var(--paper);
  transition: background-color 140ms ease, color 140ms ease;
}
.btn:hover { background: var(--wash-blue-dp); border-color: var(--wash-blue-dp); }
.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn[disabled] { opacity: 0.55; cursor: progress; }

.cta-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.9rem 1.25rem;
  margin-top: 1.75rem;
}
.cta-row .reply { font-size: var(--t-cap); color: var(--ink-60); }

/* --- hero / stage ------------------------------------------------------- */
/* The track reserves its height up front so nothing shifts when the canvas
   mounts. CLS budget is 0.05 and this is where it would be spent. */
.scroll-track { position: relative; height: auto; }
.scroll-track[data-webgl='on'] { height: var(--track-h, 480svh); }

.sticky-stage {
  position: relative;
  top: 0;
  height: auto;
  display: grid;
}
.scroll-track[data-webgl='on'] .sticky-stage {
  position: sticky;
  height: var(--stage-h);
  overflow: clip;
}
.stage-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0;
  transition: opacity 400ms ease;
}
.stage-canvas[data-ready='1'] { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .stage-canvas { transition: none; } }

.overlay {
  position: relative; z-index: 2;
  display: grid; align-content: start;
  padding-block: clamp(1.5rem, 4vh, 3.5rem) var(--rhythm);
  pointer-events: none;
}
.overlay > * { pointer-events: auto; }
.scroll-track[data-webgl='on'] .overlay { align-content: center; min-height: var(--stage-h); }

.hero-copy { max-width: 38rem; }
.hero-copy h1 { margin-bottom: 0.6em; }
.hero-copy .lede { color: var(--ink); }

.scroll-cue {
  margin-top: 2.5rem; font-size: var(--t-cap); color: var(--ink-40);
  display: inline-flex; align-items: center; gap: 0.5rem;
}
.scroll-cue::after {
  content: ''; width: 1px; height: 1.6rem;
  background: linear-gradient(var(--ink-40), transparent);
}

/* Act captions. Opacity only — the one transition allowed under reduced motion. */
.act-caption {
  position: absolute; left: 0; right: 0;
  padding-inline: var(--gap);
  font-family: var(--display); font-size: var(--t-h2); line-height: 1.15;
  max-width: 24em;
  opacity: 0;
  transition: opacity 200ms ease;
}
.act-caption[data-on='1'] { opacity: 1; }
.act-caption--ii { top: 12%; }
.act-caption--iii { bottom: 10%; }
@media (min-width: 48em) {
  /* Desktop Act III: drawing left, copy right. The canvas puts the page at
     -13% of the viewport width, so the caption takes the column it leaves. */
  .act-caption--iii {
    left: auto; right: 0; bottom: auto; top: 46%;
    width: min(34%, 22rem); padding-right: var(--gap); padding-left: 0;
  }
}

/* Act IV: the three facts, beside the settled print. In the static path this
   is just a block in normal flow — same markup, same words, no duplication. */
.act-proof { margin-top: var(--rhythm); }
.act-proof h2 { margin-bottom: 1.4rem; }
.scroll-track[data-webgl='on'] .act-proof {
  /* Mobile Act IV: the print is lifted into the top third by the scene, so the
     facts take the bottom half outright rather than sharing space with it. */
  position: absolute; left: 0; right: 0; top: 44%; bottom: 4%;
  padding-inline: var(--gap); margin: 0;
  opacity: 0; transition: opacity 200ms ease;
}
@media (max-width: 47.99em) {
  /* Phone Act IV shows the four facts, not their sub-lines — there is no room
     for both above the sticky CTA. The sub-lines are still in the DOM and are
     what the no-canvas path renders, so nothing is lost, only deferred. */
  .scroll-track[data-webgl='on'] .act-proof .proof span { display: none; }
  .scroll-track[data-webgl='on'] .act-proof .proof li { padding-top: 0.7rem; }
}
.scroll-track[data-webgl='on'] .act-proof[data-on='1'] { opacity: 1; }
.scroll-track[data-webgl='on'] .act-proof h2 { font-size: var(--t-h3); font-family: var(--body); }
@media (min-width: 48em) {
  .scroll-track[data-webgl='on'] .act-proof {
    left: auto; right: 0; bottom: auto; top: 22%;
    width: min(38%, 26rem); padding-right: var(--gap); padding-left: 0;
    transition: opacity 200ms ease;
  }
  .scroll-track[data-webgl='on'] .act-proof .proof { grid-template-columns: 1fr; gap: 0.9rem; }
  /* Act V. The print has faded and the facts are the only thing left in the
     stage, so they stop being a column beside a drawing and become the panel.
     Without this the last screen of the hero was a narrow strip of text with
     two thirds of the viewport empty — and before the fix in hero.js it was
     empty outright. */
  .scroll-track[data-act='v'] .act-proof {
    left: 0; right: 0; top: 26%;
    width: auto; max-width: var(--shell);
    margin-inline: auto;
    padding-inline: var(--gap);
  }
  .scroll-track[data-act='v'] .act-proof .proof {
    grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap);
  }
  .scroll-track[data-act='v'] .act-proof .proof span { display: block; }
}
/* The one CTA inside the stage. In the no-canvas path this is an ordinary
   button at the end of the proof list, which is where it belongs anyway. */
.act-proof .cta-row { margin-top: 1.8rem; }

/* Night variant: the room behind the overlay is deep blue, so the copy that
   sits on the canvas flips to paper. Only inside the stage — the rest of the
   page is still ink on paper. */
.scroll-track[data-overlay='light'] .overlay,
.scroll-track[data-overlay='light'] .overlay h1,
.scroll-track[data-overlay='light'] .overlay h2,
.scroll-track[data-overlay='light'] .overlay .lede,
.scroll-track[data-overlay='light'] .act-caption,
.scroll-track[data-overlay='light'] .act-proof b,
.scroll-track[data-overlay='light'] .scroll-cue { color: var(--paper); }
.scroll-track[data-overlay='light'] .overlay .reply,
.scroll-track[data-overlay='light'] .act-proof span {
  color: color-mix(in srgb, var(--paper) 72%, transparent);
}
.scroll-track[data-overlay='light'] .act-proof li { border-top-color: color-mix(in srgb, var(--paper) 28%, transparent); }
.scroll-track[data-overlay='light'] .overlay .btn {
  background: var(--paper); color: var(--ink); border-color: var(--paper);
}
.scroll-track[data-overlay='light'] .overlay .btn--ghost {
  background: transparent; color: var(--paper);
}

/* --- prototype chooser (dev only, not shipped copy) --------------------- */
.proto-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: flex; gap: .4rem; align-items: center; justify-content: center;
  padding: .5rem var(--gap) calc(.5rem + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--ink) 92%, transparent);
  font-size: .8125rem;
}
.proto-bar a {
  color: var(--paper); text-decoration: none; padding: .35rem .6rem;
  border: 1px solid color-mix(in srgb, var(--paper) 35%, transparent);
}
.proto-bar a[aria-current='page'] { background: var(--paper); color: var(--ink); }
.proto-bar b { color: var(--paper); font-weight: 500; margin-right: .4rem; }
@media (max-width: 47.99em) {
  .proto-bar { bottom: 4.5rem; }
  .proto-bar b { display: none; }
}
.proto-index { display: grid; gap: var(--gap); margin-top: var(--gap); }
@media (min-width: 48em) { .proto-index { grid-template-columns: repeat(2, 1fr); } }
.proto-index a {
  display: block; border: 1px solid var(--rule); padding: 1.4rem; text-decoration: none;
  background: var(--paper);
}
.proto-index a:hover { border-color: var(--ink); }
.proto-index h2 { font-size: 1.5rem; margin-bottom: .5rem; }
.proto-index p { color: var(--ink-60); font-size: .9375rem; }

/* --- static hero (no-canvas path) --------------------------------------- */
.hero-static {
  display: grid; gap: var(--gap);
  align-items: center;
  padding-block: clamp(2rem, 6vh, 4rem) var(--rhythm);
}
.hero-static figure { margin: 0; }
.hero-static img {
  width: 100%; height: auto;
  background: var(--paper);
}
@media (min-width: 48em) {
  .hero-static { grid-template-columns: 1fr 1fr; gap: calc(var(--gap) * 2); }
}

/* --- proof -------------------------------------------------------------- */
.proof {
  display: grid; gap: var(--gap);
  counter-reset: proof;
}
@media (min-width: 48em) { .proof { grid-template-columns: repeat(2, 1fr); } }
.proof li {
  padding-top: 1.1rem;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
}
.proof b { display: block; font-weight: 500; font-size: 1.0625rem; }
.proof span { display: block; margin-top: 0.35rem; color: var(--ink-60); font-size: var(--t-cap); }

/* --- generic prose sections --------------------------------------------- */
.prose { display: grid; gap: var(--rhythm); }
/* Where the blocks are a set of four short definitions rather than three long
   arguments, full rhythm between them reads as four separate pages. */
.prose--tight { gap: clamp(2rem, 1.4rem + 2.4vw, 3.25rem); }
.prose-block { display: grid; gap: 0.9rem; }
@media (min-width: 56em) {
  .prose-block {
    grid-template-columns: 15rem minmax(0, var(--measure));
    gap: var(--gap) calc(var(--gap) * 2);
    align-items: start;
  }
  .prose-block h2 { grid-column: 1; }
  .prose-block .body { grid-column: 2; }
}
.prose-block .body { display: grid; gap: 0.85rem; }

/* --- pricing ------------------------------------------------------------ */
/* The packages were three bordered cards. They are a matrix — three products
   against six attributes — so they are now a real table, which reads faster,
   stacks properly on a phone, and drops three boxes off a page made of drawn
   marks. The price keeps the display face; that was the good part of the card. */
.data--packages tbody th { font-family: var(--display); font-size: 1.1875rem; font-weight: 600; }
.data--packages td:nth-child(3) { font-family: var(--display); font-size: 1.5rem; white-space: nowrap; }
@media (max-width: 33.99em) {
  .data--packages td:nth-child(3) { font-size: 1.75rem; color: var(--ink); }
  .data--packages td:nth-child(3)::before { font: 400 var(--t-cap) var(--body); }
}
.pkg-includes {
  display: grid; gap: 0.45rem; margin-top: var(--gap);
  font-size: 0.9375rem; list-style: none; padding: 0;
}
.pkg-includes li { padding-left: 1.1rem; position: relative; color: var(--ink-60); }
.pkg-includes li::before {  /* the crimson rule is the marker */
  content: ''; position: absolute; left: 0; top: 0.62em;
  width: 0.5rem; height: 1px; background: var(--wash-crimson);
}
section > .lede + h2 { margin-top: var(--gap); }
section > p + h2 { margin-top: calc(var(--gap) * 1.6); }

/* --- FAQ ---------------------------------------------------------------- */
.faq { display: grid; gap: 0; margin-top: var(--gap); }
.faq > div {
  padding-block: 1.4rem;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
}
.faq h2 {
  font-size: var(--t-h3); font-family: var(--body); font-weight: 500;
  letter-spacing: 0; line-height: 1.4;
}
.faq p { margin-top: 0.5rem; max-width: var(--measure); color: var(--ink-60); }
.faq p a { color: var(--ink); }

/* --- contact / form ----------------------------------------------------- */
.enquiry { display: grid; gap: calc(var(--gap) * 1.5); }
@media (min-width: 56em) {
  .enquiry { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); gap: calc(var(--gap) * 2.5); }
}
.field { display: grid; gap: 0.35rem; margin-bottom: 1.05rem; }
.field label { font-size: var(--t-cap); font-weight: 500; }
.field .help { font-size: 0.8125rem; color: var(--ink-60); }
.field input, .field textarea {
  width: 100%;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--ink-40);
  background: var(--paper);
  border-radius: 0;
  font-size: 1rem; /* never < 16px: iOS zooms on focus below that */
}
.field textarea { resize: vertical; min-height: 5.5rem; }
.field input:focus-visible, .field textarea:focus-visible { border-color: var(--ink); }
.field[data-invalid] input, .field[data-invalid] textarea { border-color: var(--wash-crimson); }
.field .err { font-size: 0.8125rem; color: var(--wash-crimson); min-height: 0; }
.pair { display: grid; gap: 0 1rem; }
@media (min-width: 30em) { .pair { grid-template-columns: 1fr 1fr; } }

.form-foot { display: grid; gap: 0.7rem; justify-items: start; margin-top: 0.4rem; }
.form-foot .reply { font-size: var(--t-cap); color: var(--ink-60); }
.form-status { font-size: var(--t-cap); }
.form-status[data-state='error'] { color: var(--wash-crimson); }

.direct { display: grid; gap: 1.1rem; align-content: start; }
.direct a.big {
  display: grid; gap: 0.15rem;
  border: 1px solid var(--ink); padding: 1rem 1.2rem;
  text-decoration: none; min-height: 3.75rem; align-content: center;
  transition: background-color 140ms ease, color 140ms ease;
}
.direct a.big:hover { background: var(--ink); color: var(--paper); }
.direct a.big b { font-weight: 500; }
.direct a.big span { font-size: var(--t-cap); opacity: 0.75; }

 /* Built by ui/form.js when a POST succeeds; it replaces the form outright. */
.sent { border: 1px solid var(--ink); padding: 1.5rem; background: var(--paper); }
.sent h3 { font-family: var(--display); font-size: 1.5rem; font-weight: 600; }
.sent p { margin-top: 0.5rem; color: var(--ink-60); }

/* --- repeated contact strip --------------------------------------------- */
/* It used to sit between two drawn rules. On a page already made of drawn rules
   that reads as one more paragraph, so the strip now sits on a wash: the
   interruption is a colour change rather than another line. */
.strip {
  position: relative;
  isolation: isolate;
  padding: 1.7rem clamp(1rem, 3vw, 2rem);
  display: flex; flex-wrap: wrap; gap: 1rem var(--gap);
  align-items: center; justify-content: space-between;
  margin-block: clamp(1rem, 2vw, 1.75rem);
}
.strip::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: color-mix(in srgb, var(--ground) var(--ground-mix), var(--paper-warm));
  -webkit-mask-image: var(--wash-edge);
  mask-image: var(--wash-edge);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.strip p { max-width: 30rem; }

/* --- breadcrumbs --------------------------------------------------------- */
/* The hierarchy, said out loud. Cheapest possible answer to "a clearly defined,
   browseable hierarchy", and a person can use it too. */
.crumbs { padding-top: .2rem; }
.crumbs ol {
  display: flex; flex-wrap: wrap; gap: .1rem .55rem;
  list-style: none; margin: 0; padding: 0;
  font-size: var(--t-cap); color: var(--ink-60);
}
.crumbs li { display: flex; align-items: baseline; gap: .55rem; }
.crumbs li + li::before { content: '›'; color: var(--ink-40); }
.crumbs a { text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.crumbs [aria-current] { color: var(--ink); }
.crumbs + main > section:first-of-type { padding-top: clamp(1.5rem, 4vh, 2.75rem); }

/* --- the opening ---------------------------------------------------------- */
/* What used to be `.answer`: a bordered 40–60-word definition sitting in the
   same slot on seven pages. It read as a dictionary entry rather than as a
   person talking, and having the identical component in the identical place on
   every page was itself most of the "made by a machine" feeling. See the note
   at the top of src/copy.js.
   Now it is just prose. The first paragraph is set large because it is the one
   carrying the fact; the rest is body copy. No box, no rule, no slot. */
.opening { margin-top: 1.2rem; display: grid; gap: 0.85rem; }
.opening p { max-width: 36rem; }
.opening p:first-child {
  font-size: clamp(1.125rem, 1.02rem + 0.7vw, 1.375rem);
  line-height: 1.4;
  max-width: 32rem;
}
.opening p:not(:first-child) { color: var(--ink-60); }

/* --- washes --------------------------------------------------------------- */
/* Rui's note was two things at once: there is not enough colour, and every page
   reads like a book with nowhere for the eye to land. Both are answered by the
   same thing — one section per page sits on a ground of a colour taken straight
   out of the drawings, and carries whatever that page is actually selling.
   Four tones, all already in the palette. Nothing new was added: --wash-ochre
   and --wash-blue-lt had zero uses on the whole site before this. */
/* Raised from the first pass. Rui's note on the result was still "very beige":
   the washes were there but spending about a third of what the drawings spend.
   Ink on these is well past AA at every one of these strengths — the ceiling is
   taste, not contrast. */
[data-ground] { --ground: var(--wash-yellow); --ground-mix: 42%; }
[data-ground='blue'] { --ground: var(--wash-blue-lt); --ground-mix: 62%; }
[data-ground='ochre'] { --ground: var(--wash-ochre); --ground-mix: 34%; }
[data-ground='rose'] { --ground: var(--wash-rose); --ground-mix: 100%; }

.feature,
.band,
.strip {
  /* Supporting copy is --ink-60 against paper, where it clears AA comfortably.
     On a wash it does not: measured 3.81:1 on the pale blue and 4.23:1 on the
     ochre, against the 4.5:1 body-text minimum in CLAUDE.md §9. Table headers,
     captions, the spec-table labels and the review attributions were all
     failing. Darkening the token inside a ground fixes every one of them in a
     single place, and leaves the grey on paper alone. */
  --ink-60: color-mix(in srgb, var(--ink) 76%, var(--paper-warm));
  --ink-40: color-mix(in srgb, var(--ink) 58%, var(--paper-warm));
}

.feature,
.band {
  position: relative;
  isolation: isolate;          /* keeps the z-index:-1 wash above body::before */
}
.feature { padding-block: var(--rhythm); margin-block: clamp(1.5rem, 1rem + 2vw, 3rem); }
/* Colour and separation without the oversized heading, so a page can carry
   more than one wash and still have exactly one loudest thing (§4.2). */
.band { padding-block: calc(var(--rhythm) * 0.72); --ground-mix: 30%; }
.band[data-ground='blue'] { --ground-mix: 44%; }
.band[data-ground='rose'] { --ground-mix: 100%; }

/* A section whose content is drawings, not type, gets NO wash at all.
   The plates are transparent cut-outs multiplied onto the page, so any ground
   runs straight through them: at 11% it was still enough to turn a page of
   thirteen drawings uniformly yellow, which was Rui's second note. There is no
   safe tint — multiply has no neutral. These sections separate with a pair of
   drawn rules instead, and the drawings supply the colour, which is the whole
   argument of §4 anyway. */
.feature--art, .band--art { --ground-mix: 0%; }
.feature--art, .band--art {
  background-image: var(--rule-drawn), var(--rule-drawn);
  background-repeat: repeat-x, repeat-x;
  background-position: left top, left bottom;
  background-size: 240px 3px, 240px 3px;
}
.feature::before,
.band::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: color-mix(in srgb, var(--ground) var(--ground-mix), var(--paper-warm));
  -webkit-mask-image: var(--wash-edge);
  mask-image: var(--wash-edge);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
/* Inside a feature the heading is the loudest type on the page after the h1. */
.feature > .shell > h2,
.feature > .shell > h2:first-child {
  font-size: clamp(1.875rem, 1.35rem + 2.4vw, 3rem);
  max-width: 20em;
}
.feature .table-wrap { margin-top: calc(var(--gap) * 1.2); }
.feature table.data { font-size: 1rem; }
.feature .cta-row { margin-top: calc(var(--gap) * 1.4); }

/* --- social proof --------------------------------------------------------- */
.quotes {
  display: grid; gap: var(--gap); margin-top: calc(var(--gap) * 1.4);
  list-style: none; padding: 0;
}
@media (min-width: 52em) { .quotes { grid-template-columns: repeat(3, 1fr); gap: calc(var(--gap) * 1.6); } }
.quotes li {
  display: grid; align-content: start; gap: .9rem;
  padding-top: 1.2rem;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
}
.quotes blockquote { margin: 0; }
.quotes blockquote p {
  font-family: var(--display);
  font-size: clamp(1.125rem, 1.03rem + 0.5vw, 1.3125rem);
  line-height: 1.35;
}
.quotes .who { display: grid; gap: .1rem; font-size: var(--t-cap); color: var(--ink-60); }
.quotes .who b { color: var(--ink); font-weight: 500; }
.quotes .who .kind {
  justify-self: start; margin-top: .35rem;
  padding: .1rem .45rem;
  background: color-mix(in srgb, var(--wash-blue-lt) 70%, var(--paper));
  color: var(--ink);
}
/* Sample text, and it must look like sample text. The words are set in the
   quiet grey the rest of the site uses for supporting copy rather than in the
   display face, so a skim reader never mistakes one for a customer. */
.quotes--sample blockquote p {
  font-family: var(--body);
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--ink-60);
  font-style: italic;
}
.quotes--sample .who b { font-weight: 400; color: var(--ink-60); font-style: italic; }
.sample-notice {
  margin-top: var(--gap);
  max-width: var(--measure);
  padding: .9rem 1.1rem;
  background: var(--paper);
  border-left: 3px solid var(--wash-crimson);
  font-size: var(--t-cap);
}
.sample-notice b { display: block; margin-bottom: .35rem; }
.sample-notice p + p { margin-top: .3rem; }

/* The one FAQ answer everybody is actually looking for, set as an answer
   rather than as the first row of a list. */
.feature-answer {
  margin-top: 1rem;
  max-width: 34rem;
  font-size: clamp(1.125rem, 1.02rem + 0.7vw, 1.375rem);
  line-height: 1.45;
}

/* The most-booked package. On a matrix of three, the row people take should
   not have to be found. */
tr.row--picked th, tr.row--picked td {
  background-color: color-mix(in srgb, var(--wash-ochre) 42%, var(--paper-warm));
}
tr.row--picked th { font-weight: 600; }
@media (max-width: 33.99em) {
  table.data--stack tr.row--picked {
    background-color: color-mix(in srgb, var(--wash-ochre) 42%, var(--paper-warm));
    padding-inline: .7rem;
  }
  table.data--stack tr.row--picked th,
  table.data--stack tr.row--picked td { background-color: transparent; }
}

/* --- the gallery ---------------------------------------------------------- */
/* /work/. Twelve drawings at once, because the visitor has four illustrators
   open in four tabs and ten seconds for each. No carousel (CLAUDE.md §11), no
   lightbox, no filtering. */
.gallery {
  display: grid; gap: var(--gap);
  grid-template-columns: 1fr;
  margin-top: var(--gap);
}
@media (min-width: 40em) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64em) { .gallery { grid-template-columns: repeat(3, 1fr); gap: calc(var(--gap) * 1.3); } }
.gallery img { width: 100%; height: auto; mix-blend-mode: multiply; }

/* Three drawings and a way through to the other nine. */
.work-strip { display: grid; gap: var(--gap); margin-top: var(--gap); }
@media (min-width: 48em) { .work-strip { grid-template-columns: repeat(3, 1fr); align-items: end; } }
.work-strip img { width: 100%; height: auto; mix-blend-mode: multiply; }
@media (max-width: 47.99em) {
  /* Three stacked drawings is a lot of scrolling for a teaser. */
  .work-strip img:nth-child(n + 3) { display: none; }
  .work-strip { grid-template-columns: repeat(2, 1fr); }
}

/* --- what happens after you send this ------------------------------------- */
/* Under the form. Nobody enquiring here has booked a live illustrator before,
   and not knowing what happens next is a real reason not to press send. */
.after-send {
  margin-top: .4rem;
  padding-top: 1.1rem;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
}
.after-send h3 { font-size: var(--t-cap); font-weight: 500; color: var(--ink-60); }
.after-send ol {
  margin: .7rem 0 0; padding: 0; list-style: none;
  display: grid; gap: .5rem; counter-reset: step;
  font-size: 0.9375rem;
}
.after-send li { position: relative; padding-left: 1.6rem; counter-increment: step; }
.after-send li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0.05em;
  width: 1.1rem; height: 1.1rem; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 0.6875rem; font-weight: 500;
  background: color-mix(in srgb, var(--wash-ochre) 60%, var(--paper-warm));
}

/* --- tables -------------------------------------------------------------- */
/* There were none. Prices, travel bands and packages are all natively tabular
   and were all prose. Ruled in drawn ink, never boxed. */
.table-wrap { margin-top: var(--gap); overflow-x: auto; }
table.data {
  width: 100%; border-collapse: collapse;
  font-size: 0.9375rem; text-align: left;
}
table.data caption {
  caption-side: top; text-align: left;
  font-size: var(--t-cap); color: var(--ink-60);
  max-width: var(--measure); margin-bottom: .9rem;
}
table.data th, table.data td {
  padding: .7rem .9rem .75rem 0;
  vertical-align: baseline;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left bottom;
  background-size: 240px 3px;
}
table.data td:last-child, table.data th:last-child { padding-right: 0; }
table.data thead th {
  font-weight: 500; font-size: var(--t-cap);
  color: var(--ink-60); white-space: nowrap;
}
table.data tbody th { font-weight: 500; }
table.data tbody th a { text-decoration-color: var(--wash-blue); }
table.data--travel tbody th { white-space: nowrap; }
table.data--spec { max-width: 34rem; }
table.data--spec th { color: var(--ink-60); font-weight: 400; width: 46%; }
@media (max-width: 33.99em) {
  /* Three or more columns will not fit a 375 px screen, and a table that
     scrolls sideways is a worse answer than one that stacks. Each row becomes
     its own block and every cell carries the column header it lost. */
  table.data--stack thead { display: none; }
  table.data--stack tr {
    display: block; padding-block: .8rem;
    background-image: var(--rule-drawn); background-repeat: repeat-x;
    background-position: left bottom; background-size: 240px 3px;
  }
  table.data--stack th, table.data--stack td { display: block; background: none; padding: .12rem 0; }
  table.data--stack tbody th { font-size: 1.0625rem; margin-bottom: .2rem; }
  table.data--stack tbody th:empty { display: none; }
  table.data--stack td { color: var(--ink-60); font-size: var(--t-cap); }
  table.data--stack td:not([data-label=''])::before {
    content: attr(data-label) ' · '; color: var(--ink-40);
  }
}

/* --- place links --------------------------------------------------------- */
/* A link that carries a fact. Not a list of names — the drive time is the
   thing the reader is actually asking about. */
.place-links {
  display: grid; gap: 0; margin-top: 1.6rem;
  list-style: none; padding: 0;
}
@media (min-width: 40em) { .place-links { grid-template-columns: repeat(2, 1fr); column-gap: calc(var(--gap) * 2); } }
@media (min-width: 64em) { .place-links { grid-template-columns: repeat(3, 1fr); } }
.place-links a {
  display: grid; gap: .15rem;
  padding: .85rem 0 .9rem;
  text-decoration: none;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left bottom;
  background-size: 240px 3px;
}
.place-links b { font-weight: 500; font-family: var(--display); font-size: 1.25rem; }
.place-links a:hover b { text-decoration: underline; text-decoration-color: var(--wash-crimson); }
.place-links span { font-size: var(--t-cap); color: var(--ink-60); }

/* --- coverage map -------------------------------------------------------- */
.map-figure { margin: var(--gap) 0 0; }
.map { width: 100%; max-width: 30rem; height: auto; }
.map-dots circle { fill: var(--ink); opacity: .2; }
.map-marks circle { fill: var(--ink); }
.map-marks path { fill: none; stroke: var(--ink); stroke-width: .6; opacity: .45; }
.map-marks text { font-family: var(--body); font-size: 10px; font-weight: 500; fill: var(--ink); }
.map-figure figcaption { margin-top: .9rem; max-width: var(--measure); }

/* --- nearby prose -------------------------------------------------------- */
/* Hand-written sentences, not a generated block of reciprocal links. */
.nearby-prose { display: grid; gap: .8rem; max-width: var(--measure); margin-top: 1rem; }

/* --- page head ----------------------------------------------------------- */
/* h1 across the full measure, then the answer and the drawing side by side.
   The drawing is capped rather than filling the shell — a 76rem-wide sheet of
   watercolour is a poster, and this is a page. */
.page-head { display: grid; gap: var(--gap); margin-top: 1.1rem; }
@media (min-width: 56em) {
  .page-head {
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
    gap: calc(var(--gap) * 2.5);
    align-items: start;
  }
}
.page-head > div > .opening:first-child { margin-top: 0; }
.page-head figure { margin: 0; max-width: 24rem; }
.page-head figcaption { margin-top: .7rem; }

/* --- plates -------------------------------------------------------------- */
/* Transparent cut-outs, so the drawing sits on the page's paper instead of in
   a white rectangle laid on top of it. */
.plate-grid { display: grid; gap: var(--gap); margin-top: var(--gap); }
@media (min-width: 48em) { .plate-grid { grid-template-columns: repeat(2, 1fr); } }
img.plate { width: 100%; height: auto; mix-blend-mode: multiply; }

/* --- retirement notice --------------------------------------------------- */
/* Shown on the concelho set while it is on its way out. A person arriving from
   an old link should be told, not quietly served a page nobody maintains. */
.retired {
  margin-top: 1.1rem; max-width: var(--measure);
  font-size: var(--t-cap);
  padding: .75rem .95rem;
  background: var(--wash-rose);
}

/* --- concelho index ----------------------------------------------------- */
.district { margin-top: var(--rhythm); }
.district h2 { font-size: var(--t-h3); font-family: var(--body); font-weight: 500; }
.district h2 span { color: var(--ink-40); font-weight: 400; }
.conc-list {
  columns: 2; column-gap: var(--gap);
  margin-top: 0.9rem; font-size: 0.9375rem;
  padding-top: 0.9rem;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
}
@media (min-width: 40em) { .conc-list { columns: 3; } }
@media (min-width: 64em) { .conc-list { columns: 4; } }
.conc-list li { break-inside: avoid; padding-block: 0.18rem; }
.conc-list a { text-decoration: none; }
.conc-list a:hover { text-decoration: underline; }
.conc-list .km { color: var(--ink-40); font-size: 0.8125rem; }

/* --- concelho page ------------------------------------------------------ */
.band {
  display: inline-block; font-size: var(--t-cap); font-weight: 500;
  border: 1px solid currentColor; padding: 0.2rem 0.6rem; margin-bottom: 1rem;
}
.band[data-band='included'] { color: var(--wash-blue-dp); }
.band[data-band='day'] { color: var(--ink-60); }
.band[data-band='overnight'] { color: var(--ink-60); }
.band[data-band='air'] { color: var(--ink-60); }

.facts { display: grid; gap: 0; margin-top: var(--gap); max-width: 30rem; }
.facts div {
  display: flex; justify-content: space-between; gap: 1rem;
  padding-block: 0.7rem; font-size: 0.9375rem;
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
}
.facts dt { color: var(--ink-60); }
.facts dd { margin: 0; text-align: right; }

.freguesias {
  columns: 2; column-gap: var(--gap);
  font-size: 0.9375rem; color: var(--ink-60);
  margin-top: 0.9rem;
}
@media (min-width: 44em) { .freguesias { columns: 3; } }
.freguesias li { break-inside: avoid; padding-block: 0.12rem; }

.nearby { display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; margin-top: 0.9rem; font-size: 0.9375rem; }

/* --- footer ------------------------------------------------------------- */
.foot {
  background-image: var(--rule-drawn);
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 240px 3px;
  padding-block: calc(var(--gap) * 1.2) calc(var(--gap) * 2);
  font-size: var(--t-cap); color: var(--ink-60);
  display: grid; gap: 1.25rem;
}
@media (min-width: 48em) { .foot { grid-template-columns: 1fr auto; align-items: end; } }
.foot ul { display: flex; flex-wrap: wrap; gap: 0.4rem 1.1rem; list-style: none; padding: 0; }
.foot a { text-decoration: none; }
.foot a:hover { text-decoration: underline; }

/* --- sticky mobile CTA -------------------------------------------------- */
/* Persistent on mobile, always. A planner has to be able to enquire from
   anywhere on the page without hunting for the form. */
.sticky-cta {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex; gap: 0.6rem; align-items: center;
  padding: 0.6rem var(--gap) calc(0.6rem + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--paper-warm) 92%, transparent);
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--rule);
}
.sticky-cta .btn { flex: 1 1 auto; }
.sticky-cta .wa { flex: 0 0 auto; }
@media (min-width: 48em) { .sticky-cta { display: none; } }
@media (max-width: 47.99em) { body { padding-bottom: 4.5rem; } }

/* --- placeholders ------------------------------------------------------- */
/* Real-world values Rui still has to supply. Visible on purpose: a silent
   placeholder is one that ships. */
.todo {
  background: var(--wash-yellow);
  color: var(--ink);
  padding: 0 0.2em;
  border-bottom: 1px dashed var(--wash-crimson);
}

/* --- motion ------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 200ms !important;
    transition-property: opacity !important;
  }
}

/* Landscape phones: the canvas never mounts (there is no room for a drawing
   and a form at once, and the form wins), so the track must collapse. */
@media (max-height: 30em) and (orientation: landscape) {
  .scroll-track[data-webgl='on'] { height: auto; }
  .scroll-track[data-webgl='on'] .sticky-stage { position: relative; height: auto; }
}

/* --- reveal ------------------------------------------------------------- */
[data-reveal] { opacity: 0; transition: opacity 320ms ease; }
[data-reveal][data-seen='1'] { opacity: 1; }
@media (prefers-reduced-motion: reduce) { [data-reveal] { opacity: 1; } }
.no-js [data-reveal] { opacity: 1; }
