/*
  /brief, /book-a-call and /contact — the three conversion pages, one stylesheet.

  ── Why one file and not three ──────────────────────────────────────────────

  The pages share a geometry: a short light hero, then one working area holding a
  numbered left column and a right-hand panel, then a full-width exit line under both.
  A reader who arrives on one and takes the cross-link to the other should meet the same
  page with different contents, because the *contents* are the whole difference between
  them — a form on one, a scheduler on the other — and two stylesheets would let that
  difference drift into the layout, where it says nothing.

  /contact joined that arrangement rather than bringing its own. It is the same working
  area with the sides swapped: the form is the left and wider track, and the right holds
  the address and two exits instead of four numbered steps. A third stylesheet for a page
  whose panel, fields, error slots, consent line, status region and success state are all
  already in this file would have been ~600 lines of copy in order to change one ratio.

  So everything here is `cv-` and shared, and the page-specific rules are bounded and
  named: `.cv-panel--calendar` for /book-a-call, and the `/contact` section near the
  bottom for the third.

  ── What it borrows, and from where ─────────────────────────────────────────

  Nothing here is new design. The hero is /suppliers' band with the dark ground taken
  off; the grid is `.sup-grid`; the panel is `.sup-form-panel`; the fields, hints and
  error slots are `.sup-field`'s, down to the two-line label reservation and the drawn
  select arrow; the numbered list is `.ind-stages`' hairline-and-blue-number treatment
  stood up as a column; the file input is a real input at zero opacity over a drawn
  face, which is the only way a picker works with no script at all. The
  duplication is deliberate and it is bounded: /suppliers' form posts `mailto` and this
  one posts multipart, so a shared component would be one component with two contracts.

  ── The hero is light, and both heroes on this site's other pages are dark ──

  /suppliers, /industries and every Learn page open on ink. These two do not, and it is
  the one place they leave the pattern. A reader gets here by pressing a button — they
  have already read the argument, and a full-bleed black band in front of the thing they
  came to do is a screen to get past. Light also means the page has one ground from top
  to bottom, so the panel's hairline is the only edge on it.

  Loaded after tokens.css and base.css. home.css is not loaded on either page, so
  anything these pages need is here or in base.css. Every colour is a token: there is no
  hex, no rgba() and no colour name in this file except the one `color-mix` the error
  text needs, which is `.sup-form`'s value and carries its reasoning.
*/

/* ═══ HERO ═══════════════════════════════════════════════════════════════ */

/*
  Not `.section`: the top padding has to clear the fixed bar, which `.section`'s
  symmetrical `--section-y` does not. Same arithmetic as `.sup-hero`.

  Shorter at the bottom than at the top, because what follows is the working area and the
  gap between a headline and the work should be smaller than the gap above the headline.
*/
.cv-hero {
  padding: calc(var(--topbar-h) + clamp(48px, 6.5vh, 84px)) var(--gutter)
    clamp(30px, 4vh, 52px);
  background: var(--bg);
  color: var(--fg);
}

.cv-hero .eyebrow {
  margin-bottom: clamp(18px, 2.4vw, 28px);
}

/*
  One notch below `--t-display`. The display size is the home page's opening claim and
  these headlines are instructions — "Tell us what you want to make." at 76px is a
  poster, and the reader is here to fill something in.
*/
.cv-hero h1 {
  font-size: var(--t-h2);
  max-width: 20em;
  margin-bottom: clamp(18px, 2.2vw, 26px);
}

.cv-hero .lead {
  max-width: 44em;
}

/*
  The line that lowers the cost of starting — "A complete tech pack is not required." on
  one page, the length of the call on the other. Body copy at small size rather than fine
  print: on both pages it is the sentence that answers "am I ready to do this yet", and a
  reader who skims the hero has to have taken it in.

  --fg-soft is 6.0:1 on white.
*/
.cv-hero-note {
  max-width: 42em;
  margin: clamp(14px, 1.6vw, 20px) 0 0;
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--fg-soft);
}

/* ═══ BODY ═══════════════════════════════════════════════════════════════ */

