/* ==========================================================================
   responsive.css — SlabDraw responsive design system
   --------------------------------------------------------------------------
   Single source of truth for everything responsive: breakpoints, touch
   targets, safe-area handling, viewport utility classes. Every other CSS
   file in the app references the variables and breakpoints declared here.
   Loaded FIRST in the cascade so its tokens are available to every rule
   that follows.

   Why ONE file owns this: before this file, breakpoints were scattered
   across 13 CSS files using 12 different pixel values (480/540/600/640/
   660/720/768/820/860/900/1024/1100/1180). Layouts shifted at unpredictable
   widths because no one knew which threshold any given surface used. This
   file freezes the contract: three breakpoints, period.

   IF YOU NEED A NEW BREAKPOINT, FIX THE LAYOUT SO YOU DON'T. Adding a
   fourth breakpoint here will be the start of the next consolidation.
   ========================================================================== */

:root {
  /* ── BREAKPOINTS ──────────────────────────────────────────────────────
     Three tiers, every surface designs for these and only these:
       phone:  < 768   → portrait phone (S25 Ultra is 412×915)
       tablet: 768-1023 → iPad portrait, large phone landscape
       desktop:≥ 1024  → desktop + iPad landscape (1180-1366)
     Stored as both numeric values (for matchMedia in JS) and as the
     pixel constants used inside @media queries. Keep both in sync. */
  --bp-phone:  768px;   /* < this width = phone treatment */
  --bp-tablet: 1024px;  /* < this width = tablet treatment, ≥ = desktop */

  /* ── TOUCH TARGETS ────────────────────────────────────────────────────
     iOS HIG and Material both call for 44px minimum hit area. We adopt
     44px across the board for every interactive element a user can tap.
     Use this var instead of hardcoding heights so a future bump (e.g. to
     48px for accessibility) is a one-line change. Pair this with
     `touch-action: manipulation` on each interactive element to disable
     the 300ms click delay on touch devices. */
  --tap: 44px;

  /* ── SAFE-AREA INSETS ─────────────────────────────────────────────────
     Modern phones and iPads have notches, Dynamic Island, home indicators,
     and rounded corners that cut into the viewport. The browser exposes
     env(safe-area-inset-*) so layouts can avoid those zones. We expose
     them as variables so consumers don't repeat the env() syntax. The
     `0px` fallback is critical — without it, browsers without safe-area
     support evaluate the env() to nothing and break math like
     calc(16px + var(--safe-bottom)). */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left, 0px);
  --safe-right:  env(safe-area-inset-right, 0px);
}

/* ── VISIBILITY UTILITY CLASSES ─────────────────────────────────────────
   Three utility classes that hide an element on the WRONG tier. The
   class name describes WHERE the element shows, not where it hides —
   `.desktop-only` means "only show on desktop, hide on phone/tablet".

   IMPORTANT — phone detection uses `pointer: coarse` AND a max-width
   guard. A Samsung S25 Ultra in LANDSCAPE is ~915×412 = >768px width,
   which would put it in the tablet tier under width-only breakpoints.
   But it's still a phone. The pointer-coarse + small-height combo
   identifies a phone in landscape regardless of width. Desktop monitors
   are pointer-fine; iPads in landscape are pointer-coarse but >500px
   tall. Phones in landscape are pointer-coarse AND <500px tall. */
@media (max-width: 767px),
       (pointer: coarse) and (max-height: 500px) {
  .desktop-only,
  .tablet-only { display: none !important; }
}
@media (min-width: 768px) and (max-width: 1023px) and (min-height: 501px),
       (min-width: 768px) and (max-width: 1023px) and (pointer: fine) {
  .phone-only,
  .desktop-only { display: none !important; }
}
@media (min-width: 1024px) and (pointer: fine) {
  .phone-only,
  .tablet-only { display: none !important; }
}
/* Touch devices ≥1024px (iPad landscape) get tablet treatment, not desktop. */
@media (min-width: 1024px) and (pointer: coarse) and (min-height: 501px) {
  .phone-only,
  .desktop-only { display: none !important; }
}

