/* ═══════════════════════════════════════════════════════════════════════════════
   ib-bottomnav.css — site-wide mobile bottom navigation (≤767px only)
   ═══════════════════════════════════════════════════════════════════════════════
   Built by public/ib-bottomnav.js. Five slots: Dashboard · Calendar · LOG DOSE
   (raised hero, opens a sheet — never navigates) · Tools · Add.

   WHY A SEPARATE FILE: this is chrome injected into pages with three different CSS
   resets (the legacy static shells, the guides, the Next routes), exactly like
   ib-feedback.css. Nothing here may depend on a global border-box, so every box
   sets its own.

   ── iOS Safari / PWA rules this file exists to satisfy ──────────────────────────
   1. SAFE AREA. The bar's height is `--ib-bn-h` (content) PLUS
      env(safe-area-inset-bottom). The inset is applied as *padding* so the bar's
      background still bleeds to the physical bottom edge while the touch targets
      sit above the Home Indicator. In a Safari TAB the inset reads 0 and the bar
      is 64px; installed to the Home Screen it reads ~34px on a notched iPhone and
      the bar grows to ~98px. Never hard-code 34px — iPads and older phones differ.
   2. NO 100vh. Anything full-height uses 100dvh. `vh` on iOS is the *largest*
      viewport (toolbar collapsed), so a 100vh sheet is taller than the visible
      area whenever Safari's bottom toolbar is expanded, pushing its own footer
      under the chrome. dvh tracks the toolbar as it collapses/expands on scroll.
      The bar itself is position:fixed and needs no height unit at all — fixed
      elements are laid out against the small viewport, so the bar rides *above*
      Safari's floating URL bar rather than under it.
   3. CONTENT CLEARANCE. A fixed bar covers whatever scrolls beneath it, so
      html.ib-bn-on adds scroll-padding + a body padding-bottom of the full bar
      height. Set pre-paint by the gate in app/layout.tsx (no CLS).
   ════════════════════════════════════════════════════════════════════════════ */

:root {
  /* Content height of the bar, EXCLUDING the safe-area inset. The hero button
     overhangs the top edge by --ib-bn-lift and is not part of this. */
  --ib-bn-h: 64px;
  --ib-bn-lift: 18px;
  /* Total space the bar physically occupies. Used for clearance + to rebase the
     other bottom-anchored chrome (picker dock, chat FAB, feedback toast). */
  --ib-bn-total: calc(var(--ib-bn-h) + env(safe-area-inset-bottom, 0px));
}

/* ── content clearance ────────────────────────────────────────────────────────
   Only while the bar is actually live (html.ib-bn-on, set pre-paint at ≤767px).
   scroll-padding-bottom keeps :target / anchor jumps + focus scrolling clear of
   the bar as well as plain scrolling. */
@media (max-width: 767px) {
  html.ib-bn-on { scroll-padding-bottom: var(--ib-bn-total); }
  html.ib-bn-on body { padding-bottom: var(--ib-bn-total); }
}

/* Never over an embed, the admin console, or a print. */
@media print { .ib-bn { display: none !important; } }

/* ── the bar ──────────────────────────────────────────────────────────────── */
.ib-bn,
.ib-bn * { box-sizing: border-box; }

.ib-bn {
  /* Hidden by default at every width; the ≤767px block below opts it in. This
     way a desktop viewport never paints it even for a frame. */
  display: none;
  position: fixed;
  /* Above the calculator action panel (z-index 90), so the raised Log-dose hero —
     which overhangs this bar's top edge — paints ON the panel rather than being
     clipped behind it (operator, 2026-07-30). Raising the whole bar is what makes
     that possible: this element is position:fixed WITH a z-index, so it forms a
     stacking context and the hero cannot outrank the panel on its own.
     Safe because the two boxes never overlap — ib-bottomnav.css sits the panel's
     bottom edge exactly on this bar's top edge — so only the hero's overhang
     changes order. Nothing else lives between 50 and 95 at the bottom of the
     screen: the dashboard dock is 500, the chat bubble 320 (and hidden on mobile),
     page content 1. Still below all the rail drawer chrome (scrim 399, rail 400).
     WebKit note: an element with a backdrop-filter paints above plain fixed
     siblings whatever their z-index (see ib-rail.css). Both this bar and the panel
     carry one, so they are in the same compositing class and z-index is honoured
     between them. Do not remove the blur below without re-checking this. */
  z-index: 95;
  right: 0;
  bottom: 0;
  left: 0;
  /* The inset is padding, not margin — see rule 1 above. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  height: var(--ib-bn-total);
  border-top: 1px solid rgba(17, 26, 58, 0.1);
  /* Earned Glass (DESIGN.md): translucent + blur so dashboard content reads
     through as it scrolls under, with an opaque fallback first for any engine
     without backdrop-filter (else the bar would be see-through and unreadable). */
  background: #fafafb;
  background: rgba(248, 249, 250, 0.88);
  /* blur only, no saturate: a backdrop-filter is re-evaluated every frame the page
     scrolls, and dropping the extra saturate pass measurably cheapens it on mobile
     WebKit. 10px reads the same at this size as the 14px it replaced. */
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 -4px 18px rgba(17, 26, 58, 0.07);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  /* The hero button overhangs the top edge. */
  overflow: visible;
}