/*
  Explanation left, action right, exit under both — `.sup-grid`'s three-child shape and
  its ratio. The right column is the wider one on both pages: it holds eleven controls on
  one and a scheduler on the other, and the left holds four short paragraphs.

  Beside each other rather than stacked because the left column is the answer to the
  question a reader has while they are in the right one. On /brief that question is "what
  happens to this after I send it", and answering it one screen earlier is not answering
  it.

  `align-items: start` so the left column does not stretch to the panel's height and
  centre its own contents in the space.
*/
.cv-body {
  /*
    Less top padding than a standard section. The hero above it is part of the same white
    ground and already ends in space of its own, so a full `--section-y` on top of that
    left the working area a screen and a half down the page.
  */
  padding-top: clamp(40px, 4.5vw, 70px);
}

.cv-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(40px, 4.5vw, 84px);
  align-items: start;
}

/* ── The numbered column ─────────────────────────────────────────────── */

.cv-side h2 {
  font-size: clamp(25px, 2.3vw, 33px);
  max-width: 16em;
  margin-bottom: 18px;
}

.cv-side .lead {
  max-width: 32em;
  margin-bottom: clamp(26px, 3vw, 38px);
}

/*
  Four steps as hairline-separated rows with the number above the label.

  An `<ol>`, because on both pages the four are ordered — a sequence of events on one, an
  agenda on the other. The visible `01`–`04` is written into the markup rather than left
  to the list's own counter: every other number on this site is mono, uppercase-spaced and
  in route blue, and a browser's marker cannot be given that treatment. So the marker is
  off and `pad()` supplies the digits.

  Not cards. A card is a surface that invites a choice and nobody chooses between step 2
  and step 3; four bordered boxes here would also become the loudest thing on a page whose
  loudest thing has to be the panel beside them.
*/
.cv-steps {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cv-steps li {
  padding: clamp(16px, 1.6vw, 20px) 0 clamp(18px, 1.8vw, 22px);
  border-top: 1px solid var(--line);
}

.cv-steps li:last-child {
  border-bottom: 1px solid var(--line);
}

/* Stagger, one step per row — `--i` comes from the markup. `.sup-list`'s value. */
html[data-js='true'] .cv-steps li[data-reveal] {
  transition-delay: calc(var(--i) * 70ms);
}

/*
  Route blue, and the same mono treatment as `.ind-stage-code`. Blue is information and
  control everywhere in this system, which is what a step number is. Not the accent:
  orange means physical movement, and nothing has moved yet on either page.
*/
.cv-step-no {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--info);
}

.cv-steps h3 {
  font-size: var(--t-lead);
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 7px;
}

.cv-steps p {
  margin: 0;
  max-width: 32em;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg-sub);
}

/*
  The closing line of the column — the response commitment on /brief, the call's length on
  /book-a-call. Full `--fg` and body size, like `.sup-limit`: on both pages it is the
  promise the reader is being asked to act on, so it is set as a statement. No italics, no
  smaller size, no box.
*/
.cv-side-note {
  max-width: 32em;
  margin: clamp(20px, 2.2vw, 28px) 0 0;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg);
}

/* ── The exit line ───────────────────────────────────────────────────── */

/*
  The cross-link to the other page. Both columns wide and last in the grid, so it is the
  final thing on the page — it is an exit, and anywhere earlier it is an exit offered to
  somebody halfway through a form.

  The question takes the whole row and the link sits under it, which is `.sup-aside`'s
  arrangement and for the same reason: left to wrap, the line broke between the question
  and the link and orphaned the link.

  A `.link` and never a button. It must not compete with the submit action twenty pixels
  above it — the two pages are alternatives, not a fork the reader has to resolve.
*/
.cv-aside {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 24px;
  margin: clamp(10px, 1.6vw, 22px) 0 0;
  padding-top: clamp(28px, 3vw, 40px);
  border-top: 1px solid var(--line);
  font-size: var(--t-body);
  color: var(--fg-sub);
}

.cv-aside > p {
  flex: 0 0 100%;
  margin: 0;
}

/* ═══ THE PANEL ══════════════════════════════════════════════════════════ */

/*
  A bordered area of the page rather than a card floating on it: in the light theme
  `--bg-raised` is the same white as the ground and the separation is the hairline. See
  the note on `--bg-raised` in tokens.css.
*/
.cv-panel {
  padding: clamp(26px, 2.8vw, 42px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-raised);
}

.cv-panel h2 {
  font-size: clamp(25px, 2.3vw, 33px);
  margin-bottom: 10px;
}

.cv-panel-sub {
  font-size: var(--t-small);
  color: var(--fg-soft);
  margin-bottom: clamp(22px, 2.2vw, 28px);
}