/* ── SHARED MOBILE PRIMITIVES ───────────────────────────────────────────
   Three building blocks used across every page's mobile layout. Built once
   here so per-page CSS doesn't reinvent them.

   1. .sd-hscroll — horizontal scroll-snap row.
      For collections that don't need to fit (KPI cards, filter chips, tab
      strips, tool palettes). One row, scrolls horizontally with snap-to-
      child, scrollbar hidden. Children are NOT shrunk to fit — they keep
      their natural width and overflow.
      Use: <div class="sd-hscroll"><div>card</div><div>card</div>...</div>
      Children get auto scroll-snap-align: start so the leading edge snaps.

   2. .sd-form-stack — collapse multi-column form rows on phone.
      Apply to ANY container whose children are flex/grid columns that
      should stack on phone. Replaces 6+ near-identical "@media (max-width)
      { .x { flex-direction: column } }" rules across surfaces.
      Use: add .sd-form-stack to the existing .bd-row / .crm-field-row /
      etc. Their existing desktop layout stays; phone gets stacked.

   3. .sd-mobile-bottom-sheet wrapper — convention, not a class.
      Any modal that occupies > 40% of screen on phone uses sdOpenSheet()
      from js/sd-bottom-sheet.js (NOT a hand-rolled overlay). Documented
      in sd-bottom-sheet.js. No new CSS needed — that primitive owns it.

   Hide-scrollbar inside .sd-hscroll uses the cross-browser pattern:
   ::-webkit-scrollbar { display: none } for WebKit/Blink, scrollbar-width
   for Firefox, -ms-overflow-style for old Edge. */
.sd-hscroll {
  display: flex;
  flex-direction: row;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;        /* old Edge / IE */
  /* Pad the trailing edge so the last card has a peek-of-empty when
     fully scrolled, signalling end-of-row (iOS/Material convention). */
  padding-right: 16px;
}
.sd-hscroll::-webkit-scrollbar { display: none; }   /* WebKit / Blink */
.sd-hscroll > * {
  flex: 0 0 auto;                  /* don't shrink children to fit */
  scroll-snap-align: start;
}

@media (max-width: 767px) {
  .sd-form-stack {
    flex-direction: column !important;
    align-items: stretch !important;
  }
  .sd-form-stack > * {
    width: 100% !important;
  }
  /* Grids declared with `display: grid` need their template-columns
     overridden to a single column. The .sd-form-stack class works on
     both flex and grid containers without the caller knowing which. */
  .sd-form-stack[style*="display: grid"],
  .sd-form-stack.is-grid {
    display: grid !important;
    grid-template-columns: 1fr !important;
  }
}

/* ── COMPACT MOBILE TOP STRIP ───────────────────────────────────────────
   44px-tall fixed top strip used on phone for surfaces that hide their
   normal multi-row header (canvas, full-screen bid drawer, etc.).
   Carries: hamburger → nav drawer, customer/project context, publish
   state, account chip. Replaces ~80px of desktop chrome with one row
   of the same critical affordances.

   Markup contract (canvas + future surfaces):
     <div class="sd-mtop phone-only">
       <button class="sd-mtop__btn" data-sd-action="sdToggleNav" ...>≡</button>
       <div class="sd-mtop__center">…customer / project pill…</div>
       <div class="sd-mtop__right">…publish · account…</div>
     </div>

   Per-page JS owns the center + right contents (CRM badge for canvas,
   workspace name for CRM hub, etc.). This file owns the layout shell. */
