/* =================================================================================================
   offer.css — the welcome-offer component, in every placement it has.

   Markup: `sites/bochic/src/shell/offer.ts`. Placements: announcement, hero, story, footer, dialog,
   launcher. One component, one set of classes, one look — a placement changes the frame, never the
   voice.

   ## Rules this file keeps

   - **Single-class selectors, no `!important`, no id selectors.** `app.css` is the design lane's file
     and it must win on specificity wherever it says something different. Load order therefore does
     not matter either way.
   - **Nothing here paints a second sticky bar.** `.offer-launcher` is a small corner control, never
     full width, and it sits BELOW `--z-sticky` so any real bottom bar covers it. On a narrow viewport
     it shrinks rather than spreading.
   - **Tokens only** (`tokens.css`), with a literal fallback behind each so a surface that somehow
     loads this file without the token sheet still renders.
   - Motion respects `prefers-reduced-motion`; every interactive target is at least `--target-min`.
   ================================================================================================= */

/* ------------------------------------------------------------------ shared form ---------------- */

.offer-form {
  display: flex;
  gap: var(--s-2, 8px);
  flex-wrap: wrap;
  align-items: stretch;
}

.offer-form__field {
  flex: 1 1 220px;
  min-width: 0;
  min-height: var(--target-min, 44px);
  padding: 0 var(--s-3, 12px);
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: var(--fs-body, 1rem);
  color: var(--c-ink, #1f1d1a);
  background: var(--c-paper-raised, #ffffff);
  border: 1px solid var(--c-line-strong, #bdb3a4);
  border-radius: var(--r-none, 0);
}

.offer-form__field:focus-visible {
  outline: var(--focus-ring, 2px solid #1f1d1a);
  outline-offset: 2px;
}

.offer-form__submit {
  min-height: var(--target-min, 44px);
  flex: 0 0 auto;
}

/* A submission in flight: the button says so and cannot be pressed again. The rule is cosmetic —
   the controller sets `disabled`, which is what actually prevents the duplicate submit. */
.offer-form__submit[disabled] {
  opacity: 0.6;
  cursor: progress;
}

/* ------------------------------------------------------------------ inline placements ---------- */

.offer-inline {
  display: block;
  max-width: var(--max-narrow, 760px);
  margin-inline: auto;
}

.offer-inline__title {
  font-family: var(--font-display, "Fraunces", Georgia, serif);
  font-size: var(--fs-display-s, 1.375rem);
  line-height: var(--lh-display-s, 1.22);
  font-weight: var(--fw-display-s, 400);
  color: var(--c-ink, #1f1d1a);
  margin: 0 0 var(--s-3, 12px);
}

.offer-inline__lede {
  margin: 0 0 var(--s-4, 16px);
  font-size: var(--fs-body, 1rem);
  line-height: var(--lh-body, 1.6);
  color: var(--c-ink-soft, #4f4942);
}

/* The second ask on a page is quieter than the first: same component, less weight, because two asks
   at equal volume read as one nag rather than two invitations. */
.offer-inline--story .offer-inline__title {
  font-size: var(--fs-display-s, 1.3125rem);
}

/* OWNER (2026-09-08): the card used to add a second hairline right above the title, on top of the
   `.signup-inline` frame — it read as a cut-off box. One frame only: an ink border, even padding. */
.offer-inline--story {
  border: 1px solid var(--c-line, #e4ded4);
  padding: var(--s-6, 32px);
  margin-top: 0;
}
@media (max-width: 719px) {
  .offer-inline--story { padding: var(--s-5, 24px); }
}

/* ------------------------------------------------------------------ announcement --------------- */

.offer-announce {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3, 12px);
  flex-wrap: wrap;
  padding: var(--s-3, 12px) var(--gutter, 16px);
  background: var(--c-paper-sunk, #f2eee7);
  border-bottom: 1px solid var(--c-line, #e4ded4);
}

.offer-announce__text {
  margin: 0;
  font-size: var(--fs-body-s, 0.875rem);
  line-height: var(--lh-body-s, 1.55);
  color: var(--c-ink-soft, #4f4942);
}

.offer-announce__action {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min, 44px);
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: var(--fs-label, 0.75rem);
  font-weight: var(--fw-label, 500);
  letter-spacing: var(--tr-label, 0.14em);
  text-transform: uppercase;
  color: var(--c-gilt-deep, #7e6534);
  text-underline-offset: 3px;
}

.offer-announce__action:focus-visible {
  outline: var(--focus-ring, 2px solid #1f1d1a);
  outline-offset: var(--focus-offset, 3px);
}

/* ------------------------------------------------------------------ launcher ------------------- */

/* A corner control, never a bar. Below --z-sticky on purpose: a purchase control that shares the
   bottom of the viewport must cover this, not fight it. */
.offer-launcher {
  position: fixed;
  right: var(--s-4, 16px);
  bottom: calc(var(--s-4, 16px) + env(safe-area-inset-bottom, 0px));
  z-index: calc(var(--z-sticky, 100) - 1);
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min, 44px);
  max-width: min(60vw, 260px);
  padding: 0 var(--s-4, 16px);
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: var(--fs-label, 0.75rem);
  font-weight: var(--fw-label, 500);
  letter-spacing: var(--tr-label, 0.14em);
  text-transform: uppercase;
  color: var(--c-ink-invert, #faf8f4);
  background: var(--c-night, #1f1d1a);
  border: 1px solid var(--c-night, #1f1d1a);
  box-shadow: var(--sh-lift, 0 2px 6px rgba(31, 29, 26, 0.06));
}

.offer-launcher[hidden] {
  display: none;
}

.offer-launcher__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.offer-launcher:focus-visible {
  outline: var(--focus-ring, 2px solid #1f1d1a);
  outline-offset: var(--focus-offset, 3px);
}

/* ------------------------------------------------------------------ dialog --------------------- */

/* ABOVE the cookie banner (2026-09-08). `[data-consent-banner]` is `position:fixed` at `--z-toast`
   (500), higher than `--z-modal` (400) — so while the banner was on screen the dialog opened UNDER
   it, and on a phone the banner covered the submit button outright. The owner's rule is that the
   ten-second offer opens regardless of the banner (`shell/offer.ts`), which only works if the two are
   stacked: the dialog and its scrim paint over the banner, and closing the dialog hands the screen
   straight back to it. Nothing else on the site is painted this high. */
.offer-dialog {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-toast, 500) + 10);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gutter, 16px);
}

.offer-dialog[hidden] {
  display: none;
}

.offer-dialog__scrim {
  position: absolute;
  inset: 0;
  background: rgba(31, 29, 26, 0.42);
}

.offer-dialog__panel {
  position: relative;
  width: min(100%, 520px);
  max-height: calc(100vh - var(--s-8, 64px));
  overflow-y: auto;
  padding: var(--s-7, 48px) var(--s-6, 32px) var(--s-6, 32px);
  background: var(--c-paper, #faf8f4);
  border: 1px solid var(--c-line, #e4ded4);
  border-top: 2px solid var(--c-gilt, #c9a86a);
  box-shadow: var(--sh-lift, 0 2px 6px rgba(31, 29, 26, 0.06));
  animation: gp-offer-dialog-in var(--d-base, 260ms) var(--e-enter, ease-out) both;
}

@keyframes gp-offer-dialog-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* The panel receives programmatic focus on open (so screen readers land inside it). It is a container,
   not a control: painting the focus ring on it drew a heavy black frame around the whole card whenever
   the browser judged the opening "keyboard-like" (owner, 2026-09-08: "sometimes an ugly black border").
   Keyboard users still get visible focus on the fields and buttons inside. */
.offer-dialog__panel:focus,
.offer-dialog__panel:focus-visible,
.offer-dialog:focus,
.offer-dialog:focus-visible,
.offer-dialog [tabindex="-1"]:focus,
.offer-dialog [tabindex="-1"]:focus-visible {
  outline: none;
}
/* Mobile browsers keep focus on the tapped launcher and some paint a ring for it; a tap is not a
   keyboard focus, so no ring and no tap highlight on the corner control. */
.offer-launcher,
.offer-dialog__panel {
  -webkit-tap-highlight-color: transparent;
}
.offer-launcher:focus:not(:focus-visible) {
  outline: none;
}

/* Visible, reachable, and big enough to hit — a close control that has to be hunted for is a dark
   pattern with good manners. */
.offer-dialog__close {
  position: absolute;
  top: var(--s-2, 8px);
  right: var(--s-2, 8px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--target-min, 44px);
  height: var(--target-min, 44px);
  font-size: 1.5rem;
  line-height: 1;
  color: var(--c-ink-soft, #4f4942);
  background: transparent;
  border: 0;
  cursor: pointer;
}

.offer-dialog__close:hover {
  color: var(--c-ink, #1f1d1a);
}

.offer-dialog__close:focus-visible {
  outline: var(--focus-ring, 2px solid #1f1d1a);
  outline-offset: 0;
}

.offer-dialog__title {
  margin: 0 var(--s-6, 32px) var(--s-3, 12px) 0;
  font-family: var(--font-display, "Fraunces", Georgia, serif);
  font-size: var(--fs-display-s, 1.375rem);
  line-height: var(--lh-display-s, 1.22);
  font-weight: var(--fw-display-s, 400);
  color: var(--c-ink, #1f1d1a);
}

.offer-dialog__lede,
.offer-dialog__incentive {
  margin: 0 0 var(--s-4, 16px);
  font-size: var(--fs-body, 1rem);
  line-height: var(--lh-body, 1.6);
  color: var(--c-ink-soft, #4f4942);
}

.offer-dialog__disclosure,
.offer-dialog__note {
  margin: var(--s-3, 12px) 0 0;
  font-size: var(--fs-body-s, 0.875rem);
  line-height: var(--lh-body-s, 1.55);
  color: var(--c-ink-mute, #6b6358);
}

/* ------------------------------------------------------------------ notes and slot ------------- */

.offer-note:empty {
  display: none;
}

.offer-note[data-state="ok"] {
  color: var(--c-success, #2f5d3a);
}

.offer-note[data-state="error"] {
  color: var(--c-notice, #8a5a1b);
}

/* The slot is always in the DOM and empty until a capture succeeds: an empty box must collapse, not
   reserve a hole in the layout. */
.signup-inline__offer:empty {
  display: none;
}

.signup-inline__offer {
  margin-top: var(--s-4, 16px);
}

/* ------------------------------------------------------------------ narrow viewports ----------- */

@media (max-width: 599px) {
  .offer-form__field,
  .offer-form__submit {
    flex-basis: 100%;
    width: 100%;
  }

  .offer-dialog__panel {
    padding: var(--s-7, 48px) var(--s-4, 16px) var(--s-5, 24px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .offer-dialog__panel {
    animation: none;
  }
}

/* ------------------------------------------------------------------ fields, stacked ------------
   The form grew from one input to two fields plus two choices, so it stacks. `.offer-form` keeps
   `display:flex` (nothing outside this file relies on its direction) and turns the column, with each
   labelled control in its own `.offer-form__row`. The submit stays a full-width block at the end
   rather than floating next to a field it is no longer paired with.

   Every label is VISIBLE. A two-field form whose first label is screen-reader-only reads as one
   unlabelled box, and "which one is the email" is not a question a signup form should ask. */

.offer-form {
  flex-direction: column;
  gap: var(--s-3, 12px);
}

.offer-form__row {
  display: flex;
  flex-direction: column;
  gap: var(--s-1, 4px);
  width: 100%;
}

/* THE FIELD IS NOT A FLEX ITEM ON THE VERTICAL AXIS (fixed 2026-09-08).
   `.offer-form__field` carries `flex: 1 1 220px` from the shared block at the top of this file, which
   was written when the form was one ROW (email beside a button) and 220px was a width. Once each
   control moved into its own `.offer-form__row` — a COLUMN flex container — that same `flex-basis`
   became a HEIGHT, and the email box on the home page's offer chapter rendered 220px tall: a
   letterbox with a placeholder floating in the middle of it, which is the "the pairing there is off"
   the owner reported. The field sizes itself from its content and its own `min-height` here. */
.offer-form__row > .offer-form__field {
  flex: 0 0 auto;
  height: var(--target-min, 44px);
  width: 100%;
}

.offer-form__label {
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: var(--fs-body-s, 0.875rem);
  line-height: var(--lh-body-s, 1.55);
  color: var(--c-ink-soft, #4f4942);
}

/* The country picker and the number are one control to a reader, so they sit on one line and share a
   baseline. The picker is allowed to shrink but never below a thumb-sized target. */
.offer-form__tel {
  display: flex;
  gap: var(--s-2, 8px);
  align-items: stretch;
  width: 100%;
}

/* 16px, not 14 (fixed 2026-09-08). iOS Safari zooms the whole page when a form control smaller than
   16px takes focus, and it does not zoom back out — the visitor is left on a magnified, sideways-
   scrolling page in the middle of a signup. The picker and the number field are both set at the body
   size for that reason; every other control in this form already was. */
.offer-form__country {
  flex: 0 1 11rem;
  min-width: 6.5rem;
  min-height: var(--target-min, 44px);
  padding: 0 var(--s-2, 8px);
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: var(--fs-body, 1rem);
  color: var(--c-ink, #1f1d1a);
  background: var(--c-paper-raised, #ffffff);
  border: 1px solid var(--c-line-strong, #bdb3a4);
  border-radius: var(--r-none, 0);
}

.offer-form__country:focus-visible {
  outline: var(--focus-ring, 2px solid #1f1d1a);
  outline-offset: 2px;
}

.offer-form__phone {
  flex: 1 1 8rem;
}

.offer-form__field[aria-invalid="true"] {
  border-color: var(--c-notice, #8a5a1b);
}

/* Empty until the server has something to correct; `role="alert"` on the element means it is
   ANNOUNCED when it fills, so it must not carry text at rest. */
.offer-form__error:empty {
  display: none;
}

.offer-form__error {
  margin: 0;
  font-size: var(--fs-body-s, 0.875rem);
  line-height: var(--lh-body-s, 1.55);
  color: var(--c-notice, #8a5a1b);
}

.offer-form__submit {
  width: 100%;
}

/* ------------------------------------------------------------------ the two choices ------------
   Marketing consent, and the disclosure it owes. The box is a real checkbox at a real size, the
   label is clickable, and the SMS disclosure is set at the form's small-print size — never smaller,
   never collapsed, never behind a disclosure widget. `font-size` is stated rather than inherited so
   a parent that shrinks its children cannot shrink a legal notice with them. */

.offer-consent {
  margin: 0;
  padding: 0;
  border: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2, 8px);
}

.offer-consent__choice {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2, 8px);
}

.offer-consent__box {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  accent-color: var(--c-ink, #1f1d1a);
}

.offer-consent__box:focus-visible {
  outline: var(--focus-ring, 2px solid #1f1d1a);
  outline-offset: 2px;
}

.offer-consent__label {
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: var(--fs-body, 1rem);
  line-height: var(--lh-body, 1.6);
  color: var(--c-ink, #1f1d1a);
  cursor: pointer;
  /* A 44px target without a 44px-tall row: the label's own padding does the work. */
  padding: var(--s-1, 4px) 0;
}

.offer-consent__disclosure {
  margin: 0 0 var(--s-1, 4px) 28px;
  font-size: var(--fs-body-s, 0.875rem);
  line-height: var(--lh-body-s, 1.55);
  color: var(--c-ink-mute, #6b6358);
}

.offer-consent__disclosure a {
  color: inherit;
}

/* ------------------------------------------------------------------ the dialog on a phone -------
   A panel with two fields, two choices and a disclosure is taller than a small viewport, so it
   scrolls INSIDE itself and keeps clear of the notch and the home indicator. Without this the
   submit button ends up under the browser chrome and the form cannot be completed at all. */

@media (max-width: 599px) {
  .offer-dialog__panel {
    max-height: calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 24px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(var(--s-5, 24px) + env(safe-area-inset-bottom, 0px));
  }

  .offer-form__country {
    flex-basis: 8.5rem;
  }

  /* The narrow-viewport rule further up sets `flex-basis:100%` on the field and the submit; the tel
     row needs its two children to stay side by side, so it re-states the pair. */
  .offer-form__tel .offer-form__field {
    flex-basis: auto;
    width: auto;
  }
}

/* ------------------------------------------------------------------ the success state ----------
   OWNER 2026-09-08 (evening): a signup has to LOOK finished. `data-offer-success="1"` on the dialog
   host — set by the controller after a capture, and by the server for a browser that already carries
   the code cookie — hides everything that was an ASK and leaves the confirmation with one Close
   button. It is an attribute rather than a class so the server, the controller and these rules all
   name the same fact. */
.offer-dialog[data-offer-success="1"] .offer-dialog__form,
.offer-dialog[data-offer-success="1"] .offer-dialog__disclosure,
.offer-dialog[data-offer-success="1"] .offer-dialog__note,
.offer-dialog[data-offer-success="1"] .offer-dialog__incentive,
.offer-dialog[data-offer-success="1"] .offer-note {
  display: none;
}

/* The confirmation's own two controls: "Shop now" and "Close", one row, both a real touch target. */
.offer-reveal__actions {
  display: flex;
  align-items: center;
  gap: var(--s-4, 16px);
  flex-wrap: wrap;
  margin: var(--s-4, 16px) 0 0;
}

.offer-reveal__inbox {
  margin: var(--s-3, 12px) 0 0;
  font-size: var(--fs-body, 1rem);
  line-height: var(--lh-body, 1.6);
  color: var(--c-ink, #1f1d1a);
  max-width: 46ch;
}

.offer-reveal__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min, 44px);
  padding: 0 18px;
  font-family: var(--font-ui, "Inter", "Helvetica Neue", Arial, sans-serif);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  border: 1px solid var(--c-ink, #1f1d1a);
  background: transparent;
  color: var(--c-ink, #1f1d1a);
}

.offer-reveal__close:hover {
  background: var(--c-ink, #1f1d1a);
  color: var(--c-ink-invert, #faf8f4);
}
