/* ==========================================================================
   Global page header — the in-page top bar that page content migrates into as
   the dark global-app-header is emptied program-wide. Four shapes:

     .global-page-header--row   list pages (pre-page-top-design pick): title
                                left, a search/filter cluster, actions right.
     .global-page-header--card  list pages (page-top design pick, ADR-131
                                controls-canon skin pass): one card holding a
                                title bar, a toolbar row (search/filters/view
                                switcher), and the list body flush inside.
     .global-page-header--toolbar  list pages whose body is already cards: no
                                title, the controls alone in a small card, and
                                the body flush on the page background below it.
     .global-record-header      record/builder pages: a single card holding a
                                back link, title, status chip, and actions, with
                                an optional tab strip built into its bottom edge.

   The two card shapes generalize the opportunity detail's in-page header
   pattern (nothing rides the global top header — it all lives in the in-page
   header); opportunities is record_header's title_template/body_template/
   tabs_template consumer. All three shapes sit on the light content surface.
   ========================================================================== */

.global-page-header--row {
    display: flex;
    align-items: center;
    gap: var(--spacing-smd);
    flex-wrap: wrap;
    margin-bottom: var(--spacing-mdlg);
}

/* Title is shared with the card shape below; the card adds truncation since its
   heading also carries the status chip. */
.global-page-header__title,
.global-record-header__title {
    margin: 0;
    font-family: var(--font-family-heading);
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-size: clamp(0.9rem, 2vw, 1.1rem);
    line-height: 1.1;
    color: var(--color-text);
}

/* The search / filter cluster sits next to the title; actions get pushed right. */
.global-page-header__cluster {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    min-width: 0;
}

/* Actions cluster is shared with the card shape below; the card adds wrapping
   since its actions can hold more controls than a list row's. */
.global-page-header__actions,
.global-record-header__actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-xsm);
    flex-shrink: 0;
    margin-left: auto;
}

/* -- Card shape (list pages, page-top design pick) --------------------------
   The page-top design pick: the search box, filters, and view switcher get
   their own toolbar row, hairline-separated under the title, with the list
   body flush inside the same card. Structurally the same one-card-with-
   hairline-sections idea as .global-record-header below, just with a toolbar
   section instead of a tab strip. */

/* Card container is shared with .global-record-header below: both read as ONE
   card, drawing the only border, radius, and background — the bar and the
   optional toolbar/tab-strip section are borderless sections inside it.
   overflow:hidden clips them to the rounded corners; the card shape reopens the
   down axis just below, for the filter panels. This rule sets no
   transform/filter/contain, so it never becomes a CSS containing block — a
   nested position:fixed overlay (an offcanvas, a modal) escapes the clip and
   renders full-viewport as expected. If a future change adds transform,
   filter, or contain here, any fixed-position overlay nested in a
   body_template would get trapped inside this clip instead. */
.global-page-header--card,
.global-record-header {
    background: var(--color-card-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    margin-bottom: var(--spacing-mdlg);
}

/* The card's last section holds filter comboboxes on the list pages, and their
   panels open DOWNWARD past the card's bottom edge — overflow:hidden above ate
   them whole, so a filter opened to an empty space (the bill dashboard's
   Status/Month/Year, 249 of 256px clipped away). Only the down axis is opened:
   clip keeps the sideways guard the bar and tab strip rely on, and it is the one
   value that lets the other axis stay visible — overflow-y:visible beside
   overflow-x:hidden would compute to auto and clip all over again. The
   --toolbar shape already drops the clip for exactly this reason. Safe because
   no section inside paints a background, so there is nothing to bleed past the
   rounded corners; clip is not a containing block either, so the nested
   fixed-position overlays noted above still escape. */
.global-page-header--card {
    overflow: clip visible;
}

/* --pinned: the header card stays put while the page scrolls under it
   (pinned=True in page_header.html). For a page-scrolling body — the
   opportunity card view, the sign up and bills dashboards — whose header
   controls used to ride off the top and stay gone until the user scrolled all
   the way back. A viewport-fit page (.global-page-fill, main.css) gets the same
   frozen header as a consequence of the page never scrolling and does not need
   this.

   The offset is the two tokens .global-app-header is built from PLUS the
   gutter .global-app-content already puts above the card, not a measured
   number, so a change to any of the three moves the pinned bar with it. That
   third term is Nick's ask (2026-08-03): pinned hard against the top bar, the
   card read as a second strip of chrome; holding the same gap it has at rest
   keeps it reading as the page's own card that happens to stay put.

   --z-sticky is the ladder's rung for pinned toolbars: below the sidebar and
   the drawer panel. Sticky plus a z-index makes the card a stacking context,
   so popovers inside it (Filter, Sort by) no longer carry their own
   --z-dropdown out to the page — the card paints them at --z-sticky with the
   rest of its contents. The cost is the cap: anything outside the card above
   --z-sticky covers them, even at a number below their own. */
.global-page-header--pinned {
    position: sticky;
    top: calc(var(--header-bottom) + var(--page-gutter));
    z-index: var(--z-sticky);
}

/* Content fades out as it slides under the pinned card instead of being cut
   off at its edge (Nick, 09-03: "things fade out ... so that they cleanly can
   go behind the top container"). A canvas-to-transparent wash hung off the
   card's bottom; the card's `overflow: clip visible` cuts it to the card's
   width (right — the strips beside the card carry the sidebar and drawer
   shadows) while leaving the down axis open.

   The height is exactly the card's own margin-bottom, so at rest the wash
   covers only the canvas gap under the card — invisible — and never hazes the
   content below. Scrolling content fades inside that gap before it ducks
   under the card. Keep the two values in lockstep. */
.global-page-header--pinned::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: var(--spacing-mdlg);
    background: linear-gradient(to bottom, var(--color-canvas), transparent);
    pointer-events: none;
}

