/*! os-responsive.css — Octe-OS responsive layer (mobile + tablet).
 *
 * DESIGN CONTRACT: this file contains ONLY @media blocks with a max-width condition. Above the
 * largest breakpoint it contributes exactly zero declarations, so the desktop design is byte-for-byte
 * what it was before this file existed. Nothing here changes a colour, a font, a radius or a spacing
 * token — it only changes how existing boxes FLOW when the viewport is too narrow to hold them.
 *
 * Loaded last (after os.css) so it wins on equal specificity without needing !important, except in
 * the one place where it must beat an inline style attribute — see §2.
 *
 * Breakpoints match the ones os.css already established (1040 / 820 / 520); no new scale invented.
 */

/* ==========================================================================
   §1  THE ROOT CAUSE — grid/flex children that refuse to shrink
   --------------------------------------------------------------------------
   A grid or flex item defaults to min-width:auto, meaning "never shrink below my content's
   min-content width". One wide descendant — a data table, a long unbroken string, a nowrap toolbar —
   therefore forces its whole ancestor chain wider than the viewport. The page then scrolls
   horizontally, and because the app is RTL the extra width appears on the LEFT, dragging every
   right-anchored element off the screen edge.
   Measured on the dashboard at 390px: document scrollWidth was 586px (196px of overflow) purely
   from this. min-width:0 lets the chain shrink so the inner scroll container (.os-tbl-wrap) does
   the scrolling instead of the page — which is exactly the behaviour requirement 2 asks for.
   ========================================================================== */
@media (max-width: 1040px) {
  .os-shell > *,
  .os-main-wrap > *,
  .os-main > *,
  .os-cols > *,
  .os-kpis > *,
  .os-card,
  .os-filterbar,
  .os-tbl-wrap { min-width: 0; }

  /* Same rule, one level deeper: a card's own children (chart, table wrap, toolbar row) must be
     allowed to shrink inside it, or the card just relocates the overflow instead of absorbing it. */
  .os-card > * { min-width: 0; max-width: 100%; }

  /* min-width:0 is necessary but NOT sufficient. The settings screens nest a wide table inside
     card > form > .os-tbl-wrap, all display:block. A block box takes its width from its containing
     block, but when the whole chain is auto-width the max-content size of the table (1496px on
     plan_settings) propagates outward and the card lays out at 1528px — min-width never enters into
     it, because nothing was ever asking the box to be SMALLER than its content, only allowing it.
     max-width:100% is what actually caps the chain, and it lets .os-tbl-wrap's own overflow-x:auto
     take over as the scroller. Measured on plan_settings at 320px: document 1546px -> 344px. */
  .os-card { max-width: 100%; }
  .os-card form, .os-card > form { max-width: 100%; min-width: 0; }
}

/* ==========================================================================
   §2  INLINE MULTI-COLUMN GRIDS
   --------------------------------------------------------------------------
   ~15 page layouts declare their columns in a style="" attribute (e.g. "1.1fr .9fr" on the contact
   detail, "1.15fr .85fr" on the lead detail). An inline declaration outranks every selector, so a
   media query cannot reach it without !important — this is the one legitimate use of it in the file.
   Matching the attribute rather than editing 15 view files keeps the desktop markup untouched and
   covers any inline grid added later.

   Deliberately NOT collapsed:
     [style*="auto-fill"]  — repeat(auto-fill, minmax(...)) is already fluid; it reflows on its own.
     [style*="auto 1fr"]   — <dl> label/value pairs; the auto column is content-sized and stays
                             readable at 320px. Collapsing them would orphan every label on its own
                             line and make the detail panes twice as tall for no gain.
     [style*="repeat("]    — fixed-count grids (the 7-day calendar) are handled in §6, where they get
                             a horizontal scroller instead of being flattened into one column.

   NOTE for future work: prefer a class over an inline grid so this override is unnecessary.
   ========================================================================== */
@media (max-width: 820px) {
  [style*="grid-template-columns"]:not([style*="auto-fill"]):not([style*="auto 1fr"]):not([style*="repeat("]) {
    grid-template-columns: 1fr !important;
  }
}