@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .ib-bn { background: #fafafb; }
}

/* ── Dashboard: drop the glass entirely ──────────────────────────────────────
   Reported laggy on-device 2026-07-30. The account dashboard already stacks two
   full-width blurred layers at the bottom of a SCROLLING, zoom:0.675 page — the
   protocol/day picker dock (blur(12px)) sits directly on top of this bar. Two
   stacked backdrop-filters over a scrolling zoomed subtree is the expensive case on
   mobile WebKit: each is recomputed per frame over the whole width.
   The dock covers this bar's glass almost completely there anyway, so the effect is
   paid for and not seen. Opaque on that one surface; the glass stays everywhere else.
   :has() is already relied on elsewhere in this codebase (DashStyles body:has(.ib-lograil)). */
@media (max-width: 767px) {
  body:has(.ib-injection-picker-dock) .ib-bn {
    background: #fafafb;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@media (max-width: 767px) {
  .ib-bn { display: block; }
}

.ib-bn-row {
  display: grid;
  /* Five equal slots. The hero's visual size is independent of its slot width so
     the four labels stay evenly spaced. */
  grid-template-columns: repeat(5, 1fr);
  align-items: stretch;
  height: var(--ib-bn-h);
  margin: 0 auto;
  /* Keeps the bar honest on a foldable / small tablet in portrait. */
  max-width: 520px;
}

/* ── a standard slot ─────────────────────────────────────────────────────────
   min-height 44px is the Apple HIG floor. The slot is a full-height grid cell,
   so the real target is 64px tall × ~78px wide at 390px — comfortably over. */
.ib-bn-item {
  display: flex;
  position: relative;
  min-width: 44px;
  min-height: 44px;
  padding: 0 2px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border: 0;
  background: none;
  color: rgba(20, 20, 28, 0.55);
  font-family: inherit;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s ease;
}

.ib-bn-item svg {
  width: 23px;
  height: 23px;
  flex: 0 0 auto;
  /* Icons are stroke-drawn (matching the rail's icon set); the active state
     swaps in a tinted fill. */
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke-width 0.15s ease;
}

.ib-bn-lbl {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── active state ────────────────────────────────────────────────────────────
   One Indicator (DESIGN.md): the colour + weight shift IS the indicator. Blue
   ink, a heavier stroke, and a heavier label — no extra dot or underline.

   #2563eb is the LIGHTEST blue that still clears WCAG AA on this label: at 10px
   the text counts as small, so it needs 4.5:1 against the bar's #f8f9fa and this
   measures 4.90:1. A literally light blue (#7db8e8 → 2.01:1, #0ea5e9 → 2.63:1,
   #3b82f6 → 3.49:1) all fail, so the "light" reading is carried by the icon FILL
   tint below — a fill has no contrast requirement — rather than by the ink. */
.ib-bn-item[aria-current='page'] {
  color: #2563eb;
  font-weight: 800;
}
.ib-bn-item[aria-current='page'] svg {
  stroke-width: 2.3;
  fill: rgba(59, 130, 246, 0.13);
}

.ib-bn-item:active { color: #2563eb; }

/* ── Add, primed ─────────────────────────────────────────────────────────────
   Mobile Save moved off the calculator bar onto this slot, so the slot has to say
   so at the moment it becomes the action. html.ib-add-ready is set by app.js
   (useIbAddReady) only while the calculator on screen has a finished, unsaved
   result — so this never fires on the dashboard, on a guide, or on a calculator
   with nothing to save (BMI, Free T, the plotter), where Add stays plain and
   starts the funnel instead.

   One Indicator still holds: this is the same blue ink and heavier stroke the
   active state uses, plus a soft blue pill behind the icon to carry it at a
   glance. It deliberately stops short of the raised hero's treatment — Log dose
   is still the primary action and the two must not compete. */
html.ib-add-ready .ib-bn-item[data-bn='add'] {
  color: #2563eb;
  font-weight: 800;
}
html.ib-add-ready .ib-bn-item[data-bn='add'] svg {
  stroke-width: 2.3;
}
html.ib-add-ready .ib-bn-item[data-bn='add']::before {
  content: '';
  position: absolute;
  /* Sized and placed on the 23px icon, which sits above the 10px label. */
  top: 9px;
  width: 36px;
  height: 30px;
  border-radius: 9px;
  background: rgba(59, 130, 246, 0.15);
  /* Purely decorative, and the anchor itself stays the hit target. */
  pointer-events: none;
  animation: ib-add-prime 0.32s ease-out;
}
@keyframes ib-add-prime {
  from { opacity: 0; transform: scale(0.82); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  html.ib-add-ready .ib-bn-item[data-bn='add']::before { animation: none; }
}

/* Keyboard focus must be visible but must not be clipped by the bar's top edge. */
.ib-bn-item:focus-visible,
.ib-bn-hero:focus-visible {
  outline: 3px solid rgba(15, 188, 173, 0.5);
  outline-offset: -3px;
  border-radius: 10px;
}

/* ── slot 3: the raised hero ──────────────────────────────────────────────────
   A FAB fused into the bar. It sits in the middle grid cell but is pulled up by
   --ib-bn-lift so it breaks the bar's top plane. The cell keeps
   justify-content:flex-end so the caption stays on the bar's baseline with the
   other four labels. */
.ib-bn-slot-hero {
  display: flex;
  position: relative;
  min-width: 44px;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-bottom: 7px;
}

.ib-bn-hero {
  display: flex;
  position: absolute;
  /* Overhang. bottom is measured from the slot box, so the button's lower half
     stays inside the bar and its upper half rises above it. */
  bottom: calc(100% - var(--ib-bn-h) + 24px);
  width: 54px;
  height: 54px;
  padding: 0;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  /* Solid fill — the one saturated element in the bar, so it reads as THE action.
     Deep navy #001D5C is the same fixed navy DESIGN.md pins the Save CTA to. It also
     fixes a real contrast failure: the previous teal #0a9d90 put the white glyph at
     only 3.37:1 (below the 4.5:1 AA bar); navy takes it to 15.79:1. */
  background: #001D5C;
  color: #ffffff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Ring in the bar's own colour separates the button from the glass behind it. */
  box-shadow:
    0 0 0 4px rgba(248, 249, 250, 0.9),
    0 6px 18px rgba(0, 29, 92, 0.4);
  transition: transform 0.14s ease, background 0.14s ease;
}

.ib-bn-hero svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Reaction (DESIGN.md): press depresses, it doesn't lift. */
.ib-bn-hero:active {
  transform: scale(0.93);
  background: #001233;
}

/* The bar's global reaction opt-out — ib-calc.css lifts every <button> that
   isn't marked, which would make the whole bar bounce on tap. */
.ib-bn button { transform: none; }
.ib-bn .ib-bn-hero:active { transform: scale(0.93); }

.ib-bn-hero-lbl {
  color: #001D5C;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.02em;
  line-height: 1;
  white-space: nowrap;
}

/* ── PWA standalone ──────────────────────────────────────────────────────────
   Installed to the Home Screen there is no Safari toolbar to absorb a mis-tap,
   and the Home Indicator drag handle sits directly under the bar. Give the row
   a little more height so the targets clear the handle, and drop the top shadow
   (there is no browser chrome above to separate from). Both the modern
   display-mode query and the legacy iOS -webkit-standalone flag are handled;
   ib-bottomnav.js also stamps html.ib-bn-standalone for engines that report
   neither reliably. */
@media (max-width: 767px) and (display-mode: standalone) {
  :root { --ib-bn-h: 68px; }
  .ib-bn { box-shadow: none; border-top-color: rgba(17, 26, 58, 0.14); }
}
@media (max-width: 767px) {
  html.ib-bn-standalone { --ib-bn-h: 68px; }
  html.ib-bn-standalone .ib-bn {
    box-shadow: none;
    border-top-color: rgba(17, 26, 58, 0.14);
  }
}

/* ── very short viewports ────────────────────────────────────────────────────
   Landscape phone: the bar would eat a third of the screen. Shrink it and drop
   the labels rather than hide the nav outright. */
@media (max-width: 767px) and (max-height: 460px) {
  :root { --ib-bn-h: 50px; }
  .ib-bn-item { font-size: 0; gap: 0; }
  .ib-bn-lbl, .ib-bn-hero-lbl { display: none; }
  .ib-bn-item svg { width: 21px; height: 21px; }
  .ib-bn-hero { width: 44px; height: 44px; bottom: calc(100% - var(--ib-bn-h) + 16px); }
  .ib-bn-hero svg { width: 21px; height: 21px; }
}

@media (prefers-reduced-motion: reduce) {
  .ib-bn-item,
  .ib-bn-hero,
  .ib-bn-item svg { transition: none; }
  .ib-bn-hero:active { transform: none; }
}

/* ── forced-colours / high-contrast ──────────────────────────────────────────
   Glass carries no meaning in forced-colours mode; give the bar a real edge. */
@media (forced-colors: active) {
  .ib-bn { border-top: 1px solid CanvasText; background: Canvas; }
  .ib-bn-hero { border: 1px solid CanvasText; }
  .ib-bn-item[aria-current='page'] { text-decoration: underline; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   REBASING THE EXISTING BOTTOM-ANCHORED CHROME
   ═══════════════════════════════════════════════════════════════════════════════
   Four things already lived at the bottom edge on mobile before this bar existed.
   Each is lifted by exactly one bar-height so nothing is buried and nothing
   double-counts the safe-area inset. All are scoped to html.ib-bn-on, so if the
   nav is gated off (admin, embeds, auth screens, the plotter) every one of them
   keeps its original position.

   --ib-bn-total = --ib-bn-h + env(safe-area-inset-bottom).
   Add --ib-bn-total to an element that does NOT already include the inset;
   add only --ib-bn-h to one that DOES (else the inset is counted twice).
   ════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {

  /* 1. Calculator sticky action bar (app.js CalcStickyBar + MobileProtocolBar).
        The bar is z-index 90 and this nav is 95, so the nav — and with it the
        raised Log-dose hero overhanging its top edge — paints over the panel.
        The `bottom` below is what stops the two boxes overlapping at all.
        !important is required: both `bottom` and `padding-bottom` are inline
        styles on the bars, and the className exists only as this hook.

        `bottom` gets the TOTAL: the nav's top edge is exactly --ib-bn-h + inset
        up from the viewport bottom, so tracking the total keeps the bar sitting
        flush on the nav as the inset changes.

        `padding-bottom` drops the inset entirely, and this is the part that was
        wrong (reported on-device 2026-07-30: "the nav slides to the bottom and
        the CTA bar stays where it is with huge padding"). The bars set
        `calc(12px + env(safe-area-inset-bottom))` inline, which is right when
        the bar IS the bottom-most element — but here the NAV is, and the nav
        already clears the home indicator. env(safe-area-inset-bottom) is not a
        constant: it reads 0 while Safari's toolbar is expanded and jumps to
        ~34px the moment it collapses. So on every scroll-down the bar grew 34px
        of phantom bottom padding at the same instant the nav dropped away from
        it — the gap the operator saw. Invisible in a desktop emulator, where the
        inset is always 0. This is the rule stated at the top of this block:
        add the total to something that does NOT include the inset; do not let
        something that already does count it twice. */
  html.ib-bn-on .ib-calc-stickybar {
    bottom: var(--ib-bn-total) !important;
    /* Deeper than the panel's 12px top padding on purpose (operator, 2026-07-30):
       the Log-dose hero now overhangs INTO this panel from below, and the extra
       room keeps the Show-result button clear of it instead of crowding the arc.
       Still no safe-area inset here — see above. */
    padding-bottom: 22px !important;
  }

  /* 2. Account dashboard protocol/day picker dock — NOT handled here.
        It lives inside .ib-dash-wrap, which is zoom:0.675, and CSS zoom scales the
        used value of every length in its subtree — including a position:fixed
        element's own offsets. A `bottom` set from here resolved to 0.675× and put
        the dock BEHIND this bar (reported on-device 2026-07-30). The rule therefore
        lives in DashStyles.tsx, next to the zoom it has to cancel out; it consumes
        --ib-bn-total (published below) and multiplies by --ib-dash-unzoom.
        This file publishes the bar's height; it does not reach into the dashboard. */

  /* 3. Chat FAB (ib-chat.css). Was a bare bottom: 118px — the 118 clears the
        calculator action bar, and it does NOT include the inset, so add total. */
  html.ib-bn-on .ib-chat-fab {
    bottom: calc(118px + var(--ib-bn-total));
  }

  /* 4. Feedback toast (ib-feedback.css). Was calc(118px + inset) — the inset is
        ALREADY there, so add only the content height, not the total. */
  html.ib-bn-on .ib-fb-toast {
    bottom: calc(118px + env(safe-area-inset-bottom, 0px) + var(--ib-bn-h));
  }

  /* The plotter route is excluded from the nav entirely, so its own overrides
     (which pull the chat FAB and toast down to 16px) must not be lifted. Guard
     them in case the exclusion is ever relaxed. */
  html.ib-bn-on body.ib-plotter-page .ib-chat-fab,
  html.ib-bn-on body.ib-plotter-page .ib-fb-toast {
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
}
