/* ══════════════════════════════════════════════════════════════════════════
   Gigsend — shared mobile + touch layer.

   Served from /static (conditional-GET + max-age=3600) and linked from
   layout.js AFTER its inline <style> block, with a ?v=<version> cache buster.
   Two reasons it is a file and not more inline CSS:
     - HTML is Cache-Control: no-store, so every inline byte is re-downloaded on
       every page view; this file is fetched once per deploy.
     - layout.js is the serialization bottleneck for parallel work.

   Because it loads after the inline sheet, equal-specificity rules here win.
   Route-level <style> blocks live in the BODY and therefore still win over
   this file — that is deliberate: feature-specific mobile rules stay with
   their feature (calendar grid, media day page, overview).

   Breakpoint is max-width: 900px everywhere — it matches the calendar's
   existing collapse and calFit()'s >= 901px self-gate, so no viewport range
   ever has both the mobile rules and the desktop viewport-fit engine active.
   Touch-specific rules use @media (pointer: coarse) instead, since a touch
   laptop is wide but still fat-fingered.

   Nothing here may change desktop rendering: every rule is inside a media
   query except the two declarations below, which only introduce a token and
   keep a mobile-only element hidden.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Height of the bottom tab bar's touch row (the safe-area inset is added on
     top of it). Consumed by the bar itself and by the container's bottom
     padding, and available to any package that must clear the bar. */
  --m-tabbar-h: 56px;
}

/* layout() renders the bar in the DOM at every viewport (the alternative — a
   server-side viewport guess — cannot survive a rotation). Hidden by default,
   shown only inside the breakpoint below. */
.m-tabbar { display: none; }