/* ==========================================================================
   §3  SIDEBAR DRAWER — overlay + body scroll lock
   --------------------------------------------------------------------------
   os.css already turns the rail into an off-canvas drawer at <=820px and os.js already toggles
   .is-open and closes on an outside click. Missing were the scrim behind it and the body scroll
   lock. Both are done here in CSS via :has(), so NO new JavaScript is added — the existing
   outside-click handler closes the drawer when the scrim is clicked, because the scrim is a sibling
   of the drawer rather than a descendant of it.
   Browsers without :has() simply keep today's behaviour (drawer works, no scrim) — it degrades
   silently rather than breaking.
   ========================================================================== */
.os-scrim { display: none; }

@media (max-width: 820px) {
  body:has(.os-sidebar.is-open) .os-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 49;                      /* under the drawer (50), over everything else */
    background: rgba(20, 18, 19, .48);
    animation: os-scrim-in .2s var(--octe-ease-out) both;
    /* Deliberately NO backdrop-filter: a blur over a full-viewport layer forces the compositor to
       re-rasterise everything behind it on every frame, which is exactly the kind of cost a mid-range
       phone pays for. A flat scrim reads the same and stays cheap. */
  }
  /* Body scroll lock. overscroll-behavior stops the scroll chaining to the page when the drawer's
     own list reaches its end. */
  body:has(.os-sidebar.is-open) { overflow: hidden; }
  .os-sidebar { overscroll-behavior: contain; }

  /* The drawer is a fixed-position element parked off-canvas; give it a hard max so it can never be
     wider than the screen it slides over. */
  .os-sidebar { max-width: 86vw; }
}

@keyframes os-scrim-in { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .os-scrim { animation: none !important; }
}

/* ==========================================================================
   §4  TOPBAR / HEADER
   --------------------------------------------------------------------------
   os.css already lets .os-search shrink and hides the user's name at <=820px. What was left: the
   search could still collapse to an unusable sliver next to the burger + icons, and the row had no
   wrap, so on the narrowest phones the icons could ride over each other.
   ========================================================================== */
@media (max-width: 820px) {
  .os-topbar { flex-wrap: nowrap; padding-inline: 18px; }
  .os-search { flex: 1 1 auto; max-width: none; }
  .os-icon-btn, .os-burger { flex: none; }        /* icons keep their hit area, never squash */
  .os-top-spacer { display: none; }               /* the spacer would steal the search's width */
}

@media (max-width: 520px) {
  .os-search { flex: 1 1 60px; min-width: 0; }
  .os-search input { width: 100%; min-width: 0; }
  .os-topbar { gap: 8px; padding-inline: 14px; }
  .os-icon-btn { width: 38px; height: 38px; }
}

/* The topbar is the one row that cannot wrap (it is the app chrome), so below ~400px the arithmetic
   simply stops working: burger 38 + five 38px icon buttons + the 48px user chip + six 8px gaps =
   334px of controls inside a 292px content box at 320px — a measured 24px overflow on EVERY page.
   Something has to give, and it is the topbar's search: it is a duplicate of the per-page search box
   that every list screen already renders, so hiding it costs no functionality while keeping every
   icon and the user menu at a full tap target. Icons tighten slightly to buy the remaining margin. */
@media (max-width: 400px) {
  .os-search { display: none; }
  .os-topbar { gap: 6px; padding-inline: 12px; }
  .os-icon-btn { width: 36px; height: 36px; }
  .os-user { padding: 4px; }
}

/* ==========================================================================
   §5  TABLES — scroll inside the container, never the page
   --------------------------------------------------------------------------
   .os-tbl-wrap is already overflow-x:auto and the frozen first column / actions column are already
   position:sticky with logical offsets. Those are left exactly as they are — this only guarantees
   the wrap itself cannot be stretched past the viewport by its own content, and gives the scroll a
   visible affordance on touch.
   ========================================================================== */
