/* Bocas & Dientes, Nou Barris - site stylesheet.
   The only stylesheet written by hand for this site. ds.css, booking.css and
   media.css are shared and load AFTER this file, so anything that has to win
   against them is written at a higher specificity and says why in a comment.

   Design read: a first-presence pitch for the best-photographed and best-rated
   clinic of the four, with a bright, warm-wood language, leaning toward
   pistachio green and honey pine on an off-white ground.

   THE COLOUR CORRECTION THAT SHAPES THIS FILE. The research file calls the
   corridor "sage-green". It is not sage. Sage is a grey-green; the wall in
   assets/photos/g3.jpg reads as a warm YELLOW-green, closer to pistachio, and
   samples at about #7D8032 in shade against a much warmer yellow-green in full
   light. Building this page on a sage token would quietly turn the one clinic
   in the set that owns a real colour into another spa-adjacent dental site, so
   the accent below is a deepened version of the actual wall and never drifts
   toward teal. Honey pine and blond oak appear in the photographs and as one
   hairline rule; there is no CSS wood grain and no beige panel anywhere. */

/* ---------------------------------------------------------------- tokens --
   :root would lose to booking.css and ds.css, which load later and declare on
   :root themselves. html:root is one specificity step higher, so these values
   hold wherever they have to. */
html:root {
  /* This page is light whatever the system setting is, and the browser has to
     be told so, or Chrome draws the dark-scheme calendar glyph inside the date
     field and opens a dark select popup against a pale page. */
  color-scheme: light;

  /* Palette. Nothing here is pure black or pure white. Every ratio was
     computed against the surface named beside it, not estimated. */
  --ground: #F4F6EE;       /* pale ground with a green cast, off the corridor light */
  --ink: #1F2418;          /* 14.55:1 on the ground */
  --muted: #4F5745;        /* 6.93:1 on the ground, so it is body-legible */
  --accent: #55721A;       /* the corridor green, deepened. 5.06:1 on the ground,
                              which makes it legal as body-size link text */
  --accent-deep: #445C13;  /* the pressed and hovered CTA, 6.91:1 on the ground */
  --accent-ink: #FAFCF5;   /* CTA label on the accent, 5.33:1 */

  --panel: #FBFCF6;        /* raised surface: the form, the ledger */
  --bg-2: #E7EBDC;         /* read by media.css behind a photograph while it loads */
  --bg-3: #EDF0E2;         /* read by booking.css for input fills */

  /* Two line weights and the difference is not decorative. --line bounds a
     component a person has to find and hit, so it is measured: 3.57:1 on the
     ground and 3.37:1 on the input fill, both over the 3:1 a component
     boundary needs. booking.css reads it for the textarea, the confirmation
     panel and the WhatsApp note. --line-soft is a hairline between rows of a
     document and is never a boundary. */
  --line: #788663;
  --line-soft: #C9D0B8;

  /* The one honey-pine value on the page, and it is a 2px rule under the
     ledger heading and nothing else: 3.14:1 on the ground, which is a rule and
     not a text colour. Wood is a photograph here, not a surface. */
  --wood: #B08246;

  /* Error text. booking.css declares --bad on a bare :root and then swaps it
     to a light salmon under prefers-color-scheme: dark. This page is light
     whatever the system setting is, and that salmon measures 1.83:1 on this
     ground, so the value is pinned here at a specificity a bare :root inside a
     media query cannot beat. 6.00:1 on the ground, 5.65:1 on an input fill. */
  --bad: #B3261E;

  /* One corner radius system, two steps, held everywhere. Second-roundest in
     the set, and it comes from the rooms: moulded chairs, a rounded counter
     nose, cabinetry with no sharp edge in it. */
  --r-s: 10px;
  --r-m: 18px;

  /* Measured, not assumed: the language switcher is the tallest thing in the
     header (44px links inside 2px of padding and a 1px border) and the row
     adds 0.55rem top and bottom, which comes to 68px at every width. The hero
     subtracts it to fill exactly one screen and ds.css uses it to keep a
     focused control from landing under the sticky header. */
  --nav-h: 68px;
  --wrap: 72rem;

  /* Interaction dials, read by ds.css. A moderate settle: this clinic is a
     calm room, not a quick one. */
  --ds-lift: 3px;
  --ds-dur: 0.2s;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* The most comfortable body setting in the set, on purpose: Hanken Grotesk
     is humanist and generous and this is the page with the most to read. */
  font-size: 1.0938rem;
  line-height: 1.72;
  font-weight: 400;
  /* Nothing on this page may cause a sideways scroll at 360px. The full-bleed
     corridor plate is the one element that could, and this is its floor. */
  overflow-x: hidden;
}