.sd-mtop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 44px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 0 8px;
  padding-top: var(--safe-top, 0px);
  background: var(--surface, #1a1d27);
  border-bottom: 1px solid var(--border, #2a2e3c);
  z-index: 70;     /* above canvas (z-index 60 pill bar), below modals */
}
.sd-mtop__btn {
  width: var(--tap, 44px);
  height: var(--tap, 44px);
  min-height: var(--tap, 44px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--text, #e6e8ee);
  cursor: pointer;
  border-radius: 8px;
  flex-shrink: 0;
  -webkit-tap-highlight-color: transparent;
}
.sd-mtop__btn:active { background: rgba(255, 255, 255, 0.06); }
.sd-mtop__center {
  flex: 1 1 auto;
  min-width: 0;     /* allow text-overflow inside */
  display: flex;
  align-items: center;
  gap: 8px;
  font: 600 13px/1 Inter, system-ui, sans-serif;
  color: var(--text, #e6e8ee);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sd-mtop__center > * {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sd-mtop__right {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

/* Pages that show .sd-mtop must reserve 44px at top of their content.
   Add .sd-mtop-spacer or `body.sd-mtop-on { padding-top: ... }` per-page.
   The strip itself is fixed/absolute so it overlays — without padding,
   page content slides under it. */
body.sd-mtop-on {
  padding-top: calc(44px + var(--safe-top, 0px)) !important;
}

/* ── INTERACTIVE-ELEMENT BASELINE ───────────────────────────────────────
   Every button-like element in the app should hit the --tap minimum.
   Adding it here is a fail-safe so a CSS author who forgets to set a
   min-height on a new button doesn't ship a 20px tap target. Per-
   element CSS can still override (some controls are intentionally
   smaller because they're inside a parent that meets the minimum, e.g.
   a 24px chip inside a 48px row). The override is `min-height: auto`
   on the override case — explicit, searchable. */
@media (pointer: coarse) {
  button,
  [role="button"],
  input[type="checkbox"],
  input[type="radio"],
  input[type="submit"],
  input[type="reset"],
  input[type="button"],
  select {
    min-height: var(--tap);
  }
  /* Inputs that hold typed text need to be tall enough to comfortably
     position the caret on touch — 44px caret targets feel right on iOS. */
  input[type="text"],
  input[type="number"],
  input[type="email"],
  input[type="tel"],
  input[type="password"],
  input[type="search"],
  input[type="url"],
  input[type="date"],
  textarea {
    min-height: var(--tap);
  }
}

/* ── PHONE CANVAS ROUTE — chromeless full-viewport canvas ───────────────
   Industry pattern (Miro, Figma mobile, native field-measure apps): on
   phone, the canvas takes the full viewport. No header rows, no tool row,
   no info bar. Navigation/tools/panels live in the .sd-mtop strip + pill
   bar at the bottom.

   The .h-nav-group (slide-in drawer) lives INSIDE .hrow-1 in header.php,
   and CSS `display: none` on a parent kills its children's rendering even
   if children are position:fixed. So we can't blanket-hide .hrow-1.
   Instead: collapse .hrow-1 to zero height + hide its visible direct
   children, but un-hide the .h-nav-group so the hamburger drawer keeps
   working. .hrow-2 and .hrow-3 contain only canvas-specific toolbars
   that the pill bar replaces — those can be display:none normally. */
@media (max-width: 767px),
       (pointer: coarse) and (max-height: 500px) {
  body.sd-yard-open header > .hrow-2,
  body.sd-yard-open header > .hrow-3 {
    display: none !important;
  }
  body.sd-yard-open header > .hrow-1 {
    height: 0 !important;
    min-height: 0 !important;
    padding: 0 !important;
    border: none !important;
    overflow: visible !important;     /* let the fixed-position drawer escape */
    background: transparent !important;
  }
  /* Hide each direct child of .hrow-1 EXCEPT the slide-in drawer itself.
     The drawer (.h-nav-group) is position:fixed so it sits outside the
     collapsed row's flow and slides in on body.h-nav-open. */
  body.sd-yard-open header > .hrow-1 > * { display: none !important; }
  body.sd-yard-open header > .hrow-1 > .h-nav-group { display: flex !important; }
  body.sd-yard-open header { border: none !important; background: transparent !important; }
}

/* ── IMMERSIVE / FULLSCREEN MODE ────────────────────────────────────────
   On TABLET/DESKTOP the canvas normally shows header + toolbar + panels;
   immersive hides them so the user can present or focus on a single bid.
   On PHONE everything except the pill bar is already hidden by the phone
   media query above — immersive there only adds the browser fullscreen
   request + landscape orientation lock (driven by the JS, not this CSS).

   This rule lives in responsive.css (not yard-canvas.css) because the
   pattern generalises — any page that wants a "present mode" can opt in
   by setting body[data-sd-immersive="1"]. */
body[data-sd-immersive="1"] header > .hrow-2,
body[data-sd-immersive="1"] header > .hrow-3,
body[data-sd-immersive="1"] .sd-mtop,
body[data-sd-immersive="1"] .yard-info,
body[data-sd-immersive="1"] .slab-tabs,
body[data-sd-immersive="1"] .yard-toolbar,
body[data-sd-immersive="1"] .sd-side-panel,
body[data-sd-immersive="1"] .sd-resize-handle {
  display: none !important;
}
/* Same hrow-1 collapse pattern as the canvas-route rule — keep nav drawer
   reachable but invisible chrome. */
body[data-sd-immersive="1"] header > .hrow-1 {
  height: 0 !important;
  min-height: 0 !important;
  padding: 0 !important;
  border: none !important;
  overflow: visible !important;
  background: transparent !important;
}
body[data-sd-immersive="1"] header > .hrow-1 > * { display: none !important; }
body[data-sd-immersive="1"] header > .hrow-1 > .h-nav-group { display: flex !important; }
body[data-sd-immersive="1"] header {
  border: none !important;
  background: transparent !important;
}
body[data-sd-immersive="1"] .sd-canvas-body,
body[data-sd-immersive="1"] .yard-viewport {
  height: 100vh !important;
  height: 100dvh !important;   /* dynamic viewport — accounts for browser bars */
}
/* CRITICAL FIX — without this, _sdYardFitView reads 300px+300px panel
   widths from the CSS vars (which the desktop default sets), subtracts
   600px from the actual viewport, and frames the slab into a 315px-wide
   strip on the right. That's why the user sees "ruler in the middle" in
   landscape immersive: the panels are display:none but the vars aren't
   zeroed at this breakpoint. Phone media query zeroes them at <768px,
   but landscape S25 (~915px) is in tablet tier — this rule covers it. */
body[data-sd-immersive="1"] .sd-canvas-body {
  --sd-slabs-w:   0px !important;
  --sd-layers-w:  0px !important;
  --sd-cutouts-w: 0px !important;
}
/* In immersive mode the yard-overlay's desktop `top: 68px` (which
   reserved space for the two-row desktop header) becomes wrong — the
   header is hidden, the canvas should reach the very top of the viewport.
   Force top:0 + bottom:0 here, regardless of width tier. Fixes the
   landscape-immersive "ruler/canvas offset down 68px" bug where the
   auto-fit framed content into a viewport rect that didn't match the
   actually-visible area. */
body[data-sd-immersive="1"] .yard-overlay {
  top: 0 !important;
  bottom: 0 !important;
}
body[data-sd-immersive="1"] .bid-overlay,
body[data-sd-immersive="1"] .asm3d-overlay {
  top: 0 !important;
  bottom: 0 !important;
}
/* When immersive locks the phone to landscape, viewport widens past 768px
   and the .phone-only utility hides the pill bar. Un-hide it so the exit
   button is always reachable. The immersive rules in yard-canvas.css then
   collapse all pills except Full so only the exit icon shows. */
body[data-sd-immersive="1"] .sd-pill-bar {
  display: flex !important;
}

/* Disable the 300ms tap delay on every interactive surface. iOS Safari
   stopped applying this years ago in most cases, but Android browsers
   still benefit, and explicit `touch-action: manipulation` also disables
   double-tap-to-zoom which is desirable inside an editor. */
@media (pointer: coarse) {
  button,
  [role="button"],
  a,
  select,
  label,
  input[type="checkbox"],
  input[type="radio"] {
    touch-action: manipulation;
  }
}

/* ── HOVER MEDIA-FEATURE GUARD ──────────────────────────────────────────
   Wrap any hover-only flourish with `@media (hover: hover)` so touch
   devices don't get stuck in a hover-painted state after a tap. Most
   modern browsers clear hover state on touch, but this guard makes the
   intent explicit and saves a class of bugs where a hover background
   sticks on the last-tapped button. We don't enforce this app-wide
   here — too disruptive to existing CSS — but new hover rules SHOULD
   wrap themselves; legacy ones get the wrap during the consolidation
   pass. */