@media (max-width: 1040px) {
  .os-tbl-wrap { max-width: 100%; overflow-x: auto; }
  /* A table narrower than --tmin still fills the card; a wider one scrolls. Unchanged from os.css,
     restated only so the max-width above cannot be read as capping the table itself. */
  .os-tbl { min-width: max(100%, var(--tmin, 520px)); }
}

@media (max-width: 520px) {
  /* Tighter cells claw back ~40px per row on a 320px screen without touching the type scale. */
  .os-tbl thead th { padding: 9px 10px; }
  .os-tbl tbody td { padding: 10px 10px; height: 50px; }
  .os-tbl td.is-trunc { max-width: var(--cw, 190px); }
  .os-tbl td.is-clamp { max-width: var(--cw, 210px); }
}

/* ==========================================================================
   §6  FIXED-COUNT GRIDS (calendars, boards)
   --------------------------------------------------------------------------
   A 7-column month grid at 320px gives ~40px per day — unreadable, and flattening it to one column
   destroys the calendar. Instead the grid keeps its 7 columns and its container scrolls, which is
   the same pattern the tables use.
   ========================================================================== */
@media (max-width: 820px) {
  /* The month grid keeps its 7 columns and gets a scroller. The scroller has to be the grid's PARENT
     (a grid cannot scroll itself), and the calendars declare their columns inline with no wrapper
     element to hook — so :has() promotes whichever card contains one into the scroll container.
     620px keeps each day cell ~85px, which is the narrowest a date + a task chip stays readable. */
  .os-card:has([style*="repeat(7,1fr)"]) { overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; }
  [style*="repeat(7,1fr)"] { min-width: 620px; }

  /* Kanban: keep the columns side by side and scroll horizontally, the way every board app does.
     Both boards (the task board's .os-kanban and the plans board's inline flex scroller) declare
     fixed column widths — 288px and 268px — which are wider than a 320px screen minus its padding.
     Sized to the viewport instead so one column reads fully with the next peeking, which is the
     affordance that tells the user the row scrolls. */
  .os-kanban, [style*="overflow-x:auto"][style*="display:flex"] {
    overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
  }
  .os-kanban > *,
  [style*="overflow-x:auto"][style*="display:flex"] > [style*="flex:0 0"] {
    flex: 0 0 min(78vw, 288px) !important;
  }
}

/* ==========================================================================
   §7  FORMS, FILTERS, BUTTONS
   --------------------------------------------------------------------------
   Inputs go full width, control rows wrap instead of overflowing, and buttons keep a 44px touch
   target. Type sizes are untouched except the 16px input floor below, which is a platform
   requirement rather than a design choice.
   ========================================================================== */
@media (max-width: 820px) {
  .os-filterbar { padding: 14px; gap: 10px; }
  .os-filterbar .ff { flex: 1 1 160px; min-width: 0; }
  .os-filterbar input, .os-filterbar select, .os-filterbar .os-btn { width: 100%; }

  .os-field, .os-input, textarea.os-input, select.os-input { width: 100%; max-width: 100%; }

  /* Inline min-width on FORM CONTROLS is the second-biggest overflow source after §1: the tasks
     database filter bar alone carries six selects at min-width:140px, which cannot share a 256px
     usable row. Released here so they fall back to the flex basis above.
     Deliberately scoped to controls — the sticky table action column (.os-tbl__act, min-width:52px)
     and the pipeline step chips keep theirs, because those are inside their own scroll container. */
  .os-input[style*="min-width"],
  .os-filterbar [style*="min-width"],
  .os-field[style*="min-width"],
  .os-card > form [style*="min-width"] { min-width: 0 !important; }

  /* Popovers declare an inline min-width (up to 280px) that can exceed the 86vw cap os.css gives
     them — at 320px the cap is 275px, so the widest one overflowed by 5px. Cap wins. */
  .os-menu__pop[style*="min-width"] { min-width: 0 !important; max-width: min(86vw, 300px); }

  /* Control rows: wrap instead of overflowing. Restricted to rows that actually hold controls, so
     ordinary content rows (avatar + label, dot + text) keep their single-line composition. */
  form[style*="display:flex"],
  .os-filterbar,
  .os-card__head,
  .os-card__tools,
  .os-card > div[style*="display:flex"] { flex-wrap: wrap; }
}