/* The gap the pinned offset holds open, repainted so content does not scroll
   through it — one gutter of canvas that pins under the top bar.

   The content column owns it, not the bar. Hung off the bar it was an opaque
   band riding a sticky element, and it painted straight over the notice strips
   that mount flush underneath (components/notice-strip.css). At the top of the
   column instead, the band IS the column's top padding — same colour, same
   height, which is why the padding comes off — so it shows nothing until the
   page scrolls, and it hands off exactly: the strips clear the bar at the same
   moment the band reaches it.

   The card cannot own the repaint either. It cannot paint outside its own
   padding box sideways (`overflow: clip visible` above), so a band inside the
   card stops one pixel short at each side and lets the content's hairline
   border scroll up through the gap (measured: 22 leaked pixels a side at
   1440px). overflow-clip-margin reads like the fix and is not: Chromium only
   honours it on a box that clips BOTH axes.

   Inside the column the band is as wide as the card for free, in both drawer
   states — the drawer narrows .global-app-content itself (drawer.css, "Resize
   only the CONTENT"), so no --content-push term. The strips either side stay
   clear, which they must: the sidebar and docked drawer each cast a shadow
   ~32px into them and a full-width band paints those out.

   --color-canvas, not --color-page-bg: an org running a custom "Every page"
   background republishes --color-canvas on .global-app-main; a band painted
   --color-page-bg would be default grey on every such org. */
.global-app-main:has(.global-page-header--pinned) .global-app-content {
    padding-top: 0;
}

.global-app-main:has(.global-page-header--pinned) .global-app-content::before {
    content: "";
    /* A ::before is inline, and height does nothing to an inline box. Without
       this the band held the gutter open only on the fill pages, whose column
       is a flex container; every other pinned page lost it entirely. */
    display: block;
    position: sticky;
    top: var(--header-bottom);
    z-index: var(--z-raised);
    flex: 0 0 auto;
    height: var(--page-gutter);
    background: var(--color-canvas);
}


/* Bar layout is shared with .global-record-header__bar below. */
.global-page-header--card .global-page-header__bar,
.global-record-header__bar {
    display: flex;
    align-items: center;
    gap: var(--spacing-smd);
    padding: var(--spacing-smd) var(--spacing-md);
    flex-wrap: wrap;
}

.global-page-header--card .global-page-header__title {
    flex: 1 1 auto;
}

.global-page-header__toolbar {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    flex-wrap: wrap;
    padding: var(--spacing-sm) var(--spacing-md);
    border-top: 1px solid var(--color-grey-100);
}

/* --notitle: the card opens straight on the toolbar row, so that row's hairline
   has no title bar above it to divide from — it would only double the card's own
   top border. The actions cluster rides the end of the row on the base rule's
   own margin-left:auto. */
.global-page-header--notitle .global-page-header__toolbar {
    border-top: 0;
    padding-top: var(--spacing-smd);
}