h1, h2, h3, .display {
  font-family: "Rubik", "Hanken Grotesk", system-ui, sans-serif;
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.015em;
  margin: 0;
}

p { margin: 0; }
a { color: var(--accent); }
img { max-width: 100%; }

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, 4vw, 3rem);
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Icons from assets/social.svg. Always beside a word, never instead of one. */
.si {
  flex: none;
  width: 1em;
  height: 1em;
  fill: currentColor;
}

/* ---------------------------------------------------------------- reveal --
   The floor under motion.js. If Motion never loads this transition is what
   reveals the page; ds.css switches it off when motion.js takes over. The
   rise matches data-ds-rise so the two paths look the same. */
.rv {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.5s cubic-bezier(0.22, 0.75, 0.3, 1),
              transform 0.5s cubic-bezier(0.22, 0.75, 0.3, 1);
}
.rv.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .rv { opacity: 1; transform: none; transition: none; }
}

/* --------------------------------------------------------------- buttons -- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 48px;
  padding: 0.7rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--r-s);
  font-family: "Rubik", "Hanken Grotesk", system-ui, sans-serif;
  font-weight: 500;
  font-size: 1rem;
  letter-spacing: 0.005em;
  text-decoration: none;
  cursor: pointer;
  background: none;
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn-primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

.btn-ghost {
  background: var(--panel);
  border-color: var(--line);
  color: var(--ink);
}
.btn-ghost:hover { background: var(--bg-3); }

/* aria-disabled rather than disabled: site.js leaves the control reachable so
   a reader can still hear why the WhatsApp route is not open yet. */
.btn[aria-disabled="true"] { color: var(--muted); cursor: default; }

/* ---------------------------------------------------------------- header -- */
.site-head {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--ground) 90%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--line-soft);
}
@supports not (backdrop-filter: blur(2px)) {
  .site-head { background: var(--ground); }
}

.head-row {
  min-height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: clamp(0.6rem, 2vw, 2rem);
  padding-block: 0.55rem;
}

.brand {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 44px;
  line-height: 1.15;
  text-decoration: none;
  color: inherit;
  margin-inline-end: auto;
}
.brand b {
  font-family: "Rubik", sans-serif;
  font-weight: 600;
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
}
.brand span {
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
/* The neighbourhood drops out of the header below 420px and nowhere else.
   Rendered at 360px the full descriptor wrapped onto a third line, which grew
   the header past the 68px --nav-h the hero subtracts. Nou Barris is still on
   the page three times: the hero, the ledger and the footer address. */
@media (max-width: 419px) {
  .brand-hood { display: none; }
}

/* The in-page nav is desk-only. At phone width the four sections are a short
   scroll and the sticky booking CTA is the only navigation that earns a tap
   target up there. */
.head-nav { display: none; gap: 0.25rem; }
.head-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 0.7rem;
  color: var(--ink);
  text-decoration: none;
  font-size: 0.95rem;
}
.head-nav a:hover { color: var(--accent); }

.lang {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  background: var(--panel);
}
.lang a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  /* Still the one radius system: an inner corner inside a 2px-padded shell is
     the outer radius minus that padding, which is what keeps the two curves
     concentric instead of introducing a third value. */
  border-radius: calc(var(--r-s) - 2px);
  font-family: "Rubik", sans-serif;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--muted);
}
.lang a:hover { color: var(--ink); }
.lang a[aria-current="true"] {
  background: var(--ink);
  color: var(--ground);
}

.nav-cta {
  display: none;
  align-items: center;
  min-height: 44px;
  padding: 0 1.1rem;
  border: 1px solid var(--accent);
  border-radius: var(--r-s);
  background: var(--accent);
  color: var(--accent-ink);
  font-family: "Rubik", sans-serif;
  font-weight: 500;
  font-size: 0.95rem;
  text-decoration: none;
}
.nav-cta:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

@media (min-width: 860px) {
  .head-nav { display: flex; }
  .nav-cta { display: inline-flex; }
}