/*
  The panel's own small print — "Fields marked with * are required." on the form, the
  call's length and medium on the scheduler. Sits directly above the thing it qualifies.
*/
.cv-note {
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-soft);
  margin: 0 0 clamp(22px, 2.2vw, 30px);
}

/* ═══ FIELDS ═════════════════════════════════════════════════════════════ */

/*
  Two columns for the short answers, one for anything that is a paragraph. `data-span`
  comes from the field's own declaration in content/brief.js, so a new field lands in the
  grid where its author put it rather than where its input type happens to fall.
*/
.cv-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px 22px;
  margin-bottom: clamp(24px, 2.4vw, 32px);
}

/* `p` and `fieldset` both — the attachments field is the one group on the form. */
.cv-field {
  margin: 0;
  /* A fieldset carries a border and padding by default. */
  padding: 0;
  border: 0;
  min-width: 0;
}

.cv-field[data-span='full'] {
  grid-column: 1 / -1;
}

/*
  Mono and uppercase, this design system's voice for a control label — the same voice as
  the buttons, the section eyebrows and the step numbers above. Small, because the answer
  matters more than the question.

  `.cv-label-text` and not the `<label>` itself: the label is a flex container at desktop
  width, and the required marker has to be inside the text run or it strands itself at the
  far right of the box when the label wraps. A `<legend>` takes the same class.
*/
.cv-field label,
.cv-label-text {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  /* Set explicitly: the two-line reservation below is measured in these lines. */
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-sub);
}

/* Nested, so the outer label's own margin is the only one. */
.cv-field label .cv-label-text {
  margin-bottom: 0;
}

/*
  A legend is not a block-level box in the ordinary sense and browsers give it a small
  inline inset of its own. Zeroed, so it starts on the same axis as the labels above it.
*/
.cv-field legend.cv-label-text {
  padding: 0;
}

/*
  The required marker, in the accent. Decoration — `required` and `aria-required` carry
  the fact. A margin rather than a space in the markup, because a leading space collapses
  inside a flex container.
*/
.cv-req {
  margin-left: 0.35em;
  color: var(--accent);
}

/*
  Two lines reserved for the label of a half-width field, text sitting at the bottom of
  them — `.sup-field`'s fix, needed here for the same reason and at the same width.
  "TARGET COST PER UNIT" wraps in a 280px column and "QUANTITY" beside it does not, which
  left the two inputs in that row out of line with each other. Reserving the line in both
  aligns every control in every row, whichever language is rendering and however a label
  breaks.

  Bottom-aligned, so a one-line label still sits directly above its own control and the
  spare line opens above the label rather than between the label and the field it names.
*/
@media (min-width: 981px) {
  .cv-field[data-span='half'] > label {
    display: flex;
    align-items: flex-end;
    min-height: 2.8em;
  }
}

.cv-control {
  display: block;
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.45;
  transition: border-color var(--dur-fast) var(--ease);
}

textarea.cv-control {
  /* Vertical only: a resizable width would break out of the grid column. */
  resize: vertical;
  min-height: 118px;
}

.cv-field ::placeholder {
  color: var(--fg-faint);
}

/*
  The border tracks focus as well as the global outline. The outline sits outside the
  control and the border is the control, so on a form of twelve of them the border is
  what tells you which one you are in.
*/
.cv-control:focus {
  border-color: var(--fg);
}

/*
  Native select chrome varies enough by platform that one control in twelve would look
  like it came from somewhere else. The arrow is drawn instead — inline SVG so it costs no
  request, and the right padding keeps a long option label clear of it. Same mark and same
  stroke colour as `.sup-field select`.
*/
select.cv-control {
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1l4.5 4.5L10 1' fill='none' stroke='%236d6d6d' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
  cursor: pointer;
}

/* ── Target cost: an amount and the currency it is in ────────────────── */

/*
  Two controls, one field, and the currency is never optional — a target cost without one
  is not a number anybody can act on. The select is sized to its content and the amount
  takes the rest, so the field is as wide as its neighbour in the row.

  The currency's own label is `.sr-only`: the select shows its answer (`EUR €`), and a
  visible "Currency" over three letters is a label naming what the reader can already
  read. It is still in the document, because a screen reader moving control to control
  meets an unlabelled select otherwise.
*/
.cv-money {
  display: flex;
  align-items: stretch;
  gap: 10px;
}