@media (max-width: 900px) {

  /* ── Page frame ──────────────────────────────────────────────────────────
     32px of side padding costs 16% of a 390px phone. Halve it, then re-declare
     the three broadcast full-bleed bands, which break out by exactly the
     container's side padding and would otherwise overflow 16px past each
     viewport edge (matching specificity + later source order = these win). */
  .container { padding: 16px 16px 40px; }
  [data-skin="broadcast"] .topbar { margin: 0 -16px; padding: 8px 16px; margin-bottom: 0; }
  [data-skin="broadcast"] .nav { margin: 0 -16px 14px; }
  [data-skin="broadcast"] .cal-layout { margin: 0 -16px; }

  /* ── Top bar ─────────────────────────────────────────────────────────────
     ONE row: wordmark · venue name · more · Sign Out. Everything else - palette,
     skin, theme, Setup, Account, Users, Tracking - is behind the "more" button
     and opens as a second row underneath.

     It used to be nine controls in a wrapping cluster, which on a 375px phone
     came out as four ragged rows (wordmark, blank, five controls, blank, then
     Tracking and Sign Out) - a masthead taller than the content under it.

     display:contents on .topbar-actions is what makes the overflow row possible
     without moving any nodes: the cluster's children become flex items of the
     bar itself, so the menu can take a full-width row of its own while the
     button and Sign Out stay on row one. */
  .topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 10px;
    row-gap: 6px;
    margin-bottom: 14px;
  }
  .topbar-left { order: 1; gap: 10px; flex: 0 0 auto; }
  /* KEPT visible — deliberately. This rule used to read `display: none`, but
     an inline style on the span (now moved into app.css's real
     `.topbar-user` rule) always outranked it, so in practice the username has
     shown on every phone since this markup existed. That was an accident of
     specificity, not a decision; this is the decision, made real: the
     operator wants the username on mobile, so it is never hidden here.
     What DOES give way, to keep it a single row on a 360px phone:
       - the admin build number below it (.topbar-ver, right below) — a
         debugging aid nobody reads on a phone, not the username itself.
       - its own width, capped + ellipsised (.topbar-username, right below) —
         a username can run to 64 characters (MAX_USERNAME_KEY_LEN), and
         nothing here should let one claim more of row one than that. */
  .topbar-ver { display: none; }
  .topbar-username {
    display: block; max-width: 92px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .topbar-brand-name { font-size: 0.95rem; }
  .topbar-brand-ver { display: none; }
  .topbar-actions { display: contents; }
  .topbar-actions a, .topbar-actions button { font-size: 0.75rem; }
  /* The venue name shares row one now, taking whatever is left and ellipsising.
     An empty one (dashboard, users, account) must not hold a row open. */
  .topbar-title {
    order: 2;
    /* flex-basis:0 (not auto) so this item's hypothetical main size - what
       .topbar's flex-wrap line-breaking decides against - is 0, not the
       venue name's full nowrap content width. With basis:auto a long name
       alone was "too wide for line one" before shrinking was ever
       considered, so .topbar-title (and everything after it) got pushed to
       its own row regardless of min-width/ellipsis. */
    flex: 1 1 0%;
    justify-content: flex-start;
    min-width: 0;
    gap: 6px;
  }
  .topbar-title:empty { display: none; }
  .topbar-name { font-size: 0.95rem; }
  .topbar-city { display: none; }
  /* A bordered chip, not a bare glyph: three faint dots against the ink band
     read as a rendering artefact rather than as the control that holds half the
     app's navigation. */
  .topbar-more {
    order: 3;
    display: inline-flex; align-items: center; justify-content: center;
    margin-left: auto;
    font-size: 1.15rem; line-height: 1; letter-spacing: 0.04em;
    min-width: 44px; min-height: 34px; padding: 0 10px;
    color: var(--text-primary);
    border: 1px solid var(--separator-opaque); border-radius: 999px;
  }
  [data-skin="broadcast"] .topbar-more { color: var(--on-accent); border-color: var(--on-accent-border); }
  .topbar.menu-open .topbar-more { background: var(--surface-inset); }
  [data-skin="broadcast"] .topbar.menu-open .topbar-more { background: rgba(255,255,255,0.14); }

  /* The group opens as TWO rows: appearance and the way out, then where to go. */
  .topbar-menu {
    order: 5;
    flex-basis: 100%;
    display: none;
    flex-wrap: wrap;
    gap: 0;
    padding: 8px 0 4px;
    border-top: 1px solid var(--separator);
  }
  .topbar.menu-open .topbar-menu { display: flex; }
  [data-skin="broadcast"] .topbar-menu { border-top-color: var(--on-accent-border); }
  .tm-row {
    display: flex; flex-basis: 100%; align-items: center;
    flex-wrap: wrap; gap: 18px; padding: 5px 0;
  }
  .tm-row + .tm-row { border-top: 1px solid var(--separator); }
  [data-skin="broadcast"] .tm-row + .tm-row { border-top-color: var(--on-accent-border); }
  .topbar-menu form { order: 0; margin-left: auto; }

  /* ── Tab row (components.js clientNav) ───────────────────────────────────
     The three core tabs move to the bottom bar, so hide them here rather than
     showing the same navigation twice. The row itself disappears entirely
     unless a flag-driven tab (recurring / sheets) or a non-client link
     (accountNav's lone "dashboard") is left in it. :has() is Safari 15.4+ /
     Chrome 105+; where it is unsupported the whole rule is dropped and the
     empty row simply stays visible — a cosmetic fallback, not a breakage. */
  .nav a.nav-core { display: none; }
  .nav:not(:has(a:not(.nav-core))) { display: none; }
  .nav { flex-wrap: wrap; }

  /* ── Bottom tab bar ──────────────────────────────────────────────────────
     Token-based so both skins and all three themes come along for free. It sits
     at --z-nav; the calendar day-detail sheet deliberately stacks above it. */
  .m-tabbar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-nav);
    background: var(--surface);
    border-top: 1px solid var(--separator-opaque);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .m-tabbar a {
    flex: 1 1 0;
    min-width: 0;
    height: var(--m-tabbar-h);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    font-family: var(--body);
    font-size: 0.68rem;
    font-weight: var(--weight-medium);
    letter-spacing: 0.01em;
    color: var(--text-secondary);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }
  .m-tabbar a svg { width: 21px; height: 21px; display: block; }
  .m-tabbar a.active { color: var(--accent); font-weight: var(--weight-semibold); }
  /* Broadcast's metadata register: mono uppercase labels, square everything. */
  [data-skin="broadcast"] .m-tabbar a {
    font-family: var(--mono);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    border-right: 1px solid var(--separator);
  }
  [data-skin="broadcast"] .m-tabbar a:last-child { border-right: none; }
  [data-skin="broadcast"] .m-tabbar a.active { color: var(--text-primary); background: var(--surface-inset); }

  /* Page content must be able to scroll clear of the fixed bar. Set only when
     layout() actually rendered it (and kept in sync by mTabbarSync(), since
     hx-boost swaps body *contents* but leaves body's class attribute alone). */
  body.has-m-tabbar .container {
    padding-bottom: calc(var(--m-tabbar-h) + env(safe-area-inset-bottom) + 16px);
  }

  /* The padding above only stops the LAST element from ending under the bar; it
     does nothing about where the UA parks an element it scrolls to. A browser
     bringing a focused input into view scrolls it flush with the viewport
     bottom - which on a phone is behind 56px of fixed tab bar, so the field a
     person just tapped into is the one thing they cannot see (WCAG 2.4.11).
     scroll-padding shrinks the scrollport the UA aims at, so the field lands
     above the bar instead. On <html> because that is the scrolling element. */
  html {
    scroll-padding-bottom: calc(var(--m-tabbar-h) + env(safe-area-inset-bottom) + 8px);
  }

  /* ── Section-to-section fade ─────────────────────────────────────────────
     The bottom tab bar navigates between three whole pages. Fade the content
     down while the next one is on its way and let it come back up when it
     settles: the same wait, but as one movement instead of a pause and a jump.
     Only the content moves - the masthead, the tabs and the bar stay put, which
     is what keeps it feeling like one app rather than a page reload. */
  .container { transition: opacity 0.18s ease; }
  body.nav-leaving .container { opacity: 0.3; }
  @media (prefers-reduced-motion: reduce) {
    .container { transition: none; }
    body.nav-leaving .container { opacity: 1; }
  }

  /* Schedules: name on its own line, then the time and day fields side by side
     at a size that can actually be read and tapped. */
  .sched-fields { margin-left: 0; flex-basis: 100%; }
  .sched-f { flex: 1 1 0; }
  .sched-f .form-input,
  .sched-f .form-select,
  .sched-f .sel-wrap,
  .sched-f-day .sel-wrap { width: 100%; min-height: 40px; }

  /* Thumb-sized steppers, and the field between them stays legible. */
  .num-step-btn { width: 40px; height: 40px; font-size: 1.1rem; }

  /* The venue's label sits above its buttons rather than beside them - 88px of
     label out of 375px is a quarter of the row. */
  .trig-row { display: block; }
  .trig-lbl { display: block; margin-bottom: 5px; }
  .trig-btns { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
  .trig-venue { padding: 12px 14px; }

  /* ── Wide content inside a card ──────────────────────────────────────────
     .card has overflow:hidden (it clips children to its radius), so every table
     in this app - recent activity, active sessions, all users, bug reports,
     errors - was simply CUT at the card edge on a phone: no scrollbar, no way
     to reach the columns on the right. One rule, because every one of those
     tables sits in a .card-body.

     min-width on the table is what makes it scroll rather than squeeze: without
     it the table crushes five columns into 375px and each becomes unreadable
     instead of reachable. */
  .card-body { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .card-body > table { min-width: 560px; }
  .run-table th, .run-table td { white-space: nowrap; }
  /* Rows sized for a finger, not for a spreadsheet - but not so tall that six
     users fill the screen (the All Users list was the worst of these). */
  .run-table td { padding: 9px 10px; }
  .run-table th { padding: 8px 10px; }

  /* ── Theme menu (light / dark / system) ────────────────────────────────────
     `.theme-menu` (app.css) anchors `right: 0` against its own ~24px button -
     fine on desktop, where that button sits near the right end of the bar.
     On the mobile overflow row it's one of the FIRST controls instead (theme
     alone, or palette-then-theme for admins), so `right: 0` hangs the 132px
     menu off the button's right edge and lets it run 55-90px past the LEFT
     edge of the viewport - only the tail of "Light"/"Dark"/"System" stayed
     on-screen. Anchor from the button's own left edge instead: the menu then
     only ever grows into the free space to its right, which the overflow row
     always has (that row measures 328px wide at 360px viewport width - the
     narrowest this app supports - well over double the menu's 132px).
     `#pal-menu` shares the `.theme-menu` class but gets its own ID-scoped
     position override just below, which - being an ID selector - wins over
     this regardless of source order. */
  #theme-menu { left: 0; right: auto; }

  /* ── Palette picker (admin) ──────────────────────────────────────────────
     A 208px panel anchored to the right edge of a button that is itself near
     the right edge of a 375px screen opened mostly off-screen - the saturation
     square, the part you actually pick with, was the quarter that was cut off.
     On a phone it is not a dropdown at all: it spans the width under the bar. */
  #pal-menu {
    position: fixed;
    top: auto; bottom: 12px; left: 12px; right: 12px;
    width: auto; min-width: 0; max-width: none;
    margin: 0;
    max-height: 78vh; overflow-y: auto;
    z-index: 300;
  }
  /* A picker you drag needs room: the square is the control, and at the panel's
     desktop width it was a 44px smudge on a phone. */
  #pal-menu .pal-sv { height: 160px; }
  #pal-menu .pal-hue { height: 22px; }
  #pal-menu .pal-sw { width: 44px; height: 30px; }
}