/* --------------------------------------------------------------- section -- */
.sec { padding-block: clamp(3.5rem, 7.5vw, 6.5rem); }

.sec-head { max-width: 46rem; }
.sec-head h2 { font-size: var(--step-3); }
.sec-head p {
  margin-top: 1rem;
  color: var(--muted);
  max-width: 62ch;
}

/* ---------------------------------------------- family 1: the hero split --
   Type on the pale ground, the signage-bearing room as a wide plate beside
   it. Four text elements, headline two lines at every width, the booking
   button inside the first screen. Measured at 390px and at 1280px. */
.hero {
  min-height: calc(100dvh - var(--nav-h));
  display: flex;
  align-items: center;
  padding-block: clamp(2rem, 5vw, 4rem);
}

.hero-grid {
  width: 100%;
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: center;
}

.hero h1 {
  /* Rubik 600, and these are read off the rendered page rather than guessed.
     At 360px and at 390px the name sets on ONE line; at 1280px and 1440px the
     column is narrower than the text and it breaks to "Bocas & / Dientes",
     which is two. Two lines is the ceiling the brief sets and the wide end is
     where it is spent. */
  font-size: clamp(2.4rem, 1.9rem + 2.6vw, 4.25rem);
  line-height: 1.08;
  letter-spacing: -0.025em;
}

.hero-lede {
  margin-top: 1.15rem;
  font-size: clamp(1.05rem, 1rem + 0.4vw, 1.25rem);
  color: var(--muted);
  max-width: 34rem;
}

/* The location and the score sit BELOW the headline and lede on purpose.
   Above the headline either one would read as an eyebrow, and this page
   spends none at all. */
.hero-where {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.7rem;
  margin-top: 1rem;
  font-size: 0.95rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* The separator is glued to the second item rather than being an item of its
   own, so it can never be stranded at the END of a wrapped line. */
.hero-where span + span::before {
  content: "\00B7";
  margin-inline-end: 0.7rem;
  color: var(--line);
}
/* Gluing it forward solves one half and creates the other, which only showed
   up on the rendered page: at 390px the row wraps, the score drops to its own
   line and the dot goes with it, so the line opens with a bullet nobody asked
   for. Below the width where both facts fit on one line they are two lines and
   there is no separator at all. Measured at 390px and at 360px. */
@media (max-width: 559px) {
  .hero-where { display: block; }
  .hero-where span { display: block; }
  .hero-where span + span { margin-top: 0.15rem; }
  .hero-where span + span::before { content: none; }
}

.hero-actions {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
}
.hero-actions .btn { flex: 1 1 13rem; }

/* Deliberately NOT called .hero-photo. media.css attaches its 9s m-drift
   scale to `.hero-photo > .m-photo`, and motion.js separately scroll-links a
   scale to the first `.hero img`. One settle on the hero is hierarchy; two
   fighting over the same transform is a bug waiting for a slow machine. */
.hero-plate {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-m);
  background: var(--bg-2);
  aspect-ratio: 4 / 3;
}
.hero-plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The file is a 1200x1200 square and the wordmark "Bocas & Dientes" sits in
     its upper right quadrant. A 4:3 crop takes a band out of the middle, so
     the crop is pulled up and right to keep the lettering, which is the whole
     reason this is the hero photograph. Checked against the rendered crop at
     390px and 1280px, not inferred from the file. */
  object-position: 72% 38%;
  border-radius: var(--r-m);
}

@media (min-width: 880px) {
  .hero-grid { grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); }
  .hero-plate { aspect-ratio: 5 / 4; }
}

/* ------------------------------------------ family 2: the corridor plate --
   The full-bleed image, and the largest thing on the page. It is where the
   green argument is made, so it is not in the wrap and nothing is laid over
   it. No parallax: the file is 1024px wide and pushing it around would only
   show how far it is being enlarged. */
.corridor { padding-block: 0 clamp(2.5rem, 5vw, 4rem); }

.corridor-plate {
  margin: 0;
  width: 100%;
  overflow: hidden;
  background: var(--bg-2);
  /* Height rather than aspect-ratio, because at 1280px a 4:3 plate would be
     960px tall and push everything under it off the screen. */
  height: clamp(300px, 62vh, 640px);
}
.corridor-plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 58%;
}