.cv-control--amount {
  flex: 1 1 auto;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

/*
  It is a `<label>` inside a `.cv-field`, so the field-label rule above matches it and wins
  on specificity — hence `label.cv-money-currency` rather than the class alone. It is a
  wrapper around a control, not a caption above one.
*/
.cv-field label.cv-money-currency {
  display: block;
  flex: 0 0 auto;
  margin-bottom: 0;
  text-transform: none;
  letter-spacing: normal;
}

.cv-money-currency .cv-control {
  width: auto;
  /* Room for the drawn arrow, which `select.cv-control` positions 15px from the edge. */
  min-width: 104px;
}

/* ── Attachments ─────────────────────────────────────────────────────── */

/*
  A dashed frame, the one place this site draws a dashed line — the same language as the
  routes in the diagrams: something in motion or not yet settled. The real file input
  covers the whole box at zero opacity, so a click or a drop anywhere in it lands on the
  input, and the label is what is seen.

  This is the only file input on the site — /product-cost-estimator has none, because it
  delivers its brief as a JSON lead or a mail draft and neither carries bytes. Kept short
  regardless: one optional field among eleven, not the centrepiece of the panel.
*/
.cv-field--files .cv-support {
  margin: -2px 0 12px;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-soft);
}

.cv-drop {
  position: relative;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--bg);
  transition:
    border-color var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}

.cv-file {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/*
  `label.cv-drop-face` and not the class alone: this is a `<label>` inside a `.cv-field`,
  so the field-label rule above matches it and wins on specificity. It is the input's
  visible face, not a caption, so none of that rule's mono-capital treatment applies.
*/
.cv-field label.cv-drop-face {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(20px, 2.4vw, 28px);
  text-align: center;
  cursor: pointer;
  margin-bottom: 0;
  font-family: var(--font-body);
  font-size: var(--t-body);
  text-transform: none;
  letter-spacing: normal;
  color: var(--fg-sub);
}

.cv-drop-browse {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.cv-drop:hover,
.cv-drop:has(.cv-file:focus-visible) {
  border-color: var(--fg-soft);
}

/*
  The focus ring, moved from the invisible input to the box the reader can see. Without
  this the field is keyboard-reachable and gives no sign of it, which is the one way this
  technique can go wrong.
*/
.cv-drop:has(.cv-file:focus-visible) {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

/* Dragging over it. Blue: this is the page offering to read something. */
.cv-drop[data-over='true'] {
  border-color: var(--info);
  border-style: solid;
  background: color-mix(in srgb, var(--info) 4%, var(--bg));
}

/*
  How many files are attached, in this page's language, written by js/brief.js from the
  templates on the element. It has a resting value ("No files attached yet") rather than
  shipping empty, because a count that appears only after a file is chosen reads as
  something having gone wrong the first time.
*/
.cv-drop-state {
  margin: 10px 0 0;
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--fg-soft);
}

.cv-file-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cv-file-list:not(:empty) {
  margin-top: 12px;
}

.cv-file-list li {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}

.cv-file-list li:first-child {
  border-top: 1px solid var(--line);
}

.cv-file-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-size: var(--t-small);
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg);
}

.cv-file-size {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  letter-spacing: 0.04em;
  color: var(--fg-soft);
  font-variant-numeric: tabular-nums;
}

/*
  Remove. A cross rather than the word, because the row it sits on is a filename that has to
  stay readable — and it is 32px square, so it is still a target. Its accessible name carries
  the filename, so ten of them are ten different buttons to anyone navigating by control.
*/
.cv-file-remove {
  flex: 0 0 auto;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: none;
  color: var(--fg-soft);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition:
    border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

.cv-file-remove:hover {
  border-color: var(--fg);
  color: var(--fg);
}

/* ── Hints and errors ────────────────────────────────────────────────── */

/*
  Under the control rather than beside the label. Every hint on this form exists to stop a
  reader withholding an answer — that an estimate is fine, that a range is fine, that a
  tech pack is not needed — and that is read after seeing the field, not before.

  Allowed at half width, unlike a placeholder: this wraps, where a placeholder is cut off
  mid-sentence by the browser. The grid row grows and the field beside it stays
  top-aligned.
*/
.cv-hint {
  display: block;
  margin-top: 7px;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-soft);
}