@media (max-width: 520px) {
  /* At phone width even content rows need to wrap — below ~360px a three-part row (pill, title,
     trailing meta) cannot hold one line without clipping. */
  .os-main [style*="display:flex"]:not(.os-kanban):not([style*="overflow-x"]) { flex-wrap: wrap; }
  /* …except rows whose whole point is a fixed leading marker; those keep the marker inline. */
  .os-main [style*="display:flex"] > [style*="flex:none"] { flex: none; }
}

@media (max-width: 520px) {
  /* iOS zooms the page when a focused input's font-size is below 16px. Raising ONLY the input to the
     platform floor prevents that zoom (which is itself a layout shift) and leaves the rest of the
     type scale exactly as designed. */
  .os-input, .os-filterbar input, .os-filterbar select, .os-search input,
  input[type="text"], input[type="password"], input[type="email"],
  input[type="search"], input[type="number"], input[type="date"], textarea, select { font-size: 16px; }

  .os-btn { min-height: 44px; }                     /* accessible tap target */
  .os-btn--sm { min-height: 36px; }
  .os-card { padding: 18px 16px; }                  /* reclaim 16px of horizontal room */
  .os-card__head { flex-wrap: wrap; gap: 10px; }
  .os-card__tools { margin-inline-start: 0; }       /* tools drop under the title instead of clipping */
}

/* ==========================================================================
   §8  MEDIA — images, avatars, icons, charts
   ========================================================================== */
@media (max-width: 1040px) {
  img, svg, video, canvas { max-width: 100%; }
  .os-avatar, .os-stat__ic, .os-icon-btn svg, .os-tool svg { flex: none; }   /* scale, never squash */
  .os-chart svg.plot, .os-spark { width: 100%; height: auto; }
  /* Long unbroken strings (emails, URLs, file names) are a classic overflow source in a CJK/Arabic
     mixed layout — let them break rather than widen the page. */
  .os-card, .os-tbl td, .os-defs dd { overflow-wrap: anywhere; }
}

/* ==========================================================================
   §9  MODALS / POPOVERS / DIALOGS
   ========================================================================== */
@media (max-width: 820px) {
  dialog, .os-modal, .os-dialog, .os-popover, [role="dialog"] {
    max-width: calc(100vw - 24px);
    max-height: calc(100dvh - 24px);
    overflow: auto;
    overscroll-behavior: contain;
  }
}

/* ==========================================================================
   §10  RTL SAFETY NET
   --------------------------------------------------------------------------
   Everything above uses logical properties (inset-inline-*, margin-inline-*) so it is direction
   agnostic. The one physical assumption worth guarding is the notification popover, which os.css
   pins with left/right at <=520px — restated here so a narrower phone cannot push it off the
   inline-start edge.
   ========================================================================== */
@media (max-width: 520px) {
  .os-bell__pop { inset-inline: 10px; width: auto; max-width: none; }
}

/* ==========================================================================
   §11  DATA TABLES → STACKED CARDS ON PHONES
   --------------------------------------------------------------------------
   §5 keeps the table a horizontal scroller at tablet/desktop widths. Below 640px — phones and
   phablets in portrait — that swiping is the poor experience we're removing: this section drops the
   table grid entirely and re-flows each ROW into a self-contained card. No horizontal scroll, and
   every field stays on screen. Tablets (>=641px) are deliberately untouched — the brief is to keep
   the desktop/tablet table exactly as it is.

   640px, not the app's 520/820, because that is the widest a phone gets in portrait: it catches every
   phone while leaving tablets (>=768) on the real table. It is the only new number here and it means
   "phone card" throughout.

   How it works with ZERO markup churn: every list renders through os_view_render, which already
   stamps data-label="<column>" on each cell. So the label for each line is read straight from the
   existing attribute via ::before — no per-page HTML. The first column becomes the card title; the
   row's "…" menu moves to the card corner (still portaled to <body> by os.js, so it keeps working);
   pills, avatars, status colours and RTL all ride along untouched because the cell CONTENTS are never
   rewritten. The same rules also style the few raw <table class="os-tbl"> (dashboard + settings
   editors), which had data-label added to their cells so the treatment is identical everywhere.

   WIDE tables (leads, company task DB…): columns past the primary set carry .is-more and fold under a
   per-row "Show More" toggle the View Engine prints (.os-tbl__more). The fold is pure CSS via :has()
   — no JavaScript, so no event listener can break — and lives in an @supports block so a browser
   without :has() just shows every field (a taller card) instead of hiding data behind a dead control.
   Desktop never sees any of it: .os-tbl__more is display:none in os.css and .is-more is inert here.
   ========================================================================== */