/* ── Sticky hover ──────────────────────────────────────────────────────────
   A tap on a touchscreen leaves :hover applied to whatever was tapped until
   something else is tapped. Any hover state that dims or recolours therefore
   becomes a state the element is STUCK in - which is how the masthead's menu
   button turned the colour of the band it sits on and stayed there after the
   row was collapsed again. Where there is no real hover, there is no hover
   styling in this bar. */
@media (hover: none) {
  .topbar-actions a:hover,
  .topbar-actions button:hover { color: inherit; }
  .topbar-more:hover { color: var(--text-primary); }
  [data-skin="broadcast"] .topbar-actions a:hover,
  [data-skin="broadcast"] .topbar-actions button:hover,
  [data-skin="broadcast"] .topbar-more:hover { color: var(--on-accent); }
}

/* ── Touch ergonomics ──────────────────────────────────────────────────────
   Deliberately NOT inside the breakpoint: a touchscreen laptop is wide and
   still needs 44px targets. touch-action: manipulation removes the ~300ms
   double-tap-to-zoom delay without disabling pinch zoom on the page. */
@media (pointer: coarse) {
  .btn, .trigger-btn, .nav a, .m-tabbar a { min-height: 44px; }
  .nav a { display: flex; align-items: center; justify-content: center; }
  .btn, .trigger-btn { justify-content: center; }
  /* The topbar was exempt from all of this: its nine controls measured 11-24px
     tall, so Sign Out, the theme switch and the client name - the row you reach
     for first on a phone - were the smallest targets on the screen. They are
     not .btn or .nav a, so the rule above never touched them. */
  .topbar-actions a,
  .topbar-actions button,
  .topbar-brand,
  .topbar-user a { min-height: 44px; display: inline-flex; align-items: center; }
  /* Two glyph-sized controls sized by route-level <style> blocks. Those blocks
     live in the BODY and therefore win an equal-specificity tie against this
     file, so .rte-btn is element-qualified here to clear its own 24px floor;
     .clk-step's route rule sets `height`, which min-height beats outright.
     .clk-step stops at 34px rather than 44px: it is the top and bottom third of
     a three-item, 26px-wide column (up arrow / digit / down arrow), and 44px
     each turns a ~74px clock into a ~110px one. 34px still clears the 24px
     minimum that 2.5.8 actually asks for. */
  button.rte-btn { min-height: 44px; }
  .clk-step { min-height: 34px; }
  a, button, .cal-day, .dropzone { touch-action: manipulation; }
}