/*
  The validation message, written by js/brief.js into a slot that ships empty and
  `hidden` — so it costs no space until there is something to say, and a hidden element is
  correctly left out of the field's accessible description.

  The colour is local rather than a token for the reason `.sup-form` gives: `--lot-red` at
  full strength is 4.0:1 on white and fails AA at 14px, and 20% ink mixed into it gives
  5.7:1 — the same weight as `--info` and `--network` beside it. This form only ever sits
  on the white band; a dark-ground copy would need its own value.
*/
.cv-form {
  --cv-error: color-mix(in srgb, var(--lot-red) 80%, var(--ink));
}

.cv-error {
  display: block;
  margin-top: 7px;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--cv-error);
}

/*
  Keyed off `aria-invalid` rather than a class, so what a screen reader is told and what a
  sighted reader sees cannot come apart: one attribute drives both. `:not(:focus)` because
  inside the field the focus border is the more useful signal and the message underneath
  has not gone anywhere.
*/
.cv-field [aria-invalid='true']:not(:focus) {
  border-color: var(--cv-error);
}

/*
  The attachments input has no border of its own to colour — it is the transparent layer
  over the drop zone — so the state goes on the frame the reader can actually see.
*/
.cv-field--files:has([aria-invalid='true']) .cv-drop {
  border-color: var(--cv-error);
  border-style: solid;
}

/* ═══ UNDER THE FIELDS ═══════════════════════════════════════════════════ */

.cv-consent {
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-soft);
  margin: 0 0 clamp(22px, 2.2vw, 28px);
  max-width: 44em;
}

.cv-submit {
  min-height: 54px;
  padding: 0 32px;
}

/*
  Disabled, which on these two pages is a real state and not an edge case: the submit
  button while no endpoint is configured, and the scheduler's button while no calendar is.

  Outlined rather than filled, so it reads as a control that is not available instead of a
  filled button that ignores a press — and `not-allowed` plus `aria-describedby` on the
  markup point at the note that says why. The 6.0:1 `--fg-soft` on the label is
  deliberate: "disabled" is not a reason to make a word unreadable, and on both pages the
  word is the explanation.
*/
.cv-body .btn:disabled {
  background: none;
  border-color: var(--line-strong);
  color: var(--fg-soft);
  cursor: not-allowed;
}

.cv-body .btn:disabled:hover {
  background: none;
  border-color: var(--line-strong);
  color: var(--fg-soft);
}

/*
  The one live region on the form, written by js/brief.js — the validation summary when a
  submit is refused, the sending line while an upload is in flight, the failure when one
  comes back. Empty in the document, so it draws no rule and takes no space until there is
  something to say: a form shipping an empty bordered box under its button looks broken
  before it is used.

  Three states, one shape, differing in the rule's colour. They are the same kind of
  thing — the form reporting what just happened — and giving the refusal a panel of its
  own would make the page rearrange itself around a typo.
*/
.cv-status {
  margin: 22px 0 0;
  padding-left: 16px;
  border-left: 2px solid var(--info);
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg);
}

.cv-status[data-state='error'] {
  border-left-color: var(--cv-error);
}

.cv-status:empty {
  display: none;
}

/* ═══ SUCCESS ════════════════════════════════════════════════════════════ */

/*
  Replaces the form inside the same panel, so the page does not jump and the reader's
  place on it is preserved. `hidden` in the document and revealed by js/brief.js on a 2xx
  and on nothing else — there is no path on which this appears for a brief that went
  nowhere.

  A heading and two sentences. No confetti, no illustration, and no redirect to a
  calendar: the reader has just been told a human will read this and reply within a
  working day, and immediately asking them to book a meeting says the opposite.
*/
.cv-success {
  /* Roughly the form's own height, so a short panel does not collapse the layout. */
  min-height: 220px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.cv-success h2 {
  margin-bottom: 14px;
}

.cv-success .lead {
  margin-bottom: 12px;
  max-width: 32em;
}

.cv-success p:last-child {
  margin: 0;
  max-width: 32em;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg-sub);
}

/* ═══ THE SCHEDULER, ON /book-a-call ═════════════════════════════════════ */

/*
  The same panel, holding either the placeholder or the embed, and in both states it is
  exactly as tall as what is in it.

  It used to carry a `min-height` floor of 340–440px, which was there to stop a short
  placeholder reading as an afterthought beside a tall left column — and it worked only
  because a disabled button was pinned to the bottom of that space. With the button gone
  the floor is an empty half-panel, so it is gone too. The embed branch never needed it:
  `.cv-calendar` below floors the iframe on its own.
*/
.cv-panel--calendar .eyebrow {
  margin-bottom: clamp(16px, 1.8vw, 22px);
}