/* Every control in a page header keeps working while it filters. The global
   .htmx-request rule (main.css) fades and deadens whatever htmx marks, and the
   shared search points hx-indicator at its own wrapper, so measured on the rig
   the field the person was typing in sat at opacity .7 with pointer-events:none
   until the reply came back. Results land elsewhere (#list-container), so the
   field was the only thing showing it. Same opt-out the dark bar's copies of
   these controls (.global-app-header .global-search-input-wrapper.htmx-request, filter-bar.css)
   and its quick search (topbar-search.css) already carry — this is the light
   in-page header finally getting it too.

   What beats the global rule is specificity, NOT load order: three classes
   (0,3,0) against its bare .htmx-request (0,1,0). This file loads BEFORE
   main.css (base/base.html:111 vs :156), so a tie would go to main.css. The
   margin is two classes, not one — measured in Chromium, a two-class selector
   still wins and the field stays at opacity 1. Only a single class ties, and
   that is where the fade comes back with nothing to show it. */
.global-page-header .global-search-input-wrapper.htmx-request,
.global-page-header .global-header-filter.htmx-request,
.global-page-header .global-filter-select.htmx-request {
    opacity: 1;
    pointer-events: auto;
}

/* One search width for every page header — never standardized before this rule.
   The shared .global-page-search is flex:1 1 260px, so on a
   header carrying only a few other controls it grew to eat the whole row: measured at
   1440px the eight pages with a page search came out in six different widths, from
   320px on the opportunities header to 1158px on forms. HubSpot keeps this control
   about 320px and left-aligned, so pin the flex-basis + max-width and drop the grow
   here once instead of adding a per-page exception each time a header looks wrong.
   This rule replaces the --tabsactions and --notitle exceptions that grew here one at
   a time, which is the drift it exists to stop. Scoped to .global-page-header so the
   top bar's own quick search is untouched. */
.global-page-header .global-page-search {
    flex: 0 1 20rem;
    max-width: 20rem;
}

/* With the search capped, whatever sits beside it would otherwise huddle against it and
   leave the rest of the row empty. The rule: search on the left, everything else on
   the right. The auto margin on the first sibling after the search eats all the slack,
   so that control and everything following it ride the right edge as one cluster —
   the view switcher on forms, the month/year nav on sign up.
   Opportunities (--tabsactions) is excluded: it already right-aligns only its date-nav
   (below) and deliberately keeps the lens selectors left next to search.

   One thing to know before adding a second control after the search: TWO auto
   margins in the same flex row split the free space EVENLY, so neither rides an
   edge. The roster's old status segmented sat right here, next to __actions' own
   auto margin (:57), and parked at the midpoint of the gap — measured 197px of
   slack on each side at 1280 wide, 277 at 1440, 517 at 1920 — until it was
   excluded from this rule. Status became a saved-view filter on 08-26 and the
   wrapper (.header-status-filter) retired with it on 08-29, so the exclusion is
   gone; anything that lands in that slot again wants the same one. */
.global-page-header:not(.global-page-header--tabsactions) .global-page-search + * {
    margin-left: auto;
}

/* Below the global-filter-bar breakpoint the controls stack one per row, where a 320px search
   in a full-width row reads as a mistake, and a right-aligned cluster has no slack to
   ride anyway. Release both, matching what .global-search-input-wrapper already does at this
   width (filter-bar.css). */
@media (max-width: 600px) {
    .global-page-header .global-page-search {
        flex: 1 1 auto;
        max-width: none;
    }

    .global-page-header:not(.global-page-header--tabsactions) .global-page-search + * {
        margin-left: 0;
    }
}

/* Date-nav rides the FAR RIGHT of the opps toolbar: search capped on
   the left, a flexible gap, then Today + arrows + month + year right-aligned.
   `margin-left: auto` eats the slack before the cluster. The calendar is the one
   surface with a date-nav now; its view-specific Events/Tasks lens selectors sit
   before the date-nav in the DOM, so they stay left next to search while only the
   date-nav cluster moves right. */
.global-page-header--tabsactions .global-page-header__toolbar .global-date-nav {
    margin-left: auto;
}

/* The toolbar's own actions cluster trails the date nav, and two auto margins in one
   flex row split the slack between them instead of pinning either to an edge. Dropping
   the cluster's lets it sit right behind the date nav, so the two read as one
   right-hand group. */
.global-toolbar__row:has(.global-date-nav) .global-toolbar__actions {
    margin-left: 0;
}

/* Saved-view tab strip row: sits between the title bar and the toolbar. The strip
   inside (`.global-tabs`) carries its own bottom-border seam, so when the strip is
   present the toolbar drops its own top rule to avoid a doubled hairline. */
.global-page-header__tabs {
    padding: var(--spacing-xs) var(--spacing-sm) 0;
}

.global-page-header__tabs + .global-page-header__toolbar {
    border-top: 0;
}