.corridor-note {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  display: grid;
  gap: 1rem clamp(2rem, 5vw, 4rem);
  align-items: start;
}
.corridor-note h2 { font-size: var(--step-3); }
.corridor-note p { color: var(--muted); max-width: 62ch; }
.corridor-note .frame-note {
  font-size: 0.9rem;
  color: var(--muted);
  border-inline-start: 2px solid var(--line-soft);
  padding-inline-start: 0.8rem;
  max-width: 44ch;
}

@media (min-width: 880px) {
  .corridor-note { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); }
  .corridor-note h2 { grid-row: span 2; }
}

/* --------------------------------------------- family 3: the rooms two-up --
   Two plates of unequal height rather than a pair of matched cards: a small
   photograph beside a taller column of plain speech about what the first
   visit is. The container also carries .info-grid, which is a name motion.js
   already staggers, so the two plates arrive in sequence instead of landing
   as one block. That is storytelling, not decoration.

   At 360px it is one column and the photograph comes first. */
.rooms-grid {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3rem);
  align-items: start;
}

.room-plate {
  margin: 0;
  overflow: hidden;
  border-radius: var(--r-m);
  background: var(--panel);
  border: 1px solid var(--line-soft);
  padding: clamp(0.6rem, 1.4vw, 0.9rem);
}
.room-plate img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  /* The small step of the same system, nested inside the large one. */
  border-radius: var(--r-s);
  background: var(--bg-2);
}
.room-plate figcaption {
  margin-top: 0.75rem;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--muted);
}

.rooms-body h2 { font-size: var(--step-3); }
.rooms-body .lead {
  margin-top: 1rem;
  color: var(--muted);
  max-width: 62ch;
}

/* Three rows of a document, not three cards. Each row is a name and a line of
   plain speech, separated by one hairline weight. */
.visit-list {
  margin: clamp(1.5rem, 3vw, 2rem) 0 0;
  display: grid;
  gap: 0;
}
.visit-list dt {
  font-family: "Rubik", sans-serif;
  font-weight: 500;
  font-size: 1.05rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line-soft);
}
.visit-list dt:first-of-type { border-top: 0; padding-top: 0; }
.visit-list dd {
  margin: 0.25rem 0 1rem;
  color: var(--muted);
  max-width: 56ch;
}
.visit-list dd:last-of-type { margin-bottom: 0; }

.quote-note {
  margin-top: clamp(1.5rem, 3vw, 2rem);
  padding: 1rem 1.15rem;
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  background: var(--panel);
  font-size: 0.95rem;
  color: var(--ink);
  max-width: 56ch;
}