.cv-panel--calendar .lead {
  max-width: 30em;
  margin-bottom: 0;
}

.cv-panel--calendar .cv-note {
  margin-top: 14px;
  margin-bottom: 0;
}

/*
  Calendly's inline widget, in the branch that only renders when a real URL is configured.
  Its own script sizes the iframe; the height here is the floor it starts from, so the
  panel does not collapse and then jump while the widget loads.
*/
.cv-calendar {
  min-height: 640px;
  margin-top: clamp(18px, 2vw, 26px);
}

/*
  The named link to the same event, for a reader whose tracker blocker took the loader out
  and left an empty container — which is common enough that the empty container is the
  expected failure, not an unlikely one.
*/
.cv-fallback {
  margin: clamp(22px, 2.4vw, 30px) 0 0;
  padding-top: clamp(18px, 2vw, 24px);
  border-top: 1px solid var(--line);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--fg-soft);
}

.cv-fallback strong {
  color: var(--fg);
  font-weight: 600;
}

.cv-fallback a {
  color: var(--info);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* ═══ /contact ═══════════════════════════════════════════════════════════ */

/*
  The same working area with the two tracks reversed and reweighted.

  On /brief and /book-a-call the right column is the wider one, because it holds the work —
  eleven controls or a scheduler — and the left holds four short paragraphs explaining it.
  /contact has no explanation to give: the hero already said what the inbox is for in two
  sentences, and there is no numbered sequence because nothing sequential happens to a
  message. So the work moves left, where a reader starts, and what is left over on the
  right is the address and the two exits for a reader who is on the wrong page.

  1.4fr / 1fr is roughly the 60/40 the page was asked for, expressed the way `.cv-grid`
  expresses its own ratio so the two rules read as variants rather than as two systems.
  The alternative — `flex-direction: row-reverse` on the existing grid, or ordering the
  aside first in the markup and re-ordering it visually — puts the form second in the
  reading and tab order to win a column position, which is the one thing this page cannot
  afford: the form *is* the page, and a keyboard reader should reach it first.

  ── Why this sits above the NARROW section and not inside it ────────────────

  `.cv-grid--contact` is one class, so it has exactly the specificity of the
  `@media (max-width: 980px) { .cv-grid { … } }` rule that collapses the grid to one
  column. Two selectors of equal specificity are resolved by source order, so this must be
  *declared first* or the two-column rule survives the collapse and the page ships with a
  260px-wide form on a phone. The alternative is naming `.cv-grid--contact` in that media
  query too, which works and is one more place to forget. Order is the cheaper guarantee,
  and this comment is why the section is not filed with the rest of the page-specific
  rules further up.

  The collapse then needs nothing else: the panel is already first in the markup, so one
  column reads hero → form → address → exits, which is the order a reader on a phone wants
  and the reason the source order was chosen that way in build.mjs.
*/
.cv-grid--contact {
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}

/* ── The address ─────────────────────────────────────────────────────── */

/*
  A real `<address>`, which browsers italicise by default. Italics here would read as an
  aside about the company rather than as its address, and nothing else on this site is set
  in italics.
*/
.cv-contact address {
  font-style: normal;
}

.cv-contact .eyebrow {
  margin-bottom: clamp(16px, 1.8vw, 22px);
}

/*
  The registered name at lead size and semibold — it is the heading of this block, and it
  cannot be an `<h2>` because the panel beside it already holds the page's only h2 and a
  heading here would put "Library of Trade B.V." above "Send us a message" in a document
  outline that is meant to say the form is the point.
*/
.cv-contact-company {
  margin: 0 0 6px;
  font-size: var(--t-lead);
  font-weight: 600;
  line-height: 1.35;
}

.cv-contact-postal {
  margin: 0 0 clamp(18px, 2vw, 24px);
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg-sub);
}

.cv-contact-links {
  list-style: none;
  display: grid;
  gap: 9px;
  margin: 0;
  padding: 0;
}

/*
  `.learn-body a`'s underline, which is how this site draws a marked inline link
  everywhere: full `--fg` on a word with a blue rule under it, the blue moving up into the
  text on hover and on focus. Underlined at rest and not only on hover, because these are
  the two things on this page a reader may have come for and a link that only announces
  itself under a cursor announces itself to nobody on a phone.

  `border-bottom` rather than `text-decoration` for the same reason learn.css gives: the
  rule differs in colour from the text above it. Not `.cv-next-action`'s blue-with-an-arrow
  either — an arrow means "this navigates", and these two act here: one opens a mail
  client, the other dials.
*/
.cv-contact-links a {
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--info);
  transition:
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