/* Overflow guard (scoped — never mutate the shared `.global-tabs` singleton, which
   flex-wraps by design for card-shape strips). The unified strip aggregates every
   saved view of all four kinds and lives in a height-pinned header, so a wrapped
   multi-row strip would permanently steal body height on the viewport-fit table
   view. Keep it a single row that scrolls sideways instead, and cap + ellipsize a
   long saved-view name so one entry can't blow out the row. */
.global-page-header__tabs .global-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    /* Keep the sideways scroll but hide its bar (HubSpot-style) — the strip is a
       thin control row, so a visible scrollbar under it reads as clutter. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.global-page-header__tabs .global-tabs::-webkit-scrollbar {
    display: none;
}

.global-page-header__tabs .global-tab {
    flex: 0 0 auto;
    max-width: 12.5rem;
}

.global-page-header__tabs .global-tab__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* -- Card shape, arrangement C (opportunities dashboard "controls join the tabs
   line", sb-header-layout-v1) -------------------------------------------------
   The card drops its title bar; row 1 packs the saved-view tab strip on the left
   and the action cluster (Add + Filter + view-kind + gear) on the right, and row 2
   (the toolbar) carries search + the view's own controls. Scoped to the
   --tabsactions variant, so every other card consumer keeps its title bar. */
.global-page-header--tabsactions .global-page-header__tabsrow {
    display: flex;
    align-items: center;
    gap: var(--spacing-smd);
    padding: var(--spacing-xsm) var(--spacing-md) 0;
    border-bottom: 1px solid var(--color-grey-100);
}

/* Tabs take the row's slack and scroll sideways when they overrun (the nowrap +
   overflow-x guard on the inner strip already drives the scroll); the padding
   reset drops the strip's own inset so the first tab lines up with the 1rem card
   edge the bar and toolbar use. Actions hold their intrinsic width on the right. */
.global-page-header--tabsactions .global-page-header__tabs {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
}

.global-page-header--tabsactions .global-page-header__actions {
    flex: 0 0 auto;
}

/* HubSpot's small button: 32px tall with an 8px gap to its icon. Scoped to this
   cluster, so .btn-primary keeps its default height everywhere else. */
.global-page-header--tabsactions .global-page-header__actions .btn {
    height: 32px;
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding-top: 0;
    padding-bottom: 0;
}

/* The same spec draws the icon a step above the label. */
.global-page-header--tabsactions .global-page-header__actions .btn i {
    font-size: var(--font-size-base);
}

/* The row owns the full-width seam under both tabs and actions, so the inner strip
   drops its own bottom rule (it would run under the tabs only) and the toolbar
   below drops its top rule, leaving one hairline between the two rows. */
.global-page-header--tabsactions .global-page-header__tabs .global-tabs {
    border-bottom: 0;
}

.global-page-header--tabsactions .global-page-header__toolbar {
    border-top: 0;
}

/* On a phone the tabs get the whole row, so the strip scrolls sideways with each
   tab's who-sees-it icon in view, and the actions move up to their own line above
   it, on the right, where the phone mockup puts Add. */
@media (max-width: 767.98px) {
    .global-page-header--tabsactions .global-page-header__tabsrow {
        flex-wrap: wrap;
        row-gap: var(--spacing-xsm);
    }

    .global-page-header--tabsactions .global-page-header__actions {
        order: -1;
        margin-left: auto;
    }

    .global-page-header--tabsactions .global-page-header__tabs {
        flex-basis: 100%;
    }
}

/* -- Toolbar shape (list pages whose body is already cards) ------------------
   The design pick for the signup dashboard: the page has no in-page title,
   the search box and filters get their own small card, and the body renders
   after it as a page-level sibling so its own cards sit on the grey page
   background instead of being nested inside a second card. Same card chrome
   and same control row as the card shape's toolbar, minus the hairline (there
   is no title bar above to separate from) and minus overflow:hidden (this card
   holds one row, so it has nothing to clip, and a filter dropdown can open
   past its bottom edge). */
.global-page-header--toolbar {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    flex-wrap: wrap;
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--color-card-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--spacing-mdlg);
}

/* Back arrow — light-surface twin of the on-dark header back button it replaced. */
.global-record-header__back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-border);
    background: var(--color-card-bg);
    color: var(--color-text-muted);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.global-record-header__back:hover,
.global-record-header__back:focus-visible {
    background: var(--color-action-tint-pressed);
    border-color: var(--color-action);
    color: var(--color-action);
}

.global-record-header__back i {
    display: flex;
    font-size: var(--font-size-md);
    line-height: 1;
}

