/* ── Both sizes ─────────────────────────────────────────────────────────────
   The responsive specification, from `design/Off The Bone - Both sizes.dc.html`.

   That board does not draw 217 phone screens. It settles seventeen layout
   archetypes and assigns every screen to one — so this file is written the same
   way: the rules once, keyed to the shell and the archetype classes, and near
   zero per screen. Build the archetype, not the screen.

   The three widths, and the one set of moves each:

     < 640      Phone.   Single column. Rail becomes a five-slot bottom bar.
                         Tables become stacked cards. Sheets go full height from
                         the bottom. The primary action pins above the tab bar.
     640–1023   Tablet.  Two columns collapse to one, the rail collapses to an
                         88px icon strip, sheets stay centred at 560.
     ≥ 1024     Desktop. As built. Above 1440 the content stops growing at 1280
                         and the extra width becomes page margin — except the
                         four expanding customer archetypes (B1, B2, B4, B6).

   Anything that does not follow its archetype is an exception with a reason,
   and the reasons are written next to the rule rather than in a changelog. */

:root {
  --bs-phone: 639.98px;
  --bs-tablet-min: 640px;
  --bs-tablet-max: 1023.98px;
  --bs-desktop-min: 1024px;

  /* Touch, in two tiers, because they are not the same decision.

     44px for anything that changes data — every row action, every sheet button,
     the overflow. It is the platform minimum and it lets a roster row sit at
     64px with air around the pill.

     36px for filter and suggestion chips: they only change what you are looking
     at or append a word, a mis-tap costs nothing, and a row of 48px chips eats a
     fifth of the screen.

     Nothing tappable goes below 36px, and nothing that spends money or moves a
     dog goes below 44px. */
  --bs-touch: 44px;
  --bs-touch-chip: 36px;
  --bs-row: 64px;
  --bs-tabbar: 56px;

  /* Five type tiers on phone, and nothing between them. Numbers that matter keep
     their desktop size — a fee read at the gate is not metadata. */
  --bs-type-sentence: 15.5px;   /* sentences, field values, row titles, money   */
  --bs-type-control: 14px;      /* labels inside a control                      */
  --bs-type-second: 13px;       /* a row's second line, metadata under a card   */
  --bs-type-eyebrow: 11px;      /* the uppercase eyebrow over a group           */
  --bs-type-ruler: 10.5px;      /* tab labels, calendar weekday letters         */

  --bs-content-max: 1280px;
  --bs-sheet-width: 560px;
}

/* ── ≥ 1440: the content stops growing ─────────────────────────────────────
   Above 1440 the extra width goes to page margin rather than to line length. A
   roster row 1700px wide is a row nobody can read across. */
@media (min-width: 1440px) {
  .o-main > *,
  .portal-main > * { max-width: var(--bs-content-max); margin-inline: auto; }

  /* The exception, from C5: B1 hub, B2 booking flow, B4 enrolment form and B6
     card history are the four customer archetypes that earn the width. They opt
     in per page rather than by width, because it is a property of the screen. */
  .portal-main.bs-expands > * { max-width: none; }
}

/* ── 640–1023: tablet ──────────────────────────────────────────────────────
   The gate tablet lives here and already has its own screens (22a, 22d), so this
   is the ordinary two-column-becomes-one case and nothing more. */
@media (min-width: 640px) and (max-width: 1023.98px) {
  /* ax1, rule 3: "every target 44px on the gate tablet — the roster's row buttons are
     28px high, tapped with wet hands, one-handed, holding a lead."

     The rule was here already and it was in the wrong block: it lived under the phone
     breakpoint only, so the one device design names by name — the gate tablet, 640 to
     1023 — never got it. `.o-btn-sm` is padding 5px on 12.5px text, which measures the
     28px design counted. Everything that changes data gets the touch minimum, and a
     roster row keeps the extra height that makes the pill reachable without hitting the
     row above it. */
  /* The list named `.o-input` — the floor's field — plus `select` and a date picker, and
     stopped. The admin shell and the family's portal both use bare `input` elements, so
     the one rule that exists to keep a wet-handed tap on target skipped two of the three
     shells. It went unnoticed while their padding happened to add up to 44px; giving a
     field an explicit floor of design's 36px made the gap visible rather than causing it.
     Every control that takes typed data is named now. */
  .o-btn, .o-choice, .o-row, button, .o-input, select,
  input[type="date"], input[type="text"], input[type="search"], input[type="email"],
  input[type="password"], input[type="tel"], input[type="number"], input[type="time"],
  input:not([type]) {
    min-height: var(--bs-touch);
  }
  .o-row { min-height: var(--bs-row); }
  .o-chip, .o-tag { min-height: var(--bs-touch-chip); display: inline-flex; align-items: center; }

  .o-rail { width: 88px; }
  .o-rail-brand span, .o-rail-brand div b, .o-rail-section,
  .o-rail-search, .o-rail-user span { display: none; }
  .o-nav-item { justify-content: center; text-align: center; font-size: 11px; padding: 10px 4px; }
  .o-stats { grid-template-columns: repeat(2, 1fr); }
  .o-wizard, .two-col, .detail-layout { grid-template-columns: 1fr; }
}