@media (min-width: 880px) {
  /* Unequal columns and unequal heights: the photograph is the short plate and
     sits low against a taller column of text, so this never reads as a pair of
     matched cards. */
  .rooms-grid { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
  .room-plate { margin-top: clamp(2rem, 5vw, 4.5rem); }
}

/* --------------------------------------------------- family 4: the ledger --
   Hours on the left, the verified figures and how to get there on the right,
   under one thin honey-pine rule. The Saturday row is the standout, and it is
   marked by a word and a weight as well as by the accent, because status is
   never carried by colour alone. */
.ledger { background: var(--panel); border-block: 1px solid var(--line-soft); }

.ledger-head { max-width: 46rem; }
.ledger-head h2 { font-size: var(--step-3); }
.ledger-head::after {
  content: "";
  display: block;
  width: 72px;
  height: 2px;
  margin-top: 1.1rem;
  background: var(--wood);
}

.ledger-grid {
  margin-top: clamp(2rem, 4vw, 3rem);
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}

.hours {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(6.5rem, auto) 1fr;
  gap: 0;
  font-variant-numeric: tabular-nums;
}
.hours dt, .hours dd {
  margin: 0;
  padding-block: 0.7rem;
  border-top: 1px solid var(--line-soft);
}
.hours dt { color: var(--muted); }
.hours dd { text-align: end; }
.hours dt:first-of-type, .hours dd:first-of-type { border-top: 0; }
.hours .off { color: var(--muted); }

/* Saturday. The accent, a heavier weight and the word itself, so the row is
   still the standout in greyscale and to a screen reader. */
.hours .sat { color: var(--accent); font-weight: 500; }
.hours dd.sat { font-weight: 600; }

.hours-note {
  margin-top: 1.1rem;
  font-size: 0.92rem;
  color: var(--muted);
  max-width: 48ch;
}

.score {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.8rem;
}
.score .k {
  font-family: "Rubik", sans-serif;
  font-weight: 600;
  font-size: clamp(2rem, 1.6rem + 1.6vw, 2.9rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.score .v { color: var(--muted); font-size: 0.98rem; }

.addr {
  margin-top: clamp(1.5rem, 3vw, 2rem);
  font-style: normal;
  line-height: 1.7;
}
.addr .street { font-weight: 500; }
.addr p { margin-top: 0.5rem; color: var(--muted); font-size: 0.95rem; }

.contact-list {
  margin: clamp(1.25rem, 2.5vw, 1.75rem) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.35rem;
}
.contact-list a {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 44px;
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.contact-list a[href] { border-bottom-color: currentColor; }
/* site.js strips the href from anything the clinic has not confirmed, so the
   label has to still read correctly as plain text. Without this it would keep
   the accent and look like a link that does nothing. */
.contact-list a:not([href]) { color: var(--muted); cursor: default; }
.contact-list .aside {
  display: block;
  font-size: 0.88rem;
  color: var(--muted);
  max-width: 40ch;
}

@media (min-width: 880px) {
  .ledger-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* ----------------------------------------------------- family 5: the form --
   On the plain ground, not on a panel, so the page ends where it began. */
.book-grid {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
.book-aside h2 { font-size: var(--step-3); }
.book-aside p {
  margin-top: 1rem;
  color: var(--muted);
  max-width: 34rem;
}

.tel-big {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 48px;
  margin-top: 1.25rem;
  font-family: "Rubik", sans-serif;
  font-weight: 600;
  font-size: 1.35rem;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 2px solid currentColor;
}

.form {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  padding: clamp(1.15rem, 3vw, 1.9rem);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  /* booking.css gives .form textarea `border-radius: inherit`, so the radius
     the textarea uses is the one declared here. */
  border-radius: var(--r-s);
}
.field label {
  font-family: "Rubik", sans-serif;
  font-size: 0.86rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}
.field input,
.field select {
  font: inherit;
  font-size: 1rem;
  min-height: 48px;
  padding: 0.6rem 0.85rem;
  color: var(--ink);
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  width: 100%;
  min-width: 0;
}
.field select {
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 1.15rem) 55%, calc(100% - 0.8rem) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-inline-end: 2.4rem;
}
.field textarea { width: 100%; }
.field-msg { margin: 0.15rem 0 0; }

.field-hint {
  margin: 0.1rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.form-error, .form-ok {
  grid-column: 1 / -1;
  display: none;
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
}
.form-error {
  color: var(--bad);
  border-inline-start: 3px solid var(--bad);
  padding-inline-start: 0.8rem;
}
.form-error a { color: var(--bad); font-weight: 600; }
.form-ok {
  color: var(--ink);
  border-inline-start: 3px solid var(--accent);
  padding-inline-start: 0.8rem;
}
.form-error.show, .form-ok.show { display: block; }

.book-note {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.86rem;
  color: var(--muted);
}

#book-result { border-radius: var(--r-m); margin-top: 1rem; }

@media (min-width: 880px) {
  .book-grid { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
  .form { grid-template-columns: 1fr 1fr; }
  .field.full { grid-column: 1 / -1; }
}

/* ---------------------------------------------------------------- footer -- */
.site-foot {
  border-top: 1px solid var(--line-soft);
  padding-block: clamp(2.5rem, 5vw, 3.5rem);
  font-size: 0.95rem;
}

.foot-grid {
  display: grid;
  gap: 1.25rem;
  align-items: start;
}
.foot-brand {
  font-family: "Rubik", sans-serif;
  font-weight: 600;
  font-size: 1.05rem;
}
.site-foot address { font-style: normal; color: var(--muted); margin-top: 0.3rem; }

.foot-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 1.25rem;
}
.foot-links a {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 44px;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.foot-links a[href] { border-bottom-color: currentColor; }
.foot-links a:not([href]) { color: var(--muted); cursor: default; }

.demo-notes {
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
  padding-top: clamp(1.25rem, 2.5vw, 1.75rem);
  border-top: 1px solid var(--line-soft);
  display: grid;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.86rem;
  line-height: 1.65;
  max-width: 68ch;
}

@media (min-width: 720px) {
  .foot-grid { grid-template-columns: 1fr auto; }
  .foot-links { justify-content: flex-end; }
}