.cv-contact-links a:hover,
.cv-contact-links a:focus-visible {
  color: var(--info);
}

/* ── Right place? ────────────────────────────────────────────────────── */

/*
  The two exits, under a hairline. Same device as `.cv-aside` on the other two pages and
  for the same reason — an exit is separated from the work rather than mixed into it — but
  vertical here, because it is in a column instead of across the foot of the page.
*/
.cv-next {
  margin-top: clamp(30px, 3.4vw, 44px);
  padding-top: clamp(24px, 2.8vw, 34px);
  border-top: 1px solid var(--line);
}

.cv-next .eyebrow {
  margin-bottom: clamp(16px, 1.8vw, 22px);
}

/*
  The reader's case, then the destination under it. Two lines rather than one sentence with
  a link in it, because "Already have a sourcing project? Start a free brief →" on one line
  wraps unpredictably and orphans the arrow, and because the question is the part that has
  to be scanned: a reader is looking for their own situation, not for a link.
*/
.cv-next-lead {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg-sub);
}

.cv-next-row {
  margin: 7px 0 clamp(18px, 2vw, 24px);
}

/*
  Quiet links, never buttons — content/contact.js argues that at length. Blue because blue
  is navigation and information in this system, and the arrow is `::after` content so no
  translator has to carry a glyph through a string. `.link`'s hover shift, without
  `.link`'s uppercase mono border-bottom: that treatment is a call to action, and these are
  redirections for somebody who is in the wrong place.
*/
.cv-next-action {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: var(--t-body);
  line-height: 1.5;
  color: var(--info);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}

.cv-next-action::after {
  content: '→';
  transition: transform var(--dur-fast) var(--ease);
}

.cv-next-action:hover {
  color: var(--fg);
}

.cv-next-action:hover::after {
  transform: translateX(3px);
}

/*
  The supplier route, one step quieter again and behind its own hairline: it is not a third
  answer to "is this the right place for my message" but a different reader entirely, and
  at the same weight as the two above it would read as a third option a buyer has to
  dismiss.
*/
.cv-next-foot {
  margin: 0;
  padding-top: clamp(16px, 1.8vw, 22px);
  border-top: 1px solid var(--line);
}

.cv-next-foot .cv-next-action {
  font-size: var(--t-small);
  color: var(--fg-soft);
}

.cv-next-foot .cv-next-action:hover {
  color: var(--fg);
}

/* ═══ NARROW ═════════════════════════════════════════════════════════════ */

/*
  One column, explanation first. On /brief that is the hero, then what happens next, then
  the form — the order the brief asks for and the order the markup is already in, so this
  is the column count changing and nothing else. Same on /book-a-call: hero, what we
  discuss, then the scheduler.
*/
@media (max-width: 980px) {
  .cv-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(38px, 6vw, 56px);
  }

  .cv-side h2,
  .cv-side .lead {
    max-width: 30em;
  }

  /*
    The gap between the columns is now the gap between the explanation and the panel, and
    the aside's own top rule already separates it — so it takes the smaller of the two
    margins rather than a second full gap.
  */
  .cv-aside {
    margin-top: 0;
  }
}

@media (max-width: 560px) {
  /* Two short fields side by side leave neither of them room to be read. */
  .cv-fields {
    grid-template-columns: minmax(0, 1fr);
  }

  /*
    The panel loses its border and becomes the page. At a 22px gutter a bordered box
    inside it spends 26px a side on a frame that separates it from nothing.

    The calendar panel keeps its border: unlike the form it is a discrete object with a
    hard edge of its own, and the whole point of it is that it is a bounded thing waiting
    to be filled.
  */
  .cv-panel:not(.cv-panel--calendar) {
    padding: 0;
    border: 0;
    border-radius: 0;
  }

  .cv-panel--calendar {
    padding: clamp(20px, 5vw, 26px);
  }

  /* The amount and its currency, one above the other. Side by side, the select's
     104px floor left the amount field narrower than the number it holds. */
  .cv-money {
    flex-wrap: wrap;
  }

  .cv-money-currency .cv-control {
    min-width: 0;
  }

  .cv-money-currency {
    flex: 1 1 100%;
  }
}