/* ── < 640: phone ──────────────────────────────────────────────────────────*/
@media (max-width: 639.98px) {
  .organic { font-size: var(--bs-type-sentence); }

  /* A1 · Rail + working list. The rail becomes a five-slot bottom bar — five,
     not sixteen: a scrolling strip of every destination is a menu wearing a tab
     bar's clothes, and the sixteenth item is never reached. Everything else
     lives behind More. */
  .o-shell { flex-direction: column; }
  .o-rail { display: none; }
  .o-main {
    order: 1;
    padding: 16px 16px calc(var(--bs-tabbar) + 28px);
  }

  .o-page-head { flex-direction: column; align-items: flex-start; gap: var(--o-space-3); }

  /* The header's actions wrap rather than running off the edge. Found by measuring
     scrollWidth against the viewport on every floor screen at 390px — two of them
     pushed a button past the right edge, and a page that scrolls sideways on a phone
     is the one thing "single column" is supposed to rule out. */
  .o-head-actions, .o-actions-inline, .o-toolbar {
    display: flex; flex-wrap: wrap; gap: 8px; width: 100%;
  }
  .o-head-actions > *, .o-toolbar > * { flex: 1 1 auto; text-align: center; }

  /* And a backstop: nothing wider than the screen, whatever it is. A long dog name or
     a pasted URL should wrap, not widen the page. */
  .organic { overflow-x: hidden; }
  .o-card, .o-row, .o-table, .o-page-head { max-width: 100%; }
  .organic td, .organic th, .o-row, .o-note { overflow-wrap: anywhere; }
  .organic h1 { font-size: 27px; line-height: 1.15; }
  /* h2 was left at its desktop size and swallowed the card it sits in. A card's
     heading is a label for what follows, not a second page title. */
  .organic h2 { font-size: 19px; line-height: 1.25; }
  .organic h3 { font-size: 16px; line-height: 1.3; }
  .o-stat-value { font-size: 24px; }
  .o-eyebrow, .o-card-kicker { font-size: var(--bs-type-eyebrow); }
  .o-note, .o-stat-note, .o-row small, .o-choice em { font-size: var(--bs-type-second); }
  .o-label { font-size: var(--bs-type-control); }

  /* Numbers that matter hold their size. This is the tier the design is most
     explicit about, because it is the one a phone layout usually gets wrong. */
  .o-stat-value, .o-total b, .o-table td[data-label*="mount"],
  .o-table td[data-label*="rice"], .o-table td[data-label*="eek"] {
    font-size: 1rem;
  }

  .o-stats { grid-template-columns: repeat(2, 1fr); }

  /* Anything that changes data is 44px, and a roster row is 64px with air
     around the pill. */
  /* The list named `.o-input` — the floor's field — plus `select` and a date picker, and
     stopped. The admin shell and the family's portal both use bare `input` elements, so
     the one rule that exists to keep a wet-handed tap on target skipped two of the three
     shells. It went unnoticed while their padding happened to add up to 44px; giving a
     field an explicit floor of design's 36px made the gap visible rather than causing it.
     Every control that takes typed data is named now. */
  .o-btn, .o-choice, .o-row, button, .o-input, select,
  input[type="date"], input[type="text"], input[type="search"], input[type="email"],
  input[type="password"], input[type="tel"], input[type="number"], input[type="time"],
  input:not([type]) {
    min-height: var(--bs-touch);
  }
  .o-row { min-height: var(--bs-row); }
  /* A row is one unwrapped flex line, so two trailing pills squeeze the main column
     until `overflow-wrap: anywhere` above starts breaking the dog's name mid-word —
     "Nimb / us", "R / o / c / k / et" — and the sub-line ellipses to "Bord…". Found on
     /floor/dogs at 390px with a dog carrying two tags.

     The main column keeps three fifths of the row and the pills wrap under it when they
     no longer fit, which is what archetype A1 draws: "the row's trailing action becomes
     a 44px pill". A pill that fits still sits alongside. */
  .organic .o-row { flex-wrap: wrap; }
  /* Scoped to .organic because organic.css sets `.organic .o-row-main { min-width: 0 }`,
     which outranks a bare .o-row-main however late this file loads — the first version
     of this fix changed nothing for exactly that reason. */
  .organic .o-row-main { min-width: 50%; }
  /* And stop the breaking itself. `overflow-wrap: anywhere` above is the backstop for a
     pasted URL; on a row it also breaks an ordinary word the moment the column is tight,
     which is how "Nimbus" became "Nimb / us". break-word only splits a word that cannot
     fit on a line of its own. */
  .organic .o-row, .organic .o-row-main { overflow-wrap: break-word; }
  .o-chip, .o-tag { min-height: var(--bs-touch-chip); display: inline-flex; align-items: center; }

  /* Filters become a scrolling chip row that keeps its counts — the counts are
     half the reason anybody looks at them. */
  .o-filters, .o-chips {
    display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .o-filters::-webkit-scrollbar, .o-chips::-webkit-scrollbar { display: none; }
  .o-filters > *, .o-chips > * { flex: 0 0 auto; }

  /* A7 · Dense table becomes stacked cards. Each row is a card, and each cell
     carries the column it came from — a stacked table without its headers is a
     list of unlabelled values, which is worse than scrolling sideways. Cells
     with no data-label fall back to the plain stacked layout. */
  .o-table thead { display: none; }
  .o-table, .o-table tbody { display: block; }
  .o-table tr {
    display: block;
    padding: 12px 0;
    border-bottom: 1px solid var(--o-divider);
  }
  .o-table td {
    display: flex; justify-content: space-between; gap: 16px;
    padding: 3px 0; border: 0; text-align: left;
  }
  .o-table td[data-label]::before {
    content: attr(data-label);
    color: var(--o-neutral-600, #82796a);
    font-size: var(--bs-type-second);
    flex: 0 0 auto;
  }
  .o-table td.o-right { justify-content: space-between; }

  /* A3 · Sheet. A centred dialog becomes a bottom sheet at full height with a
     grab handle. Its two footer buttons stack, primary on top, both clear of the
     home area. */
  .o-sheet, dialog.o-sheet {
    position: fixed; inset: auto 0 0 0; width: 100%; max-width: none;
    max-height: 92vh; border-radius: 18px 18px 0 0;
    padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .o-sheet::before {
    content: ""; display: block; width: 40px; height: 4px; margin: 6px auto 12px;
    border-radius: 99px; background: var(--o-divider);
  }
  .o-sheet .o-actions, .o-actions {
    display: flex; flex-direction: column-reverse; gap: 10px;
  }
  .o-actions > * { width: 100%; text-align: center; }

  /* The delivered admin shell's toolbar is a three-child flex row with no wrap, so on a
     phone the last child — usually the count, sometimes the primary button — is pushed
     off the right edge. Every admin page has one; st3 is only where it was noticed. */
  .toolbar { flex-wrap: wrap; }
  .toolbar > * { min-width: 0; }
  .tabs { flex-wrap: wrap; }
  .wrap-cell { max-width: none; }

  /* lp1's escalation log. Three columns fit a desk and not a phone: the buttons in the
     third are what somebody is actually reaching for at six o'clock, so the row stacks
     and they get the full width instead of squeezing the number they belong to.

     Scoped with .organic on purpose. organic.css defines these as `.organic .o-timeline-row`,
     which outranks a bare class selector however late it appears — the same specificity
     trap that left the phone tab bar invisible until it was found in a browser. */
  .organic .o-timeline-row { grid-template-columns: 1fr; gap: 4px; }
  .organic .o-timeline-state { justify-self: stretch; }
  .organic .o-inline-form { display: flex; flex-wrap: wrap; gap: 8px; }
  .organic .o-inline-form > .o-btn { flex: 1 1 auto; min-height: var(--bs-touch); }
  .o-sheet .o-choice { min-height: 52px; }

  /* A5 · Board / grid. No drag on a phone: the handle opens a "move to" sheet
     instead. The same decision, one tap more, and no dogs dropped between
     columns because a thumb slipped. */
  [draggable="true"] { -webkit-user-drag: none; }
  .o-drag-hint { display: none; }
  .o-move-handle { display: inline-flex; min-width: var(--bs-touch); min-height: var(--bs-touch); }

  /* Forms and grids collapse to one column. Column order follows the job, not
     the desktop grid — a screen that wants a different order says so with
     bs-first rather than by reordering its markup for one width. */
  .o-form-grid, .o-choices, .o-wizard, .two-col, .form-grid, .card-grid,
  .detail-layout, .account-grid, .portal-form-grid { grid-template-columns: 1fr; }
  .bs-first { order: -1; }

  /* The primary action pins above the tab bar. */
  .o-pinned-action {
    position: sticky; bottom: calc(var(--bs-tabbar) + 8px); z-index: 5;
    padding-top: 8px; background: linear-gradient(transparent, var(--o-bg) 30%);
  }
}

/* ── The five-slot bottom bar ──────────────────────────────────────────────
   Hidden at every width except phone. Rendered in the shell rather than per
   screen, so a new screen inherits it by existing. */
.o-tabbar { display: none; }

@media (max-width: 639.98px) {
  .o-tabbar {
    /* display lives here rather than in the phone block above, because the base
       `display: none` is declared after it — same specificity, so source order
       decides, and the bar rendered invisible. Found by looking at the page. */
    display: grid;
    position: fixed; inset: auto 0 0 0; z-index: 40;
    grid-template-columns: repeat(5, 1fr);
    background: var(--o-surface, #ebddc5);
    border-top: 1px solid var(--o-divider);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .o-tabbar a {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; min-height: var(--bs-tabbar);
    font-size: var(--bs-type-ruler); text-decoration: none;
    color: var(--o-text, #201e1d); opacity: .68;
  }
  .o-tabbar a.is-active { opacity: 1; font-weight: 700; }
  /* The bar carried its icons as text characters — ◧ ◕ ＋ ✎ ≡ — which is a second icon
     vocabulary in the same navigation, and one that renders differently on every phone
     because it is whatever that device's emoji font decides. It draws the rail's icons
     now, one size up, so a tap target on a phone is the thing it is on the desk. */
  .o-tabbar .o-tab-icon { display: flex; line-height: 1; }
  .o-tabbar .o-tab-icon .o-nav-icon { width: 21px; height: 21px; }
}

/* ── Desktop-only, which means read-only rather than broken ────────────────
   From r1b, and extended by t2a to the whole settings and setup block — 26
   screens. These are consequential, wide, and never done at the gate: a change
   to opening hours moves every booking after it, and a permission grid stops
   being a comparison the moment it is collapsed.

   So on a phone they render a readable summary and one plain line saying where
   to finish the job. Never a half-usable form: a form that looks editable and
   silently is not is worse than one that says so. */
@media (max-width: 1023.98px) {
  .bs-desk-only form:not(.bs-allowed),
  .bs-desk-only .o-actions,
  .bs-desk-only button:not(.bs-allowed),
  .bs-desk-only .o-input,
  .bs-desk-only input:not([type="hidden"]),
  .bs-desk-only select,
  .bs-desk-only textarea { display: none !important; }
  .bs-desk-only .bs-finish-at-a-desk { display: block; }
}
.bs-finish-at-a-desk { display: none; }

@media (max-width: 1023.98px) {
  .bs-finish-at-a-desk {
    margin: var(--o-space-4, 16px) 0;
    padding: 14px 16px;
    border: 1px dashed var(--o-divider);
    border-radius: 12px;
    color: var(--o-neutral-700, #645c50);
    font-size: var(--bs-type-second);
  }
}

/* ── B7 · Phone-only, and the customer screens generally ───────────────────
   36 customer screens get no desktop expand at all: one column, one job, no
   stretched paragraphs. That is a rule, not thirty-six drawings. */
.portal-main > * { max-width: 720px; margin-inline: auto; }
.portal-main.bs-expands > * { max-width: none; }

@media (max-width: 639.98px) {
  .portal-main { padding: 16px 16px 32px; }
  .portal-card, .portal-page-head { margin-inline: 0; }
  .portal-primary, .portal-secondary, .portal-form button {
    min-height: var(--bs-touch); width: 100%;
  }
  .check-row { min-height: var(--bs-touch); align-items: center; }
}

/* ── Print, and reduced motion ─────────────────────────────────────────────
   The print sheets are fixed at A4 by definition (15a-15d) and need a rule
   about phone access rather than a layout. This is that rule: the chrome goes,
   the content stays. */
@media print {
  .o-tabbar, .bs-finish-at-a-desk { display: none !important; }
}

/* ── The delivered admin shell's toolbar subtitle ──────────────────────────
   `<div><strong>Heading</strong><span>The sentence under it</span></div>` is the shape
   every admin toolbar uses, and nothing ever made the span a line of its own — so the
   two ran together as "Policies & consentPublishing a new version...". The count that
   sits directly in the toolbar is a different span and stays inline. */
.toolbar > div > span { display: block; color: var(--muted); font-size: 12px; margin-top: 3px; }

/* .field-help carries a negative top margin and no padding, because it is drawn to tuck
   under a form field that supplies both. At the foot of a panel it does neither job: the
   margin pulls it up over the last row's border and the missing padding runs it out to
   the panel edge, which clipped "Support" and "For the accountant" on st2 and put the
   first-week caption through the last row of the guide's index. Same shape, so one class
   rather than one per page. */
.panel-note { margin-top: 0; padding: 0 22px 18px; }