.global-record-header__heading {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    flex: 1 1 auto;
    min-width: 0;
}

/* Record title truncates — the heading also carries the status chip, so long
   record names can't push it off the card. */
.global-record-header__title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* A title that opens its parent record reads as the title until pointed at. */
.global-record-header__title-link {
    color: inherit;
    text-decoration: none;
}
.global-record-header__title-link:hover,
.global-record-header__title-link:focus-visible {
    text-decoration: underline;
}

/* Record actions wrap — a record's cluster can hold more controls than a
   list row's single "+ New". */
.global-record-header__actions {
    flex-wrap: wrap;
}

/* Optional tab strip is the divider row inside the one card: a light hairline
   above splits it from the bar, and its bottom seam is toned to the card's own
   border so the whole card reads as one surface. */
.global-record-header .global-tabs {
    padding: 0 var(--spacing-smd);
    border-top: 1px solid var(--color-grey-100);
    border-bottom-color: var(--color-border);
}

/* Optional summary row under the bar (a record's facts strip on the left, its
   stage track on the right). Hairline above, like the tab strip below it. A
   summary with neither a fact nor a track draws nothing. */
.global-record-header__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spacing-lg);
    padding: var(--spacing-smd) var(--spacing-md);
    border-top: 1px solid var(--color-grey-100);
}

.global-record-header__summary:not(:has(.global-facts__item, .global-stage-track)) {
    display: none;
}

.global-record-header__summary > .global-facts {
    flex: 1 1 auto;
}

.global-record-header__summary > .global-stage-track {
    flex: 0 1 auto;
}

/* ── Phone (<768px) ─────────────────────────────────────────────────────────
   The record header stacks: back / heading / stage on the first row, actions
   on a full-width second row, title clamped to two lines, tabs in a
   horizontal scroller with a fade. Stays LAST in the file — a base rule
   written after this block wins the cascade back. */
/* A partial whose every control is gated off leaves the wrapper empty
   (record_header.html renders it spaceless for exactly this). */
.global-record-header__actions:empty {
    display: none;
}

@media (max-width: 767.98px) {
    .global-record-header__bar {
        display: grid;
        grid-template-areas:
            "back heading stage"
            "actions actions actions";
        grid-template-columns: auto minmax(0, 1fr) auto;
        align-items: center;
        row-gap: var(--spacing-sm);
        padding: var(--spacing-sm) var(--page-gutter);
    }

    .global-record-header__back {
        grid-area: back;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: var(--control-h-touch);
        height: var(--control-h-touch);
    }

    .global-record-header__heading {
        grid-area: heading;
        min-width: 0;
    }

    /* Primary action takes the row; the ⋯ button keeps a 44px square. */
    .global-record-header__actions {
        grid-area: actions;
        display: grid;
        grid-template-columns: minmax(0, 1fr) var(--control-h-touch);
        gap: var(--spacing-sm);
        /* The bar's margin-left: auto pushes the cluster right on a desktop;
           on a grid item it shrinks the row to its content instead. */
        margin-left: 0;
    }

    .global-record-header__actions .btn {
        min-height: var(--control-h-touch);
    }

    /* Two lines, then an ellipsis. Case as typed — caps eat width. */
    .global-record-header__title {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
        white-space: normal;
        text-transform: none;
        font-size: var(--font-size-h4);
        line-height: 1.25;
    }

    /* The summary stacks: facts first, the stage track full width under them. */
    .global-record-header__summary {
        flex-direction: column;
        align-items: stretch;
        gap: var(--spacing-smd);
        padding: var(--spacing-smd) var(--page-gutter);
    }

    .global-record-header__summary > .global-stage-track {
        flex: 0 0 auto;
    }

    /* Tabs scroll sideways. overscroll-behavior-x sits in the same rule as
       overflow-x (test_overscroll_containment keys on file + selector). */
    .global-record-header .global-tabs {
        display: flex;
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x proximity;
        scrollbar-width: none;
        padding: 0 var(--page-gutter);
    }

    .global-record-header .global-tabs::-webkit-scrollbar {
        display: none;
    }

    /* A sticky trailing item fades the right edge so a cut-off tab reads as
       "more to the right". A gradient, never mask-image: a mask on the
       scroller would clip a fixed popover opened from inside it. */
    .global-record-header .global-tabs::after {
        content: "";
        position: sticky;
        right: 0;
        flex: 0 0 var(--spacing-lg);
        align-self: stretch;
        background: linear-gradient(to right, transparent, var(--color-card-bg));
        pointer-events: none;
    }
}