@media (max-width: 640px) {
  .os-tbl-wrap { overflow: visible; }            /* stop being a sideways scroller; let cards flow down */

  /* drop the table box model → block-level cards */
  .os-tbl { display: block; width: 100%; min-width: 0; font-size: 14px; }
  .os-tbl thead { display: none; }               /* labels now come from data-label; sort stays a desktop affordance */
  .os-tbl tbody { display: block; }

  .os-tbl tbody tr {
    display: flex; flex-direction: column;
    position: relative;                           /* anchor for the corner row-menu */
    background: var(--octe-surface);
    border: 1px solid var(--octe-line);
    border-radius: var(--octe-r-in);
    box-shadow: var(--octe-shadow-sm);
    padding: 6px 16px 12px;
    margin-bottom: 12px;
  }
  .os-tbl tbody tr:last-child { margin-bottom: 0; }
  .os-tbl tbody tr:hover td { background: transparent; }   /* the desktop row-hover fill has no place on a card */

  /* each cell → one "label ………… value" line */
  .os-tbl tbody td {
    order: 0;
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    height: auto; min-height: 44px;               /* comfortable touch row */
    padding: 8px 0; margin: 0;
    border: 0; border-bottom: 1px solid var(--octe-line-2);
    background: transparent !important;           /* the card (tr) owns the surface / selected fill */
    max-width: none !important; white-space: normal; overflow: visible; text-overflow: clip;
    text-align: start;
  }
  .os-tbl tbody td::before {
    content: attr(data-label);
    flex: 0 0 auto; max-width: 42%;
    color: var(--octe-muted); font-size: 12px; font-weight: 600; line-height: 1.35;
  }
  .os-tbl tbody td:not([data-label])::before,
  .os-tbl tbody td[data-label=""]::before { content: none; }   /* action / toggle cells carry no label */
  .os-tbl tbody td > * { min-width: 0; }          /* let a long value wrap rather than widen the card */

  /* undo the desktop single-line truncation so the card shows the value in full */
  .os-tbl td.is-trunc, .os-tbl td.is-clamp { max-width: none; white-space: normal; }
  .os-tbl td.is-clamp > b, .os-tbl td.is-clamp > a, .os-tbl td.is-clamp .m-sub {
    white-space: normal; overflow: visible; text-overflow: clip;
  }

  /* nothing scrolls sideways now, so release the frozen first / actions columns */
  .os-tbl thead th:first-child, .os-tbl tbody td:first-child,
  .os-tbl--actions thead th.os-tbl__act, .os-tbl--actions tbody td.os-tbl__act { position: static; }

  /* View Engine: the first column is the card TITLE — full width, no label prefix, stronger divider */
  .os-tbl--fit tbody td:first-child {
    order: -1; display: block;
    padding-block: 2px 10px; padding-inline: 0 40px;   /* 40px inline-end keeps clear of the corner menu */
    border-bottom: 1px solid var(--octe-line);
    font-size: 15px; font-weight: 600; color: var(--octe-ink);
  }
  .os-tbl--fit tbody td:first-child::before { content: none; }
  .os-tbl--fit tbody td:first-child .m-sub { font-weight: 400; }

  /* row "…" menu → card corner (inline-end = left in RTL). When os.js portals the popup to <body>,
     only the trigger moves and the menu opens/flips/clamps exactly as on desktop. */
  .os-tbl--actions tbody td.os-tbl__act {
    position: absolute; inset-block-start: 8px; inset-inline-end: 10px;
    width: auto; min-width: 0; padding: 0; margin: 0; border: 0;
    display: block; z-index: 10;
  }
  .os-tbl--actions tbody td.os-tbl__act::before { content: none; }

  /* Row-menu popup — in-card fallback position. .os-menu__pop is position:fixed with NO offsets, so
     on its own it has no on-screen coordinates: it depends on os.js moving it to <body> and setting
     left/top. Until/unless that runs, it renders at its CSS static position — which in this card
     layout lands off-screen (the ⋮ sits in the card's inline-end corner, the card's entrance-animation
     transform is the fixed popup's containing block, and RTL block-flow pushes a 172px popup off the
     inline-start edge). Anchoring the still-in-card popup to its button gives it real coordinates so
     it is always visible, sitting above the card via the actions cell's z-index. The instant os.js
     portals it to <body> this selector stops matching, so the desktop/portaled path is untouched. */
  .os-tbl .os-menu[open] > .os-menu__pop {
    position: absolute;
    top: 100%;
    inset-inline-end: 0;
    margin-top: 6px;
  }

  /* selected row → filled card (the desktop child overrides for pills/avatars/text still apply) */
  .os-tbl tbody tr.is-selected { background: var(--octe-red); border-color: var(--octe-red); }
  .os-tbl tbody tr.is-selected td { border-bottom-color: rgba(255,255,255,.22); }
  .os-tbl tbody tr.is-selected td::before { color: rgba(255,255,255,.8); }
  .os-tbl--fit tbody tr.is-selected td:first-child { color: #fff; border-bottom-color: rgba(255,255,255,.3); }

  /* raw settings editors: the inline-editable inputs fill the value side instead of a fixed width */
  .os-tbl tbody td .os-input { flex: 1 1 auto; width: auto; max-width: none !important; min-width: 0; }
  .os-tbl tbody td .os-swatches { flex-wrap: wrap; justify-content: flex-end; }

  /* order the "Show More" fold: primary fields (0) · toggle (1) · folded fields (2) */
  .os-tbl tbody td.is-more { order: 2; }
  .os-tbl__more { order: 1; }
}

/* The fold only exists where :has() does. Without it, .is-more stays visible (a longer card) rather
   than trapping data behind a toggle that could never open — matching the file's degrade-silently rule. */
@supports selector(:has(*)) {
  @media (max-width: 640px) {
    .os-tbl__more { display: block; padding: 10px 0 2px; border: 0; background: transparent; }
    .os-tbl__more::before { content: none; }
    .os-more-chk {                                /* visually hidden but still focusable (keyboard + SR) */
      position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
      overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
    }
    .os-more-lbl {
      display: inline-flex; align-items: center; gap: 6px;
      color: var(--octe-red); font-size: 12.5px; font-weight: 700;
      cursor: pointer; user-select: none; padding: 4px 2px;
    }
    .os-more-lbl .lbl-less { display: none; }
    .os-more-chev { width: 15px; height: 15px; transition: transform var(--octe-micro); }
    .os-more-chk:focus-visible + .os-more-lbl { outline: 2px solid var(--octe-red); outline-offset: 3px; border-radius: 6px; }

    /* folded by default; the row's own checkbox reveals its .is-more cells */
    .os-tbl--fit tbody td.is-more { display: none; }
    .os-tbl--fit tbody tr:has(.os-more-chk:checked) td.is-more { display: flex; }
    .os-tbl--fit tbody tr:has(.os-more-chk:checked) .lbl-more { display: none; }
    .os-tbl--fit tbody tr:has(.os-more-chk:checked) .lbl-less { display: inline; }
    .os-tbl--fit tbody tr:has(.os-more-chk:checked) .os-more-chev { transform: rotate(180deg); }
  }
}
