@charset "UTF-8";
/* Must stay the FIRST bytes of this file - a @charset rule is only honoured at
   byte 0, and without it a browser falls back to a locale default and renders the
   separators below as mojibake ("Working on it Â· 6s"). Static CSS goes out as
   text/css with no charset parameter, so the stylesheet has to declare its own. */

/* LipiCore Insights - modern dashboard styles. Theme values overridable via config/themes. */

:root {
    /* The brand triad. primary carries actions, links and active states;
       secondary is its companion, used for the product name in the brand
       lockup, the assistant entry and the Overview's section headings; hint
       marks guidance - the sign-in note, empty states and the tips section.
       secondary and hint are BRAND colors, often saturated (a white-label may
       set anything), so they are only ever used on icons, borders and tints -
       never as body text, where a vivid brand hue would fail contrast. */
    --ins-primary: #5E8FD3;
    --ins-secondary: #5FB89B;
    --ins-hint: #E39A4E;

    --ins-danger: #d64550;
    --ins-warning: #f59e0b;
    --ins-success: #2f9e5f;

    --ins-background: #f4f5fb;
    --ins-surface: #ffffff;
    --ins-header-bg: #ffffff;
    --ins-on-color: #ffffff;

    --ins-text: #1f2333;
    --ins-muted: #6b7c92;

    --ins-border: #e7eaf3;
    --ins-shadow: #0f172a;

    /* Accent colors (KPI tints, highlights) - overridable via the theme's "accents" list */
    --ins-accent-1: #5E8FD3;
    --ins-accent-2: #5FB89B;
    --ins-accent-3: #D9AE45;
    --ins-accent-4: #D9755E;
    --ins-accent-5: #866CCB;
    --ins-accent-6: #63B9E5;
    --ins-accent-7: #E287B5;
    --ins-accent-8: #8FAA4D;
    --ins-accent-9: #8E8E8E;
    --ins-accent-10: #E39A4E;

    /* Two shadow depths from --ins-shadow: the resting shadow cards use, and
       the deeper one elevated surfaces (dialogs, dropdowns, hover) add on top. */
    --ins-shadow-light: 0 1px 3px color-mix(in srgb, var(--ins-shadow) 6%, transparent);
    --ins-shadow-strong: color-mix(in srgb, var(--ins-shadow) 25%, transparent);

    --ins-font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;

    --ins-control-height: 40px;
    --ins-radius-control: 10px;
    --ins-border-radius: 0.85rem;
    /* corner radius of the AI chat / pin board surfaces; every use had been
       falling back to its 12px default because this was never defined */
    --ins-radius: 12px;

    /* Not a theme knob: the header's floor height, shared with everything that
       sticks underneath it (the home page's narrow-screen section rail). Must
       clear the tallest content variant - 36px controls + the bar's padding and
       rule - or the header grows past it and the sticky rail sits in the gap. */
    --ins-header-min-height: 55px;

    /* Not a theme knob: the lane every internal scroll region reserves for its
       scrollbar, and the gap between that lane and the content beside it. See
       the "Scroll regions" section for why the lane has to be a fixed number
       we choose rather than whatever the OS hands us. Anything that must line
       up with a scroll region's content (a panel head above it) insets itself
       by lane + gap, so the two can never drift apart. */
    --ins-scrollbar-lane: 10px;
    --ins-scrollbar-gap: 0.3rem;
    --ins-scrollbar-inset: calc(var(--ins-scrollbar-lane) + var(--ins-scrollbar-gap));

}

/* Touch screens keep no grabbable thumb, so on a phone the reserved lane is
   dead space - and every inset derived from it (the AI composer's right
   margin, the context row's padding, the home panel-head inset) pushed
   content visibly off-centre at 375px. Zeroing the variables collapses the
   lane and all of its dependents together; scrolling itself never needed the
   bar on touch. hover:none keeps this to touch-first devices - a desktop
   window narrowed to phone width still has a pointer, so it keeps its lane. */
@media (max-width: 900px) and (hover: none) {
    :root {
        --ins-scrollbar-lane: 0px;
        --ins-scrollbar-gap: 0rem;
    }
}

body.ins-body {
    background: var(--ins-background);
    color: var(--ins-text);
    font-family: var(--ins-font-family);
    /* header / main / footer as a column, so the footer never floats mid-screen */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* How opaque a scroll region's thumb currently is. Registered rather than a
   plain custom property for one reason: only a registered property has a type,
   and only a typed property can be INTERPOLATED - which is what lets the bar
   fade instead of blink. See the "Scroll regions" section for why the fade has
   to be routed through a variable at all. */
@property --ins-scrollbar-alpha {
    syntax: '<number>';
    inherits: true;
    initial-value: 0;
}

/* ---------- Scroll regions: one scrollbar that behaves the same everywhere ----
   Every region in the app that scrolls inside the page opts in here: the home
   panels and the Overview, the sidebar rail, the AI transcript, the filter
   facet lists, the shared dropdown menu, the admin grant list and the pin spec
   sheet. One bar, one width, one behaviour, on every page.

   Why this exists. macOS renders scrollbars two different ways and switches
   between them behind the app's back: OVERLAY (a thumb floating on top of the
   content, taking zero layout space) on a trackpad, and CLASSIC (a bar that
   takes its own width out of the layout) the moment a mouse is attached -
   System Settings > Appearance > "Show scroll bars". The standard
   scrollbar-width / scrollbar-color properties recolor and thin the bar but do
   NOT take it out of overlay mode, and scrollbar-gutter: stable reserves
   nothing under an overlay bar, because a gutter can only hold space open for
   a bar that takes space to begin with. So under those properties alone the
   only thing keeping the thumb off the content is the region's right padding -
   which has to be a different number in each of the two modes, can be changed
   by plugging in a mouse, and is therefore impossible to get right. That is
   what made the scrollbar drift over the cards and re-render differently later
   in the same session, and why .ins-home-scroll's right padding was re-tuned
   four times (0.65 -> 0.2 -> 0.3 -> 0.2) without ever settling.

   ::-webkit-scrollbar is the one lever that does settle it: a custom scrollbar
   is never an overlay scrollbar, in either OS mode. The bar always takes the
   lane below, scrollbar-gutter: stable holds that lane open whether the region
   currently overflows or not, and layout stops depending on both the OS
   setting and the overflow state. Measured on Chrome 148 / macOS, 2026-07-30.

   The catch, also measured: Chrome ignores every ::-webkit-scrollbar rule on an
   element that ALSO sets scrollbar-width or scrollbar-color - the standard
   property wins and hands the element straight back to the overlay bar. The two
   cannot be mixed on one element. Firefox supports only the standard pair and
   none of the pseudo-elements, so it gets them behind @supports not
   selector(::-webkit-scrollbar), a query no WebKit/Blink browser enters.

   Anything laid out beside a scroll region (the panel head above a listing)
   must inset itself by --ins-scrollbar-inset to stay aligned with the content
   inside it - the lane is real width, and only the scroll region loses it.

   THE REGION LIST BELOW IS THE ONE PLACE IT IS WRITTEN. It is repeated verbatim
   in insights.js (initScrollbars' SCROLL_REGIONS) - a region added here without
   being added there gets a lane but never a visible thumb. The :is() wrapper
   keeps it to one list per rule instead of one selector per region per rule;
   note it takes the specificity of its heaviest arm (.dropdown-menu.ins-menu,
   so 0-2-0), which any later override has to clear. */
:is(.ins-filter-options,
    .ins-home-scroll,
    .ins-ai-transcript,
    .ins-home-nav,
    .dropdown-menu.ins-menu,
    .ins-admin-grant-list,
    .ins-pin-spec-body,
    .ins-widget-table) {
    scrollbar-gutter: stable;
    /* Resting state, and the slow half of the fade: the bar dissolves once the
       region goes quiet. Transitions do not run on ::-webkit-scrollbar-thumb at
       all (measured - no CSSTransition is even created for it), so the fade is
       driven HERE, on the element, where transitions do work; the thumb reads
       this variable and repaints as it interpolates. */
    --ins-scrollbar-alpha: 0;
    transition: --ins-scrollbar-alpha 0.5s ease;
}

/* Arriving is quicker than leaving - the bar should already be there by the
   time you look down at it, then take its time going away. */
:is(.ins-filter-options,
    .ins-home-scroll,
    .ins-ai-transcript,
    .ins-home-nav,
    .dropdown-menu.ins-menu,
    .ins-admin-grant-list,
    .ins-pin-spec-body,
    .ins-widget-table).ins-scroll-show {
    --ins-scrollbar-alpha: 0.26;
    transition-duration: 0.12s;
}

:is(.ins-filter-options,
    .ins-home-scroll,
    .ins-ai-transcript,
    .ins-home-nav,
    .dropdown-menu.ins-menu,
    .ins-admin-grant-list,
    .ins-pin-spec-body,
    .ins-widget-table)::-webkit-scrollbar {
    width: var(--ins-scrollbar-lane);
    height: var(--ins-scrollbar-lane);
}

:is(.ins-filter-options,
    .ins-home-scroll,
    .ins-ai-transcript,
    .ins-home-nav,
    .dropdown-menu.ins-menu,
    .ins-admin-grant-list,
    .ins-pin-spec-body,
    .ins-widget-table)::-webkit-scrollbar-track {
    background: transparent;
}

/* Invisible at rest, and it takes its opacity from the variable above rather
   than from a colour of its own - that indirection IS the fade. The thumb is
   inset from its lane by a transparent border rather than being made narrower:
   background-clip keeps the paint off that border, so the pill floats centred
   in the lane with clear space on both sides instead of butting against the
   content.

   What raises the variable is a class, never :hover. A custom scrollbar does
   not repaint when the SCROLLER's own state changes: .region:hover::-webkit-
   scrollbar-thumb never paints, and neither does :focus-within (measured on
   Chrome 148). A class toggled from script DOES repaint it, so insights.js
   drives .ins-scroll-show while the region is being scrolled and while it holds
   keyboard focus. Only the paint is deferred - the lane itself is always held,
   so a reveal never moves any content. */
:is(.ins-filter-options,
    .ins-home-scroll,
    .ins-ai-transcript,
    .ins-home-nav,
    .dropdown-menu.ins-menu,
    .ins-admin-grant-list,
    .ins-pin-spec-body,
    .ins-widget-table)::-webkit-scrollbar-thumb {
    background: color-mix(in srgb,
        var(--ins-text) calc(var(--ins-scrollbar-alpha) * 100%), transparent);
    background-clip: padding-box;
    border: 2px solid transparent;
    border-radius: 999px;
}

/* Reaching for the bar is the other way to summon it, and the only one left
   that is pure CSS: the thumb's OWN :hover is honoured (unlike the scroller's)
   and it hit-tests across the whole lane, not just the painted pill - so
   pointing at the gutter brings up a solid, grabbable thumb even when the
   region has been quiet long enough to have faded out completely. No transition
   is possible on this (see above), but a state you reached for deliberately is
   the one place an instant response is right. */
:is(.ins-filter-options,
    .ins-home-scroll,
    .ins-ai-transcript,
    .ins-home-nav,
    .dropdown-menu.ins-menu,
    .ins-admin-grant-list,
    .ins-pin-spec-body,
    .ins-widget-table)::-webkit-scrollbar-thumb:hover {
    background: color-mix(in srgb, var(--ins-text) 42%, transparent);
    background-clip: padding-box;
}

/* Firefox: the same behaviour through the only properties it has. It applies
   them to the scroller itself, so the bar is genuinely thin, genuinely reserved
   (scrollbar-gutter: stable does hold a lane open there) and genuinely
   transitionable - the fade needs no variable indirection here, because
   scrollbar-color animates directly. Script drives the same .ins-scroll-show
   class, so the scroll-driven reveal matches; :hover rides along as well, since
   Firefox has no way to style the thumb's own hover and this is its only
   reach-for-it affordance. */
@supports not selector(::-webkit-scrollbar) {
    :is(.ins-filter-options,
        .ins-home-scroll,
        .ins-ai-transcript,
        .ins-home-nav,
        .dropdown-menu.ins-menu,
        .ins-admin-grant-list,
        .ins-pin-spec-body,
        .ins-widget-table) {
        scrollbar-width: thin;
        scrollbar-color: transparent transparent;
        transition: scrollbar-color 0.5s ease;
    }

    :is(.ins-filter-options,
        .ins-home-scroll,
        .ins-ai-transcript,
        .ins-home-nav,
        .dropdown-menu.ins-menu,
        .ins-admin-grant-list,
        .ins-pin-spec-body,
        .ins-widget-table):is(:hover, :focus-within, .ins-scroll-show) {
        scrollbar-color: color-mix(in srgb, var(--ins-text) 26%, transparent) transparent;
        transition-duration: 0.12s;
    }
}

/* ---------- Header ---------- */
.ins-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: var(--ins-header-bg);
    border-bottom: 1px solid var(--ins-border);
    padding: 0.55rem 1.25rem;
    /* tallest content variant (36px controls + padding + border), so the height
       never jumps between pages with and without the picker */
    min-height: var(--ins-header-min-height);
    position: sticky;
    top: 0;
    z-index: 100;
}

/* The brand is the header's elastic element: a white-label client can put any logo
   and any two names in it, so it must yield rather than push the controls off the
   edge - unchecked, that is what shoved Sign Out off screen on a phone. The right
   group refuses to shrink (below), so the brand truncates first. */
.ins-brand {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
    text-decoration: none;
    color: var(--ins-text);
}

.ins-brand-company,
.ins-brand-product {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ins-header-logo {
    height: 28px;
    width: auto;
}

.ins-separator-left {
    margin-left: 0.5rem;
    padding-left: 0.75rem;
    border-left: 1px solid var(--ins-border);
}

.ins-brand-company {
    font-weight: 700;
    font-size: 1rem;
}

/* The product half of the brand lockup wears the theme's SECONDARY color, so the
   two halves ("GotoLiquorStore Insights") read as the brand's own pairing. */
.ins-brand-product {
    color: var(--ins-secondary);
    font-weight: 700;
    font-size: 1rem;
}

.ins-header-right {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    /* shrinkable only so the dashboard picker inside it can give way; the icon
       controls and the user chip below opt out, so they are never the ones squeezed */
    min-width: 0;
}

.ins-header-right > .ins-admin-link,
.ins-header-right > .ins-user {
    flex-shrink: 0;
}

.ins-dash-picker {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.ins-dash-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ins-secondary);
    margin: 0;
    white-space: nowrap;
}

/* The header's page picker. 36px like the utilities beside it - a 34px control in
   the same row reads as a misalignment, not a size choice. (Other pages reuse this
   class inside their own fields, which set their own height.) */
.ins-dash-select {
    height: 36px;
    min-width: 180px;
    border-radius: 9px;
    border: 1px solid var(--ins-border);
    background: var(--ins-surface);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--ins-text);
    padding: 0 0.6rem;
    cursor: pointer;
}

/* ---------- Page ---------- */
.ins-main {
    padding: 0.5rem 1rem 0.5rem 1rem;
    margin: 0 auto;
    /* fill the column so the footer sits at the viewport bottom on short pages;
       width keeps auto margins from shrink-wrapping the flex item */
    width: 100%;
    flex: 1;
}

/* The home catalog caps its width so cards fill instead of spreading across
   ultra-wide screens; data pages (grid/dashboard) stay full-width for tables. */
.ins-main--home {
    max-width: 1500px;
}

/* Phones: the page gutter narrows to the header's own 0.75rem - at 375px the
   ~8px a side visibly widens every card, table and chart. */
@media (max-width: 640px) {
    .ins-main {
        padding-left: 0.75rem;
        padding-right: 0.75rem;
    }
}

.ins-page-head {
    margin-bottom: 0.8rem;
}

.ins-page-title {
    font-size: 1.3rem;
    font-weight: 700;
    margin-bottom: 0.1rem;
}

/* The gap belongs between the title and a description; with no description below
   it would just push the title off centre */
.ins-page-title:last-child {
    margin-bottom: 0;
}

/* A dashboard/grid page head: the title and its favorite star on one line, so the
   star reads as belonging to the name - the same relationship it has on the home
   cards. It IS the home cards' control, so it needs their accent variable, and the
   absolute positioning that pins it to a card corner has to go. */
.ins-page-title-row {
    --card-accent: var(--ins-primary);
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin-bottom: 0.1rem;
}

.ins-page-title-row:last-child {
    margin-bottom: 0;
}

.ins-page-title-row .ins-page-title {
    margin-bottom: 0;
}

.ins-page-title-row .ins-fav-form {
    position: static;
}

/* Sized against the 1.3rem title rather than the card's body text */
.ins-page-title-row .ins-fav-btn {
    font-size: 1rem;
}

/* Divider between a page's name and the saved view applied to it - lighter than
   both names so the eye reads two parts rather than one run-on title */
.ins-page-title-sep {
    color: var(--ins-muted);
    font-weight: 400;
    margin: 0 0.1rem;
}

.ins-page-desc {
    color: var(--ins-muted);
    font-size: 0.86rem;
    margin-bottom: 0;
}

.ins-centered {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 4rem;
    min-height: 60vh;
}

/* Empty state sitting inside a home tab panel, not the whole page - sit higher and
   don't reserve most of the viewport */
.ins-centered--panel {
    min-height: auto;
    padding-top: 1.5rem;
}

/* ---------- Content: filters + grid ---------- */
.ins-content {
    display: flex;
    gap: 0.9rem;
    align-items: flex-start;
}

.ins-filters {
    flex: 0 0 230px;
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    padding: 0.85rem 0.95rem;
    align-self: flex-start;
}

.ins-filters-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.4rem;
}

/* The folded sidebar's head affordances (initFiltersCollapse): a chevron at
   the row's end and, when facet values are applied, their total as a filled
   pill - so the collapsed panel still says "3 values are narrowing this".
   Both exist only while the panel is collapsible; desktop never shows them. */
.ins-filters-caret,
.ins-filters-count {
    display: none;
}

.ins-filters--collapsible .ins-filters-caret {
    display: inline-block;
    flex-shrink: 0;
    margin-left: 0.5rem;
    font-size: 0.8rem;
    color: var(--ins-muted);
    transition: transform 0.15s ease;
}

.ins-filters--collapsible.ins-filters--open .ins-filters-caret {
    transform: rotate(180deg);
}

.ins-filters--collapsible .ins-filters-count {
    display: inline-block;
    flex-shrink: 0;
    margin-left: 0.45rem;
    padding: 0.14rem 0.42rem;
    border-radius: 999px;
    font-size: 0.66rem;
    font-weight: 700;
    line-height: 1;
    color: var(--ins-on-color);
    background: var(--ins-primary);
}

/* Compare-with-previous-period switch under the filter heading */
.ins-compare-toggle {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0.15rem 0 0.6rem;
    font-size: 0.65rem;
    font-weight: 500;
    color: var(--ins-muted);
    cursor: pointer;
}

.ins-compare-toggle:has(input:checked) {
    color: var(--ins-primary);
}

.ins-filters-title {
    font-weight: 600;
    font-size: 0.95rem;
}

.ins-filters-title .bi {
    color: var(--ins-hint);
    margin-right: 0.2rem;
}

.ins-filters-reset {
    font-size: 0.76rem;
    font-weight: 600;
    color: var(--ins-hint);
    text-decoration: none;
}

.ins-filters-reset:hover {
    text-decoration: none;
}

.ins-filter {
    border: 0;
    padding: 0;
    margin: 0.5rem 0 0;
    border-top: 1px solid var(--ins-border);
}

.ins-filter-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.7rem 0 0.2rem;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--ins-secondary);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    user-select: none;
    list-style: none;               /* hide the default disclosure marker (Firefox) */
}

.ins-filter-label::-webkit-details-marker {
    display: none;                  /* hide the default disclosure marker (WebKit) */
}

.ins-filter-label:hover {
    color: var(--ins-text);
}

.ins-filter-heading {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    min-width: 0;
}

/* Count of selected values, shown in the section header (e.g. ACTION (3)) so an
   active filter is visible even when the section is collapsed or scrolled past.
   Primary-tinted pill to set it apart from the muted per-value record counts. */
.ins-filter-selected {
    flex-shrink: 0;
    font-size: 0.66rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0;
    padding: 0.12rem 0.36rem;
    border-radius: 999px;
    color: var(--ins-muted);
    background: color-mix(in srgb, var(--ins-muted) 14%, transparent);
}

.ins-filter-caret {
    font-size: 0.7rem;
    color: var(--ins-muted);
    transition: transform 0.15s ease;
}

/* Chevron points down while open, rotates to point right when collapsed. */
.ins-filter:not([open]) .ins-filter-caret {
    transform: rotate(-90deg);
}

/* Trim the top gap on the first section so it sits flush under the filters head. */
.ins-filters .ins-filter:first-of-type {
    margin-top: 0.5rem;
}

/* Value lists show at most ten rows; longer facets scroll inside their section
   instead of stretching the sidebar. A row is 1.575rem (measured): the 16px
   checkbox sits on the text baseline, so checkbox + text descender + 2 ×
   0.14rem padding set the height, not the 0.8rem type alone. The cap only
   bites past ten options - short lists render exactly as before.
   The 0.25rem slack absorbs WebKit's baseline rounding: Safari lays those ten
   rows out a hair taller than Chrome, so an exactly-ten list overflowed the bare
   cap and micro-scrolled there (a scrollbar for two stray pixels). The slack is
   an order of magnitude under one row (1.575rem), so an eleventh option still
   scrolls - as does a ten-option list whose values wrap to two lines.
   The bar comes from the shared "Scroll regions" section: a fixed lane reserved
   on EVERY list, scrolling or not, so a facet that overflows ten rows cannot
   shift its right-aligned counts left of the short lists above it - every
   group's counts line up. Transparent at rest, painting only while the pointer
   or keyboard focus is inside the list. */
.ins-filter-options {
    max-height: calc(10 * 1.575rem + 0.25rem);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.ins-filter-option {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    padding: 0.15rem 0 0 0;
    font-size: 0.8rem;
    line-height: 1.5;               /* pinned: the ten-row cap above counts on it */
    cursor: pointer;
}

.ins-filter-value {
    overflow-wrap: anywhere;
}

.ins-filter-count {
    color: var(--ins-muted);
    font-size: 0.7rem;
    margin-left: auto;
    white-space: nowrap;
}

.ins-filter-dates {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.ins-date-field {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--ins-muted);
    position: relative;             /* anchors the enhanced field's calendar glyph */
}

.ins-date-field input[type="date"],
.ins-date-field input[type="number"],
.ins-date-field input.ins-date-input,
.ins-date-field select {
    height: 32px;
    border: 1px solid var(--ins-border);
    border-radius: 8px;
    padding: 0 0.5rem;
    font-size: 0.8rem;
    color: var(--ins-text);
    background: var(--ins-surface);
}

/* The visible field of an enhanced date input clears its calendar glyph. */
.ins-date-field input.ins-date-input {
    padding-right: 1.7rem;
}

.ins-date-field input[type="date"]:disabled,
.ins-date-field input.ins-date-input:disabled {
    color: var(--ins-muted);
    opacity: 0.7;
}

/* Calendar glyph on enhanced date fields (the native input drew its own). */
.ins-date-field:has(input.ins-date-input)::after {
    content: "\f1f6";                /* bootstrap-icons: bi-calendar */
    font-family: bootstrap-icons;
    position: absolute;
    right: 0.55rem;
    bottom: 7px;
    font-size: 0.85rem;
    color: var(--ins-muted);
    pointer-events: none;
}

.ins-date-field:has(input.ins-date-input:disabled)::after {
    opacity: 0.5;
}

/* ---------- Themed date picker (flatpickr) ----------
   Recolors the vendored flatpickr calendar with the theme variables (the
   native browser popup cannot be themed at all). Selectors mirror the vendor
   sheet's specificity; this sheet loads after flatpickr.min.css, so source
   order decides. Range and week-number rules are skipped - the app only uses
   single-date pickers. */
.flatpickr-calendar {
    font-family: var(--ins-font-family);
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    box-shadow: 0 6px 24px -6px var(--ins-shadow-strong);
}

.flatpickr-calendar.arrowTop::before { border-bottom-color: var(--ins-border); }
.flatpickr-calendar.arrowTop::after { border-bottom-color: var(--ins-surface); }
.flatpickr-calendar.arrowBottom::before { border-top-color: var(--ins-border); }
.flatpickr-calendar.arrowBottom::after { border-top-color: var(--ins-surface); }

.flatpickr-months .flatpickr-month {
    color: var(--ins-text);
    fill: var(--ins-text);
}

.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
    color: var(--ins-muted);
    fill: var(--ins-muted);
}

/* The vendor hover is an off-brand red. */
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
    fill: var(--ins-primary);
}

.numInputWrapper:hover {
    background: var(--ins-background);
}

.numInputWrapper span.arrowUp::after {
    border-bottom-color: var(--ins-muted);
}

.numInputWrapper span.arrowDown::after {
    border-top-color: var(--ins-muted);
}

span.flatpickr-weekday {
    color: var(--ins-muted);
}

.flatpickr-day {
    color: var(--ins-text);
}

.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
    background: var(--ins-background);
    border-color: var(--ins-background);
}

.flatpickr-day.today {
    border-color: var(--ins-primary);
}

.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
    border-color: var(--ins-primary);
    background: var(--ins-primary);
    color: var(--ins-on-color);
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.selected.nextMonthDay {
    background: var(--ins-primary);
    border-color: var(--ins-primary);
    color: var(--ins-on-color);
}

.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
    color: color-mix(in srgb, var(--ins-muted) 55%, transparent);
}

/* ---------- Widget rows: each config group is one row, shared equally ---------- */
.ins-rows {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

.ins-row {
    display: grid;
    gap: 0.9rem;
    grid-template-columns: 1fr;
    align-items: stretch;
}

.ins-row-2 {
    grid-template-columns: repeat(2, 1fr);
}

.ins-row-3 {
    grid-template-columns: repeat(3, 1fr);
}

.ins-row-4 {
    grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 1199.98px) {
    .ins-row-3,
    .ins-row-4 {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 861.98px) {
    .ins-content {
        flex-direction: column;
    }

    .ins-filters {
        width: 100%;
        flex: initial;
    }

    /* Stacked, the sidebar sits ABOVE the results - a wall of facets the
       reader must scroll past before any data appears. initFiltersCollapse
       folds it to its head row at this width; the head toggles it open. The
       class only exists while this query matches (JS mirrors the breakpoint),
       so desktop keeps its always-open sidebar, and without JS nothing folds. */
    .ins-filters--collapsible:not(.ins-filters--open) > :not(.ins-filters-head) {
        display: none;
    }

    .ins-filters--collapsible .ins-filters-head {
        margin-bottom: 0;
        cursor: pointer;
        user-select: none;
        -webkit-user-select: none;
    }

    .ins-filters--collapsible.ins-filters--open .ins-filters-head {
        margin-bottom: 0.4rem;
    }

    /* Stacked, .ins-content is a column, and a column flex item is sized by
       align-items (flex-start here) - so min-width:0 no longer restrains it and the
       results pane shrink-wraps to the grid table's min-content width. That takes
       the whole page sideways with it (tip, filters, search, pager, footer all
       drift) AND starves .table-responsive, which never scrolls because its parent
       already grew to fit. Pinning the width puts the scrolling back inside the card. */
    .ins-results {
        width: 100%;
    }

    /* The dashboard column has the same disease from the same flex geometry: at
       tablet widths .ins-rows shrink-wrapped to its widest natural child (the
       KPI strip) and the widgets stopped ~180px short of the right edge. */
    .ins-rows {
        width: 100%;
    }

    .ins-row-2,
    .ins-row-3,
    .ins-row-4 {
        grid-template-columns: 1fr;
    }
}

/* ---------- Widget cards ---------- */
.ins-widget {
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ins-widget-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.95rem;
    font-weight: 600;
    font-size: 0.85rem;
    border-bottom: 1px solid var(--ins-border);
}

.ins-widget-icon {
    display: inline-flex;
    align-items: center;
    color: var(--ins-muted);
    font-size: 0.9rem;
}

.ins-widget-body {
    padding: 0.75rem 0.95rem;
    flex: 1;
    min-width: 0;
}

.ins-widget-error {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    background: color-mix(in srgb, var(--ins-warning) 9%, var(--ins-surface));
    border: 1px solid color-mix(in srgb, var(--ins-warning) 30%, var(--ins-surface));
    color: color-mix(in srgb, var(--ins-warning) 60%, black);
    border-radius: calc(var(--ins-border-radius) / 1.6);
    padding: 0.6rem 0.8rem;
    font-size: 0.85rem;
}

/* ---------- Charts ---------- */
.ins-chart,
.ins-breakdown-chart {
    width: 100%;
    height: 290px;
}

.ins-chart--tall {
    height: 400px;
}

/* Drillable charts: segments apply their value as a dashboard filter on click */
.ins-chart--drill {
    cursor: pointer;
}

/* ---------- Explorer widgets (breakdown, flow) ---------- */
.ins-explore-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.45rem;
    margin-bottom: 0.6rem;
}

.ins-explore-controls .ins-select-toggle {
    height: 30px;
    padding: 0 1.9rem 0 0.65rem;
    font-size: 0.8rem;
}

.ins-explore-arrow {
    color: var(--ins-muted);
    font-size: 0.78rem;
}

/* ---------- KPI tiles ---------- */
.ins-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.9rem;
}

.ins-kpi {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    padding: 0.85rem 0.95rem;
    min-width: 0;
}

.ins-kpi-icon {
    width: 42px;
    height: 42px;
    flex: 0 0 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 12px;
    font-size: 1.15rem;
}

.ins-kpi-text {
    min-width: 0;
}

.ins-kpi-label {
    color: var(--ins-muted);
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ins-kpi-value {
    font-size: 1.42rem;
    font-weight: 800;
    line-height: 1.15;
}

.ins-kpi-delta {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: 0.7rem;
    font-weight: 700;
    white-space: nowrap;
}

.ins-kpi-delta--good { color: var(--ins-success); }
.ins-kpi-delta--bad { color: var(--ins-danger); }
.ins-kpi-delta--flat { color: var(--ins-muted); }

/* Baseline note beside the delta, shown only when it is not the default
   preceding period - quiet enough to read as an annotation, not a second figure */
.ins-kpi-basis {
    margin-left: 4px;
    font-size: 0.6rem;
    font-weight: 600;
    color: var(--ins-muted);
    white-space: nowrap;
}

/* Phones: two tiles per row instead of a full-width stack - five KPIs were
   five screens' worth of scrolling before the first chart. The 9.5rem floor
   pairs them on anything from ~360px up and falls back to one column below;
   the tile slims to match (smaller icon chip, tighter padding, value a step
   down). Labels already ellipsize. The pin board's own column-width override
   (.ins-pin-item .ins-kpis) outweighs this and keeps its behaviour. */
@media (max-width: 640px) {
    .ins-kpis {
        grid-template-columns: repeat(auto-fit, minmax(min(9.5rem, 100%), 1fr));
        gap: 0.6rem;
    }

    .ins-kpi {
        gap: 0.6rem;
        padding: 0.7rem 0.75rem;
    }

    .ins-kpi-icon {
        width: 36px;
        height: 36px;
        flex: 0 0 36px;
        font-size: 1rem;
        border-radius: 10px;
    }

    .ins-kpi-value {
        font-size: 1.25rem;
    }
}

/* Icon tints - theme accents with automatically derived soft backgrounds */
/* Accent tint for both the KPI tile icon and the home-card icon (same formula) */
.ins-tint-1 .ins-kpi-icon, .ins-tint-1 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-1) 11%, var(--ins-surface)); color: var(--ins-accent-1); }
.ins-tint-2 .ins-kpi-icon, .ins-tint-2 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-2) 11%, var(--ins-surface)); color: var(--ins-accent-2); }
.ins-tint-3 .ins-kpi-icon, .ins-tint-3 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-3) 11%, var(--ins-surface)); color: var(--ins-accent-3); }
.ins-tint-4 .ins-kpi-icon, .ins-tint-4 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-4) 11%, var(--ins-surface)); color: var(--ins-accent-4); }
.ins-tint-5 .ins-kpi-icon, .ins-tint-5 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-5) 11%, var(--ins-surface)); color: var(--ins-accent-5); }
.ins-tint-6 .ins-kpi-icon, .ins-tint-6 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-6) 11%, var(--ins-surface)); color: var(--ins-accent-6); }
.ins-tint-7 .ins-kpi-icon, .ins-tint-7 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-7) 11%, var(--ins-surface)); color: var(--ins-accent-7); }
.ins-tint-8 .ins-kpi-icon, .ins-tint-8 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-8) 11%, var(--ins-surface)); color: var(--ins-accent-8); }
.ins-tint-9 .ins-kpi-icon, .ins-tint-9 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-9) 11%, var(--ins-surface)); color: var(--ins-accent-9); }
.ins-tint-10 .ins-kpi-icon, .ins-tint-10 .ins-home-card-icon { background: color-mix(in srgb, var(--ins-accent-10) 11%, var(--ins-surface)); color: var(--ins-accent-10); }

.ins-tint-1 .ins-kpi-value { color: var(--ins-accent-1); }
.ins-tint-2 .ins-kpi-value { color: var(--ins-accent-2); }
.ins-tint-3 .ins-kpi-value { color: var(--ins-accent-3); }
.ins-tint-4 .ins-kpi-value { color: var(--ins-accent-4); }
.ins-tint-5 .ins-kpi-value { color: var(--ins-accent-5); }
.ins-tint-6 .ins-kpi-value { color: var(--ins-accent-6); }
.ins-tint-7 .ins-kpi-value { color: var(--ins-accent-7); }
.ins-tint-8 .ins-kpi-value { color: var(--ins-accent-8); }
.ins-tint-9 .ins-kpi-value { color: var(--ins-accent-9); }
.ins-tint-10 .ins-kpi-value { color: var(--ins-accent-10); }

/* ---------- Leaderboard widgets ---------- */
.ins-leaders {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
}

.ins-leader {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    min-width: 0;
}

.ins-leader-rank {
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--ins-muted) 12%, var(--ins-surface));
    color: var(--ins-muted);
    font-size: 0.75rem;
    font-weight: 700;
}

.ins-leader-main {
    flex: 1;
    min-width: 0;
}

.ins-leader-top {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}

.ins-leader-label {
    flex: 1;
    min-width: 0;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ins-leader-secondary {
    color: var(--ins-muted);
    font-size: 0.7rem;
    white-space: nowrap;
}

.ins-leader-value {
    font-size: 0.85rem;
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.ins-leader-bar {
    height: 6px;
    margin-top: 4px;
    border-radius: 3px;
    background: color-mix(in srgb, var(--ins-primary) 10%, var(--ins-surface));
    overflow: hidden;
}

.ins-leader-bar i {
    display: block;
    height: 100%;
    border-radius: 3px;
    background: var(--ins-primary);
}

/* "Nothing came back" inside a widget body: the leaderboard's own note, and the
   one a chart shows in place of empty axes. .ins-chart-empty fills the chart box
   it replaces, so the card keeps the height it reserved rather than collapsing. */
.ins-leaders-empty {
    color: var(--ins-muted);
    font-size: 0.85rem;
    text-align: center;
    padding: 1rem 0;
}

.ins-chart-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    min-height: 120px;
    color: var(--ins-muted);
    font-size: 0.85rem;
}

/* ---------- Tables ---------- */
.ins-table {
    font-size: 0.84rem;
}

.ins-table th {
    color: var(--ins-muted);
    background: var(--ins-background);
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    padding: 0.5rem 0.7rem;
}

.ins-table td {
    padding: 0.5rem 0.75rem;
    vertical-align: middle;
}

/* ---------- Footer ---------- */
.ins-footer {
    padding: 0 0 0.5rem 0;
    color: var(--ins-muted);
    text-align: center;
    font-size: 0.7rem;
}

/* ---------- Header: signed-in user ---------- */
.ins-user {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding-left: 0.6rem;
    border-left: 1px solid var(--ins-border);
}

.ins-user-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
}

.ins-user-chip .bi {
    color: var(--ins-secondary);
    font-size: 1.15rem;
}

.ins-logout {
    display: flex;
    margin: 0;
}

.ins-logout-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* square, and the same 36px as the gear beside it */
    width: 36px;
    height: 36px;
    border: 1px solid var(--ins-border);
    border-radius: 8px;
    background: var(--ins-surface);
    color: var(--ins-muted);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.ins-logout-btn:hover {
    color: var(--ins-danger);
    border-color: color-mix(in srgb, var(--ins-danger) 35%, var(--ins-border));
    background: color-mix(in srgb, var(--ins-danger) 7%, var(--ins-surface));
}

/* ---------- Buttons ---------- */
/* Shared base for every button variant; each rule below adds only its differences.
   .ins-search-btn and .ins-login-submit (defined later) opt in here too. */
.ins-btn-primary,
.ins-btn-ghost,
.ins-btn-danger,
.ins-search-btn,
.ins-login-submit {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    height: var(--ins-control-height);
    padding: 0 1.1rem;
    border: 0;
    border-radius: var(--ins-radius-control);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: filter 0.15s ease, box-shadow 0.15s ease;
}

/* The filled variants (everything but ghost) share the same accent and hover lift */
.ins-btn-primary,
.ins-btn-danger,
.ins-search-btn,
.ins-login-submit {
    background: var(--ins-primary);
    color: var(--ins-on-color);
}

.ins-btn-primary:hover,
.ins-btn-danger:hover,
.ins-search-btn:hover,
.ins-login-submit:hover {
    filter: brightness(1.07);
    color: var(--ins-on-color);
    box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--ins-primary) 65%, transparent);
}

/* Quiet sibling of the primary button - page actions that should not compete */
.ins-btn-ghost {
    border: 1px solid var(--ins-border);
    background: var(--ins-surface);
    color: var(--ins-text);
    transition: border-color 0.15s ease, color 0.15s ease;
}

.ins-btn-ghost:hover {
    border-color: var(--ins-primary);
    color: var(--ins-primary);
}

/* Cap a long saved-view name from stretching the toggle, but leave room for a
   realistic name to show in full before the ellipsis kicks in. */
.ins-saved-label {
    display: inline-block;
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

/* Destructive confirm (e.g. deleting a saved view) */
.ins-btn-danger {
    background: var(--ins-danger);
}

.ins-btn-danger:hover {
    box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--ins-danger) 65%, transparent);
}

.ins-page-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
}

/* The favorite toggle in the page head is a form; strip its default margin so it
   lines up with the other actions */
.ins-page-actions form {
    margin: 0;
}

/* ---------- One themed checkbox, used everywhere ----------
   Custom-drawn (class on the <input>) so the tick is always white, regardless
   of the theme accent's luminance - native accent-color lets the browser pick
   a dark tick on mid tones. Filters, the compare switch and the admin user
   form all share it. */
.ins-checkbox {
    appearance: none;
    -webkit-appearance: none;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    margin: 0;
    border: 1.5px solid var(--ins-border);
    border-radius: 4px;
    background: var(--ins-surface);
    cursor: pointer;
    position: relative;
}

.ins-checkbox:checked {
    background: var(--ins-primary);
    border-color: var(--ins-primary);
}

.ins-checkbox:checked::after {
    content: "";
    position: absolute;
    /* Center the 4x8 tick in the 13x13 content box (16px minus the 1.5px borders):
       (13-4)/2 and (13-8)/2. Rotation keeps the center, so the glyph sits mid-box. */
    left: 4.5px;
    top: 2px;
    width: 4px;
    height: 8px;
    border: solid var(--ins-on-color);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.ins-checkbox:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ins-primary) 25%, transparent);
}

/* Labels that lay out on the text baseline (filters, admin checks) nudge the
   box down to sit on the line; centered rows (compare switch) need no nudge. */
.ins-filter-option .ins-checkbox,
.ins-admin-check .ins-checkbox {
    transform: translateY(2px);
}

/* ---------- One themed dropdown, used everywhere ----------
   The shared menu for every dropdown in the app: the Saved-views pickers and
   the custom selects (header nav picker, filter presets) all render this list
   instead of the browser's native, unstyleable <select> popup. */
.dropdown-menu.ins-menu {
    min-width: 220px;
    max-height: 60vh;
    overflow-y: auto;
    padding: 0.3rem;
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    box-shadow: 0 12px 28px -12px var(--ins-shadow-strong);
}

/* The saved-views picker on grid/dashboard pages lists user-named views, which run
   longer than the app's other menus - give it a wider footprint so names get proper
   room (still wrapping past the cap rather than overflowing). Right-aligned, so the
   extra width grows leftward and never pushes off the viewport edge. */
.ins-menu.ins-saved-menu {
    min-width: 260px;
    max-width: min(360px, 90vw);
}

.ins-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.42rem 0.6rem;
    border-radius: 7px;
    font-size: 0.86rem;
    color: var(--ins-text);
    white-space: normal;
    cursor: pointer;
}

/* A thin gap between rows so a hovered item never touches the selected one */
.ins-menu li:not(:last-child) {
    margin-bottom: 2px;
}

.ins-menu .dropdown-item .bi {
    flex-shrink: 0;
    color: var(--ins-muted);
}

.ins-menu .dropdown-item:hover,
.ins-menu .dropdown-item:focus {
    background: color-mix(in srgb, var(--ins-primary) 10%, var(--ins-surface));
    color: var(--ins-text);
}

.ins-menu .dropdown-item:active {
    background: color-mix(in srgb, var(--ins-primary) 16%, var(--ins-surface));
    color: var(--ins-text);
}

/* The currently-selected option in a custom select */
.ins-menu .dropdown-item.active {
    background: color-mix(in srgb, var(--ins-primary) 12%, var(--ins-surface));
    color: var(--ins-primary);
}

/* optgroup label inside a custom select's menu (e.g. Dashboards / Grids) */
.ins-menu-header {
    padding: 0.35rem 0.6rem 0.2rem;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ins-muted);
}

.ins-menu-header:not(:first-child) {
    margin-top: 0.25rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--ins-border);
}

/* ---------- Custom select: a <select> progressively enhanced into .ins-menu ----------
   JS hides the native control (kept for form value + no-JS fallback) and shows a
   toggle that looks like a select box but opens the themed menu above. */
.ins-select {
    display: inline-block;
}

/* The Save dialog's "Save as" picker fills its field, like the inputs beside it */
.ins-save-modal .ins-select {
    display: block;
}

/* The real <select> once enhanceSelect has replaced it with the themed dropdown: kept
   in the DOM for the form value and the no-JS fallback, but taken out of sight.
   The size is !important because any page rule sizing its own selects (e.g.
   ".ins-dataapi-collection .ins-dash-select {width: 100%}", written for the no-JS
   case) out-specifies a single class and would stretch this hidden, absolutely
   positioned box across the viewport - clip() hides the paint but the box still
   counts towards scrollable overflow, so the page grows a phantom sideways scroll. */
.ins-select-native {
    position: absolute;
    width: 1px !important;
    height: 1px !important;
    /* .ins-dash-select carries min-width: 180px, which would clamp the 1px back up -
       computed width is the value after min/max clamping, so the size above is not
       enough on its own */
    min-width: 0 !important;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
}

.ins-select-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    height: var(--ins-control-height);
    padding: 0 0.7rem;
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    background: var(--ins-surface);
    color: var(--ins-text);
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ins-select-toggle:hover {
    border-color: color-mix(in srgb, var(--ins-primary) 45%, var(--ins-border));
}

.ins-select-toggle:focus-visible,
.ins-select-toggle[aria-expanded="true"] {
    outline: none;
    border-color: var(--ins-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

.ins-select-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ins-select-caret {
    flex-shrink: 0;
    font-size: 0.75rem;
    color: var(--ins-muted);
    transition: transform 0.15s ease;
}

.ins-select-toggle[aria-expanded="true"] .ins-select-caret {
    transform: rotate(180deg);
}

/* size the menu to its content but never narrower than the toggle */
.ins-select-menu {
    width: max-content;
    min-width: 100%;
    max-width: 320px;
}

.ins-select-menu .dropdown-item {
    white-space: nowrap;
}

/* Header nav picker keeps its compact height/width */
.ins-dash-picker .ins-select {
    display: block;
}

.ins-dash-picker .ins-select-toggle {
    height: 36px;
    min-width: 180px;
    border-radius: 9px;
}

/* Filter preset select fills its date-field column at the smaller field height */
.ins-date-field .ins-select {
    display: block;
    width: 100%;
}

.ins-date-field .ins-select-toggle {
    height: 32px;
    padding: 0 0.5rem;
    border-radius: 8px;
    font-size: 0.8rem;
}

/* ---------- Login ---------- */
.ins-login-body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: radial-gradient(56rem 32rem at 85% -12%,
            color-mix(in srgb, var(--ins-primary) 16%, transparent), transparent 60%),
        radial-gradient(44rem 30rem at -12% 112%,
            color-mix(in srgb, var(--ins-accent-5) 13%, transparent), transparent 55%),
        var(--ins-background);
}

.ins-login {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.1rem;
    padding: 2rem 1rem;
}

.ins-login-card {
    width: min(400px, 100%);
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: calc(var(--ins-border-radius) * 1.25);
    box-shadow: 0 18px 45px -18px var(--ins-shadow-strong), var(--ins-shadow-light);
    padding: 2rem 2rem 1.8rem;
}

.ins-login-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding-bottom: 1.2rem;
    border-bottom: 1px solid var(--ins-border);
    margin-bottom: 1.4rem;
}

.ins-login-brand .ins-brand-company,
.ins-login-brand .ins-brand-product {
    font-size: 1.12rem;
}

.ins-login-logo {
    height: 36px;
    width: auto;
}

.ins-login-mark {
    width: 58px;
    height: 58px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 14px;
    background: color-mix(in srgb, var(--ins-primary) 12%, var(--ins-surface));
    color: var(--ins-primary);
    font-size: 1.7rem;
}

.ins-login-title {
    font-size: 1.35rem;
    font-weight: 700;
    text-align: center;
    margin-bottom: 0.15rem;
}

.ins-login-sub {
    color: var(--ins-muted);
    font-size: 0.86rem;
    text-align: center;
    margin-bottom: 1.4rem;
}

/* Client-configured note (branding.loginMessage); newlines in the JSON kept */
/* The deployer's own note to whoever is signing in - guidance, so it is tinted
   with the HINT color rather than the primary (which reads as an action here).
   The text itself stays --ins-text: a saturated brand hint would not carry
   enough contrast to be read. */
.ins-login-message {
    white-space: pre-line;
    background: color-mix(in srgb, var(--ins-hint) 8%, var(--ins-surface));
    border-radius: calc(var(--ins-border-radius) / 1.6);
    color: var(--ins-hint);
    font-size: 0.85rem;
    line-height: 1.45;
    padding: 0.7rem 0.9rem;
    margin-bottom: 1.2rem;
}

.ins-login-alert {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    border-radius: calc(var(--ins-border-radius) / 1.6);
    padding: 0.55rem 0.8rem;
    font-size: 0.84rem;
    margin-bottom: 1rem;
}

.ins-login-alert-error {
    background: color-mix(in srgb, var(--ins-danger) 9%, var(--ins-surface));
    color: color-mix(in srgb, var(--ins-danger) 76%, black);
}

.ins-login-alert-info {
    background: color-mix(in srgb, var(--ins-success) 9%, var(--ins-surface));
    color: color-mix(in srgb, var(--ins-success) 65%, black);
}

.ins-login-form {
    display: flex;
    flex-direction: column;
    gap: 0.95rem;
}

.ins-login-field {
    display: block;
    margin: 0;
}

.ins-login-label {
    display: block;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ins-muted);
    margin-bottom: 0.3rem;
}

.ins-login-input {
    position: relative;
    display: block;
}

/* Shared text-input styling: the login and grid search boxes. The home
   sections' search box shares the placeholder/icon/focus treatment below but
   sizes itself to its 36px toolbar siblings, not --ins-control-height. */
.ins-login-input input,
.ins-search-box input[type="search"] {
    height: var(--ins-control-height);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    background: var(--ins-surface);
    color: var(--ins-text);
}

.ins-login-input input::placeholder,
.ins-search-box input[type="search"]::placeholder,
.ins-home-search-input::placeholder {
    color: color-mix(in srgb, var(--ins-muted) 55%, transparent);
}

/* The search icon inside those boxes; each box sets its own left offset below */
.ins-login-input .bi,
.ins-search-box > .bi,
.ins-home-search > .bi {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ins-muted);
    font-size: 0.95rem;
    pointer-events: none;
}

/* One focus ring for every control */
.ins-dash-select:focus,
.ins-date-field input[type="date"]:focus,
.ins-date-field input[type="number"]:focus,
.ins-date-field input.ins-date-input:focus,
.ins-date-field select:focus,
.ins-login-input input:focus,
.ins-search-box input[type="search"]:focus,
.ins-home-search-input:focus,
.ins-hash-result:focus,
.ins-save-desc:focus {
    outline: none;
    border-color: var(--ins-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

.ins-login-input .bi {
    left: 0.8rem;
}

.ins-login-input input {
    width: 100%;
    font-size: 0.9rem;
    padding: 0 0.85rem 0 2.35rem;
}

.ins-login-submit {
    justify-content: center;
    width: 100%;
    padding: 0;
    margin-top: 0.35rem;
    font-size: 0.9rem;
}

/* ---- Single sign-on ---- */

/* Outlined rather than filled: the local Sign In below it is the page's one filled
   button, and two solid blocks of the brand colour would argue about which is the
   real way in. This is the wider door, not the louder one. */
.ins-login-sso {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    height: var(--ins-control-height);
    border: 1px solid color-mix(in srgb, var(--ins-primary) 45%, var(--ins-border));
    border-radius: var(--ins-radius-control);
    background: color-mix(in srgb, var(--ins-primary) 6%, var(--ins-surface));
    color: var(--ins-primary);
    font-size: 0.9rem;
    font-weight: 600;
    text-decoration: none;
    transition: filter 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.ins-login-sso:hover {
    background: color-mix(in srgb, var(--ins-primary) 12%, var(--ins-surface));
    color: var(--ins-primary);
    box-shadow: 0 8px 20px -10px color-mix(in srgb, var(--ins-primary) 55%, transparent);
}

/* A rule with the label sitting in a gap in it. */
.ins-login-divider {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin: 1.1rem 0 0.15rem;
    color: var(--ins-muted);
    font-size: 0.76rem;
}

.ins-login-divider::before,
.ins-login-divider::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: color-mix(in srgb, var(--ins-border) 75%, transparent);
}

.ins-login-footer {
    color: var(--ins-muted);
    font-size: 0.76rem;
    text-align: center;
}

/* Terms / Privacy under the login card: same quiet weight as the footer text,
   links muted until approached. */
.ins-login-legal {
    display: block;
    margin-top: 0.35rem;
}

.ins-login-legal a {
    color: var(--ins-muted);
    text-decoration: none;
}

.ins-login-legal a:hover {
    color: var(--ins-text);
    text-decoration: underline;
}

/* ---------- Search page ---------- */
.ins-results {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.ins-search-box {
    position: relative;
    display: flex;
    gap: 0.6rem;
}

.ins-search-box > .bi {
    left: 0.9rem;
}

.ins-search-box input[type="search"] {
    flex: 1;
    min-width: 0;
    font-size: 0.9rem;
    padding: 0 0.9rem 0 2.4rem;
}

.ins-search-btn {
    padding: 0 1.2rem;
    white-space: nowrap;
}

/* Phones: the icon carries the button (the label span is markup so it can
   step aside); the input keeps the width for the query itself. */
@media (max-width: 640px) {
    .ins-search-btn {
        padding: 0 0.9rem;
    }

    .ins-search-btn .ins-search-btn-label {
        display: none;
    }
}

.ins-suggest {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 60;
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    box-shadow: 0 12px 28px -12px var(--ins-shadow-strong);
    padding: 0.25rem;
}

.ins-suggest-item {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: none;
    border-radius: 7px;
    padding: 0.4rem 0.65rem;
    font-size: 0.85rem;
    color: var(--ins-text);
    cursor: pointer;
}

.ins-suggest-item:hover,
.ins-suggest-item--active {
    background: color-mix(in srgb, var(--ins-primary) 8%, var(--ins-surface));
    color: var(--ins-primary);
}

.ins-hits {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    color: var(--ins-muted);
    font-size: 0.84rem;
}

.ins-hits b {
    color: var(--ins-text);
    font-weight: 700;
}

.ins-hits-time {
    margin-left: auto;
    font-size: 0.76rem;
    white-space: nowrap;
}

.ins-didyoumean {
    margin: 0;
    font-size: 0.86rem;
    color: var(--ins-muted);
}

.ins-didyoumean a {
    color: var(--ins-primary);
    font-weight: 600;
    font-style: italic;
    text-decoration: none;
}

.ins-didyoumean a:hover {
    text-decoration: underline;
}

.ins-grid-card {
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    overflow: hidden;
}

.ins-grid th .ins-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: inherit;
    text-decoration: none;
}

.ins-grid th .ins-sort .bi {
    font-size: 0.66rem;
    opacity: 0.45;
}

.ins-grid th .ins-sort:hover {
    color: var(--ins-primary);
}

.ins-grid th .ins-sort--active,
.ins-grid th .ins-sort--active .bi {
    color: var(--ins-hint);
    opacity: 1;
}

.ins-grid--expandable tbody tr[data-doc-row] {
    cursor: pointer;
}

mark.ins-hit {
    background: color-mix(in srgb, var(--ins-primary) 16%, transparent);
    color: inherit;
    font-weight: 600;
    padding: 0 0.12em;
    border-radius: 3px;
}

.ins-grid--expandable tbody tr.ins-grid-row--open > td {
    background-color: color-mix(in srgb, var(--ins-primary) 6%, var(--ins-surface));
}

/* Record dialog: label-over-value cards in as many columns as the dialog
   width fits (one on narrow screens). CSS multi-columns, not grid, so the
   sorted fields read top-to-bottom first, then continue in the next column. */
.ins-doc-section {
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ins-primary);
    margin: 0 0 0.75rem;
}

.ins-doc-fields + .ins-doc-section {
    margin-top: 1.1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--ins-border);
}

.ins-doc-fields {
    columns: 230px;
    column-gap: 1.4rem;
    margin: 0;
    font-size: 0.84rem;
}

.ins-doc-field {
    break-inside: avoid;
    margin-bottom: 0.9rem;
}

.ins-doc-field dt {
    color: var(--ins-muted);
    font-weight: 600;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.15rem;
}

.ins-doc-field dd {
    margin: 0;
    overflow-wrap: anywhere;
}

.ins-pager {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    flex-wrap: wrap;
}

/* keeps Previous/Next on the right, with or without the size selector */
.ins-pager-nav {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    margin-left: auto;
}

.ins-pager-sizes {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.ins-pager-sizes-label {
    color: var(--ins-muted);
    font-size: 0.8rem;
    margin-right: 0.3rem;
}

.ins-pager-size {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    height: 34px;
    padding: 0 0.45rem;
    border: 1px solid var(--ins-border);
    border-radius: 9px;
    background: var(--ins-surface);
    color: var(--ins-text);
    font-size: 0.8rem;
    font-weight: 600;
    text-decoration: none;
}

a.ins-pager-size:hover {
    border-color: var(--ins-primary);
    color: var(--ins-primary);
}

.ins-pager-size--active {
    border-color: var(--ins-primary);
    background: var(--ins-primary);
    color: var(--ins-on-color);
}

@media (max-width: 861.98px) {
    .ins-pager-sizes,
    .ins-pager-nav {
        margin-inline: auto;
    }
}

.ins-pager-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    height: 34px;
    padding: 0 0.8rem;
    border: 1px solid var(--ins-border);
    border-radius: 9px;
    background: var(--ins-surface);
    color: var(--ins-text);
    font-size: 0.84rem;
    font-weight: 600;
    text-decoration: none;
}

a.ins-pager-btn:hover {
    border-color: var(--ins-primary);
    color: var(--ins-primary);
}

.ins-pager-btn--off {
    opacity: 0.45;
}

.ins-pager-state {
    color: var(--ins-muted);
    font-size: 0.8rem;
}

/* ---------- No-access placeholder ---------- */
.ins-noaccess {
    width: min(500px, 100%);
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    padding: 2.2rem 2rem;
    text-align: center;
}

.ins-noaccess-icon {
    width: 64px;
    height: 64px;
    margin: 0 auto 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 18px;
    /* Empty states explain what to do next - the HINT color marks them as
       guidance. Decorative glyph on its own tint, never load-bearing text. */
    background: color-mix(in srgb, var(--ins-hint) 12%, var(--ins-surface));
    color: var(--ins-hint);
    font-size: 1.7rem;
}

.ins-noaccess-text {
    color: var(--ins-muted);
    font-size: 0.9rem;
    margin: 0.35rem 0 0;
}

.ins-noaccess-actions {
    display: flex;
    justify-content: center;
    margin-top: 1.3rem;
}

/* ---------- Password hash generator ---------- */
.ins-hash {
    width: min(480px, 100%);
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    padding: 2.2rem 2rem;
}

.ins-hash-head {
    text-align: center;
    margin-bottom: 1.3rem;
}

.ins-hash-text {
    color: var(--ins-muted);
    font-size: 0.9rem;
    margin: 0.35rem 0 0;
}

.ins-hash-result-wrap {
    margin-top: 1.2rem;
    padding-top: 1.2rem;
    border-top: 1px solid var(--ins-border);
}

.ins-hash-result {
    width: 100%;
    height: 44px;
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    background: color-mix(in srgb, var(--ins-primary) 4%, var(--ins-surface));
    color: var(--ins-text);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    padding: 0 0.75rem;
    margin-top: 0.3rem;
}

.ins-hash-hint {
    color: var(--ins-muted);
    font-size: 0.76rem;
    margin: 0.45rem 0 0;
}

/* ---------- Header on a phone ----------
   At 375px the full header wants ~410px: brand (logo + company + product) plus the
   feature pill, the gear, the user's name and Sign Out. Rather than let the last of
   those fall off the edge, the spacing tightens and the two pieces of prose the
   icons already convey - the user's name, and any over-long logo - give way. */
@media (max-width: 640px) {
    .ins-header {
        padding: 0.55rem 0.75rem;
        gap: 0.5rem;
    }

    .ins-header-right {
        gap: 0.35rem;
    }

    .ins-user {
        gap: 0.35rem;
        padding-left: 0.35rem;
    }

    /* the avatar still says "signed in"; the name is on the admin page */
    .ins-user-chip > span {
        display: none;
    }

    /* The logo is the whole brand on a phone (the name pair hides below), so it
       gets the room that freed rather than the old 90px squeeze - which drew a
       5:1 wordmark at ~18px in a 55px bar, a sliver floating in space. Height
       steps UP a notch: with no text beside it, the mark alone must hold the
       row. The width cap only protects against absurdly wide marks; a page
       with the picker in the bar reins the logo back in - there the page name
       is what the pixels are for. */
    .ins-header-logo {
        height: 30px;
        max-width: min(50vw, 180px);
        object-fit: contain;
        object-position: left center;
        /* the logo is the one part of the brand that must never be squeezed -
           the elastic .ins-brand around it clips, and a clipped logo reads as
           broken where truncated text merely reads as tight */
        flex-shrink: 0;
    }

    .ins-header:has(.ins-dash-picker) .ins-header-logo {
        max-width: 32vw;
    }

    /* With a logo present, the name pair beside it says the same thing twice -
       "gotoLiquorStore [logo] GotoLiq... Ins..." - and the ellipses cost the
       page picker its label. The logo IS the brand on a phone; text-only
       deployments (no logo configured) keep both names, which are then all
       there is. */
    .ins-header-logo ~ .ins-brand-company,
    .ins-header-logo ~ .ins-brand-product {
        display: none;
    }

    /* The picker's own grid/dashboard glyph goes too - the open menu's group
       headers carry that distinction, and the pixels belong to the page name. */
    .ins-dash-label {
        display: none;
    }

    /* On a dashboard or grid the page picker is the widest thing in the bar (its
       180px floor plus the page's full name, ~250px). Cap it and ellipsize the name
       instead - switching pages matters on a phone, so it shrinks rather than goes.
       It also GROWS into whatever the bar has spare, so the cap only bites when
       the row is genuinely full. */
    .ins-dash-picker {
        min-width: 0;
        flex: 1 1 auto;
        max-width: 42vw;
    }

    /* A logo-only brand (names hidden above) frees real room - spend it on the
       page name rather than leaving a gap between brand and controls. */
    .ins-header:has(.ins-header-logo) .ins-dash-picker {
        max-width: none;
    }

    .ins-dash-picker .ins-select {
        min-width: 0;
        width: 100%;
    }

    .ins-dash-picker .ins-dash-select,
    .ins-dash-picker .ins-select-toggle {
        min-width: 0;
    }

    .ins-select-label {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
}

/* Filter hides cards/rows, then any section left with none; class beats UA [hidden] */
.ins-home-card[hidden],
.ins-home-section[hidden],
.ins-home-trow[hidden] {
    display: none;
}

.ins-home-noresults {
    color: var(--ins-muted);
    font-size: 0.9rem;
    margin-top: 1.4rem;
}

/* ---------- Home: app shell (sidebar nav + one panel at a time) ----------
   The grouped section nav on the left, the active panel's content on the right.
   Same pill language as the admin section nav; on desktop the shell locks to the
   viewport and only each panel's .ins-home-scroll region scrolls (below). */
.ins-home-shell {
    display: flex;
    gap: 1.5rem;
    margin-top: 0.5rem;
}

.ins-home-nav {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    flex: 0 0 210px;
}

.ins-home-content {
    flex: 1 1 auto;
    min-width: 0;
}

/* Small uppercase group label between sidebar clusters (Browse / My items / More) */
.ins-home-nav-group {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ins-secondary);
    margin: 0.95rem 0.9rem 0.3rem;
}

.ins-home-nav-group:first-child {
    margin-top: 0.35rem;
}

/* Same visual language as the admin section nav, but (mostly) a real <button>
   that swaps panels in place (see initHomeTabs) rather than navigating - the
   AI Assistant entry is the one true link, hence the anchor resets. */
.ins-home-tab {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.65rem;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    font-weight: 500;
    font-size: 0.85rem;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}

.ins-home-tab .bi {
    font-size: 1rem;
    opacity: 0.85;
}

.ins-home-tab:hover {
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-text) 5%, transparent);
}

/* Themed keyboard-focus ring; drop the default browser outline that clashes with
   the primary pill on a mouse click */
.ins-home-tab:focus-visible {
    outline: 2px solid var(--ins-primary);
    outline-offset: 2px;
}

.ins-home-tab:focus:not(:focus-visible) {
    outline: none;
}

.ins-home-tab--active {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

.ins-home-tab--active .bi {
    opacity: 1;
}

/* Sidebar entries fill their rail; the anchor variant (AI Assistant) drops the
   link underline and keeps the pill colors on hover. */
.ins-home-nav .ins-home-tab {
    width: 100%;
    text-align: left;
    text-decoration: none;
}

/* The nav is a scroll container (overflow clips ink), so the usual outside
   focus ring would get its edges shaved - draw it just inside the pill. */
.ins-home-nav .ins-home-tab:focus-visible {
    outline-offset: -2px;
}

/* Per-section count badge at the pill's right edge */
.ins-home-tab-count {
    margin-left: auto;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.4;
    color: var(--ins-muted);
    background: color-mix(in srgb, var(--ins-muted) 10%, transparent);
    border-radius: 99px;
    padding: 0.05rem 0.5rem;
}

.ins-home-tab--active .ins-home-tab-count {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 10%, transparent);
}

/* Sidebar entries that navigate AWAY (AI Assistant, Admin) end in the go-arrow
   every card and row uses, nudging on hover just like theirs - the panel
   switches around them carry counts instead, so the two kinds read apart. */
.ins-home-tab-go {
    margin-left: auto;
    font-size: 0.8rem;
    opacity: 0.7;
    transition: transform 0.15s ease;
}

.ins-home-tab:hover .ins-home-tab-go {
    transform: translateX(3px);
}

/* Only the active section's panel is shown; class-based so the live filter keeps
   sole ownership of the [hidden] attribute on cards, rows and sections */
.ins-home-panel {
    display: none;
}

.ins-home-panel--active {
    display: block;
}

/* A lazily loaded region's stand-in while its fragment is on the wire - the
   Pin Board cards' loading treatment, sized to read as a whole panel/page. The
   spinner reuses the pin loader's keyframes. .ins-loading-note is the same thing
   for a dashboard's content area, which loads the same way. */
.ins-content--loading {
    display: block;
}

.ins-home-panel-loading,
.ins-loading-note {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 40vh;
    color: var(--ins-muted);
    font-size: 0.9rem;
}

.ins-home-panel-loading .bi,
.ins-loading-note .bi {
    animation: ins-pin-spin 1s linear infinite;
}

/* Panel head: the non-scrolling chrome above a section's listing - its toolbar.
   Dropdown-bearing controls must live here, never inside the scroll region,
   whose overflow would clip their menus. No top margin of its own: every page's
   title starts at the same height under the header (the admin page is the
   reference - Vishal, 2026-07-25). */

.ins-home-scroll > .ins-home-section:first-child {
    margin-top: 0.9rem;
}

/* The four listing sections' regions start right under the controls row; their
   0.4rem plus the region's 0.25rem shadow lane equals the controls row's own
   0.65rem gap above, so the search row sits evenly between the KPI strip and
   the cards/table. The other panels (Overview, Pins, Tips) have no controls
   row and keep the roomier 0.9rem above. */
.ins-home-scroll--tight > .ins-home-section:first-child {
    margin-top: 0.4rem;
}

/* Desktop: the shell locks to the viewport - header, sidebar and each panel's
   head stay put, only the listing region scrolls. Every flex link in the chain
   needs min-height:0 or the content refuses to shrink below its natural height.
   dvh with a vh fallback for older WebKit. */
@media (min-width: 901px) {
    body.ins-body--home {
        height: 100vh;
        height: 100dvh;
        /* the base body rule's min-height:100vh would out-vote the dvh height
           wherever dynamic browser chrome makes 100dvh < 100vh (iPad Safari),
           pushing the footer under an overflow:hidden fold */
        min-height: 0;
        overflow: hidden;
    }

    .ins-main--home {
        min-height: 0;
        display: flex;
        flex-direction: column;
        padding-bottom: 0.35rem;
    }

    .ins-main--home > .ins-home-shell,
    .ins-main--home > .ins-centered {
        flex: 1;
        min-height: 0;
    }

    .ins-home-nav {
        min-height: 0;
        overflow-y: auto;
    }

    .ins-home-content {
        display: flex;
        flex-direction: column;
        min-height: 0;
    }

    .ins-home-panel--active {
        display: flex;
        flex-direction: column;
        flex: 1;
        min-height: 0;
    }

    /* Empty states (no favorites yet, no pins yet) still center in the panel */
    .ins-home-panel--active > .ins-centered {
        flex: 1;
    }

    /* The scrollbar comes from the shared "Scroll regions" section, which holds
       a fixed lane open on the right whether or not the region overflows. The
       padding here is only the gap between the cards and that lane, so the
       thumb never rides on top of them - it is no longer load-bearing for
       keeping them apart, which is what made it un-tunable before. The bottom
       padding is the run-off under the last row; the left stays flush so cards
       line up with the panel head's title and rule. */
    .ins-home-scroll {
        flex: 1 1 0;
        min-height: 0;
        overflow-y: auto;
        padding: 0 var(--ins-scrollbar-gap) 1.1rem 0;
    }

    /* The head is a sibling of the scroll region, so it spans the full panel
       while the listing below it is a lane narrower. Inset it by exactly what
       the region loses and the title rule, search row and KPI strip end where
       the cards and table rows do. */
    .ins-home-panel-head {
        padding-right: var(--ins-scrollbar-inset);
    }
}

/* Narrow: back to document scroll; the sidebar becomes a sticky horizontal pill
   rail under the app header (53px tall - see .ins-header). */
@media (max-width: 900px) {
    .ins-home-shell {
        display: block;
    }

    .ins-home-nav {
        flex-direction: row;
        align-items: center;
        gap: 0.25rem;
        overflow-x: auto;
        position: sticky;
        top: var(--ins-header-min-height);
        z-index: 90;
        background: var(--ins-background);
        /* the gap below is padding, not margin, so the rail's background keeps
           covering it while list content scrolls underneath */
        padding: 0.45rem 0 0.6rem;
    }

    .ins-home-nav .ins-home-tab {
        width: auto;
        flex: 0 0 auto;
        white-space: nowrap;
    }

    .ins-home-nav-group {
        display: none;
    }
}

@media (max-width: 640px) {
    .ins-home-tab-count {
        display: none;
    }
}

.ins-home-section {
    margin-top: 1.4rem;
}

.ins-home-heading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.95rem;
    font-weight: 700;
    margin-bottom: 0.7rem;
}

.ins-home-heading .bi {
    color: var(--ins-hint);
}

.ins-home-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 0.9rem;
}

.ins-home-card {
    --card-accent: var(--ins-primary);
    display: flex;
    flex-direction: column;
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    overflow: hidden;
    text-decoration: none;
    color: var(--ins-text);
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.ins-home-card:hover {
    color: var(--ins-text);
    border-color: color-mix(in srgb, var(--ins-primary) 45%, var(--ins-border));
    box-shadow: 0 10px 24px -12px var(--ins-shadow-strong);
    transform: translateY(-2px);
}

/* One tint hook for every tinted surface (cards, table rows, recent rows,
   tip rows): the class sets --card-accent, each component consumes it. The
   doubled class keeps the tint's specificity above the components' own
   --card-accent defaults, which come later in the file. */
.ins-tint-1.ins-tint-1 { --card-accent: var(--ins-accent-1); }
.ins-tint-2.ins-tint-2 { --card-accent: var(--ins-accent-2); }
.ins-tint-3.ins-tint-3 { --card-accent: var(--ins-accent-3); }
.ins-tint-4.ins-tint-4 { --card-accent: var(--ins-accent-4); }
.ins-tint-5.ins-tint-5 { --card-accent: var(--ins-accent-5); }
.ins-tint-6.ins-tint-6 { --card-accent: var(--ins-accent-6); }
.ins-tint-7.ins-tint-7 { --card-accent: var(--ins-accent-7); }
.ins-tint-8.ins-tint-8 { --card-accent: var(--ins-accent-8); }
.ins-tint-9.ins-tint-9 { --card-accent: var(--ins-accent-9); }
.ins-tint-10.ins-tint-10 { --card-accent: var(--ins-accent-10); }

/* Top of the card: icon + title (+ record count). Grows so footers line up.
   Descriptions are tooltip-only (the link's title attr), not rendered text. */
.ins-home-card-body {
    flex: 1;
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.75rem;
}

.ins-home-card-icon {
    width: 36px;
    height: 36px;
    flex: 0 0 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    font-size: 1rem;
}

.ins-home-card-text {
    min-width: 0;
}

.ins-home-card-title {
    display: block;
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1.25;
}

/* Card description: rendered but hidden until the page's Descriptions toggle
   (ins-descs-on on <main>) reveals it as a 2-line-clamped block */
.ins-home-card-desc {
    display: none;
    color: var(--ins-muted);
    font-size: 0.8rem;
    margin-top: 0.2rem;
}

.ins-descs-on .ins-home-card-desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Prominent record count on grid & saved-grid cards */
.ins-home-card-count {
    display: block;
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--ins-text);
}

/* Footer bar: tinted to match the icon; meta on the left, go-arrow on the right */
.ins-home-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.4rem 0.8rem;
    background: color-mix(in srgb, var(--card-accent) 11%, var(--ins-surface));
    color: var(--card-accent);
    font-size: 0.74rem;
    font-weight: 600;
}

.ins-home-card-meta {
    display: flex;
    gap: 0.9rem;
    min-width: 0;
}

.ins-home-card-meta > span {
    display: inline-flex;
    align-items: center;
    gap: 0.28rem;
}

.ins-home-card-go {
    flex-shrink: 0;
    font-size: 0.95rem;
    transition: transform 0.15s ease;
}

.ins-home-card:hover .ins-home-card-go {
    transform: translateX(3px);
}

/* ---------- Home: KPI strips (the Overview's stats, each catalog's totals) ---------- */
.ins-home-kpis {
    margin-bottom: 1.15rem;
}

/* Deliberately neutral - these totals sit above the clickable dashboard/grid
   cards and must not out-shout them, so the icons stay grey and the number
   keeps the natural body colour (no accent tint, unlike the dashboard KPIs). */
.ins-home-kpis .ins-kpi-icon {
    background: var(--ins-background);
    color: var(--ins-muted);
}

/* In a catalog section's head the strip sits between the title rule and the
   controls row; the controls row brings its own top margin, so the strip only
   pushes off the rule above it. */
.ins-home-kpis--head {
    margin: 0.85rem 0 0;
}

/* ---------- Home: section toolbar (title row) and controls row ----------
   Each listing section's head is two rows: the title with its underline rule,
   then a controls row (.ins-home-controls) - search left, everything that
   narrows or reshapes the listing right-aligned. Sits in the panel head, above
   the scroll region. */
.ins-home-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 0.6rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--ins-border);
    /* One row height for every section, whether or not it carries a control:
       the Pins title row holds the 36px column switch, plus this row's own
       padding and rule - so the title and its underline sit at the same spot
       on every section. */
    min-height: calc(36px + 0.6rem + 1px);
}

.ins-home-toolbar-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 1rem;
    font-weight: 700;
    margin: 0;
}

.ins-home-toolbar-title .bi {
    color: var(--ins-hint);
}

/* The Pins title's "· <picked label>" tail: same line, but muted and lighter so
   the section still reads as "Pins" at a glance and the tail as its current
   narrowing. A long label truncates rather than pushing the row's controls. */
.ins-home-toolbar-title-sub {
    font-weight: 500;
    color: var(--ins-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 14rem;
    /* the tail is the only part that may give way - it truncates so the count
       pill after it is never squeezed off the row on a narrow screen */
    flex: 0 1 auto;
    min-width: 0;
}

.ins-home-toolbar-title-sub::before {
    content: '·';
    margin-right: 0.4rem;
}

/* The sidebar count pill, reused in a title: there it sits at the pill's right
   edge (margin-left:auto), here it follows the text it counts. */
.ins-home-toolbar-title-count {
    margin-left: 0;
    flex: none;
}

/* Phones drop the sidebar's counts (the rail is a cramped horizontal strip
   there) - but the title's count is the heading's own number and the row has
   room for it, so it stays. */
@media (max-width: 640px) {
    .ins-home-toolbar-title-count {
        display: inline-block;
    }
}

.ins-home-toolbar-spacer,
.ins-home-controls-spacer {
    flex: 1 1 auto;
}

/* Controls row under the title rule: the section's own search on the left,
   everything that narrows or reshapes the listing right-aligned. Wraps to two
   rows on narrow screens with the search keeping the first. */
.ins-home-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 0.6rem;
    margin-top: 0.65rem;
}

/* The box shares the login/grid-search placeholder, icon and focus styling
   (see the shared groups above); like the row's other controls it sizes to the
   app-wide 40px control height. */
.ins-home-search {
    position: relative;
    width: min(300px, 100%);
}

/* Phones: the box takes its own full row - at 300px it left a sliver beside
   itself that the other controls half-wrapped into. */
@media (max-width: 640px) {
    .ins-home-search {
        width: 100%;
    }
}

.ins-home-search > .bi {
    left: 0.6rem;
    font-size: 0.85rem;
}

.ins-home-search-input {
    width: 100%;
    height: 40px;
    border: 1px solid var(--ins-border);
    border-radius: 8px;
    background: var(--ins-surface);
    font-size: 0.82rem;
    color: var(--ins-text);
    padding: 0 0.6rem 0 1.9rem;
}

/* Controls-row selects (collection filter, sort) before enhanceSelect dresses
   them; the enhanced toggle then matches. Every control in the row shares the
   app-wide 40px height and 8px radius so the cluster reads as one family. */
.ins-home-tool-select {
    height: 40px;
    max-width: 180px;
    border-radius: 8px;
    border: 1px solid var(--ins-border);
    background: var(--ins-surface);
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--ins-text);
    padding: 0 0.6rem;
    cursor: pointer;
}

.ins-home-controls .ins-select-toggle {
    height: 40px;
    border-radius: 8px;
    font-size: 0.82rem;
}

/* A select in a TITLE row (the Pins label filter) sizes to the title rows' 36px
   control family instead - the height the toolbar min-height is pinned to.
   (Deleted once as dead when the listing controls moved to their own row;
   live again, do not "clean" it.) */
.ins-home-toolbar .ins-home-tool-select,
.ins-home-toolbar .ins-select-toggle {
    height: 36px;
    border-radius: 8px;
    font-size: 0.82rem;
}

/* An identifying icon (collection / sort) rides inside the select's left edge -
   absolutely positioned over the control, so it decorates the native select and
   the enhanced dropdown toggle alike without being clickable itself. */
.ins-home-tool-field {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.ins-home-tool-field > .bi {
    position: absolute;
    left: 0.6rem;
    z-index: 1;
    font-size: 0.85rem;
    color: var(--ins-muted);
    pointer-events: none;
}

.ins-home-tool-field .ins-home-tool-select,
.ins-home-tool-field .ins-dash-select,
.ins-home-tool-field .ins-select-toggle {
    padding-left: 1.9rem;
}


/* Base for the controls row's favorites-only toggle, tinted while on. */
.ins-home-tool-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    height: 40px;                /* the controls row's uniform height */
    padding: 0 0.65rem;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}

.ins-home-tool-btn:hover {
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-text) 5%, transparent);
}

.ins-home-tool-btn:focus-visible {
    outline: 2px solid var(--ins-primary);
    outline-offset: 2px;
}

.ins-home-tool-btn:focus:not(:focus-visible) {
    outline: none;
}

.ins-home-tool-btn--on {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

/* Descriptions toggle, now one per section toolbar: icon-only ghost pill in the
   same language (the title attr carries the words), tinted while on. */
.ins-home-desc-toggle {
    display: inline-flex;
    align-items: center;
    height: 36px;                /* the toolbar's uniform control height */
    padding: 0 0.7rem;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    font-size: 1rem;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}

.ins-home-desc-toggle:hover {
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-text) 5%, transparent);
}

.ins-home-desc-toggle:focus-visible {
    outline: 2px solid var(--ins-primary);
    outline-offset: 2px;
}

.ins-home-desc-toggle:focus:not(:focus-visible) {
    outline: none;
}

.ins-home-desc-toggle--on {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

/* The controls row's two icon toggles (favorites-only, descriptions) wear the
   segmented switch's bordered skin and one square 40px footprint - so search,
   selects, toggles and switch line up as a single consistent control family. */
.ins-home-tool-btn,
.ins-home-desc-toggle {
    width: 40px;
    height: 40px;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--ins-border);
    border-radius: 8px;
    background: var(--ins-surface);
    font-size: 0.95rem;
}

.ins-home-tool-btn:hover,
.ins-home-desc-toggle:hover {
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-text) 5%, var(--ins-surface));
}

.ins-home-tool-btn.ins-home-tool-btn--on,
.ins-home-desc-toggle.ins-home-desc-toggle--on {
    color: var(--ins-primary);
    border-color: color-mix(in srgb, var(--ins-primary) 35%, var(--ins-border));
    background: color-mix(in srgb, var(--ins-primary) 10%, var(--ins-surface));
}

/* Cards/table segmented switch, in the Pin Board column picker's skin. 36px in
   a title row (the Pins column picker - the toolbar min-height is sized to it);
   the listing sections' controls row stretches it to its 40px family below. */
.ins-view-seg {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    height: 36px;
    padding: 0.25rem;
    border: 1px solid var(--ins-border);
    border-radius: 8px;
    background: var(--ins-surface);
}

.ins-home-controls .ins-view-seg {
    height: 40px;
}

.ins-view-seg-btn {
    width: 1.9rem;
    height: 1.6rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--ins-muted);
    font-size: 0.85rem;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}

.ins-view-seg-btn:hover {
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-text) 5%, transparent);
}

.ins-view-seg-btn--active {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

/* Phones force the pin board to one column and sleep its switcher */
.ins-view-seg-btn:disabled {
    opacity: 0.45;
    cursor: default;
}

/* ---------- Home: table view ----------
   Every listing renders both its card grid and this table; ins-view-table on
   <main> decides which shows. The Overview's quick-access card rows are exempt
   (see the :not(.ins-overview-favs) rule below). */
/* overflow-x auto at every width: nowrap cells (long titles/descriptions) must
   scroll sideways, never clip columns off the table's right edge. The scroll
   container also clips for the border radius. It costs sticky column headers -
   position:sticky can only stick against this wrap, which never scrolls
   vertically - an accepted trade: reachable data beats a pinned header row. */
.ins-home-tablewrap {
    display: none;
    /* no own top margin: the gap under the panel head belongs to the section
       (whose margin this would collapse with and out-vote - it did once, and
       table view sat lower than the cards in the tightened listing panels) */
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    overflow-x: auto;
}

.ins-view-table .ins-home-tablewrap {
    display: block;
}

/* The Overview's quick-access row is exempt: it is a curated summary with no
   table twin, so table mode would silently blank it. */
.ins-view-table .ins-home-cards:not(.ins-overview-favs) {
    display: none;
}

.ins-home-table {
    margin: 0;
    width: 100%;
}

/* Header row: plain labels (sorting lives in the toolbar dropdown), sized to
   match the data rows and set off from them on the page background. */
.ins-home-table thead th {
    padding: 0.75rem 0.75rem;
    background: var(--ins-background);
    vertical-align: middle;
    border-bottom: 1px solid var(--ins-border);
}

.ins-home-th-num {
    text-align: right;
}

/* The whole row opens the page (data-href); the star and delete cells stop the
   click from bubbling into that. */
.ins-home-trow {
    --card-accent: var(--ins-primary);
    cursor: pointer;
}

.ins-home-trow:hover {
    background: color-mix(in srgb, var(--ins-primary) 5%, transparent);
}

.ins-home-trow-title {
    font-weight: 600;
    white-space: nowrap;
}

/* The shared kind chip inlines beside the title (its base recipe is flex) */
.ins-home-trow-title .ins-kind-icon {
    display: inline-flex;
    vertical-align: middle;
    margin-right: 0.6rem;
}

/* Description column follows the page-wide Descriptions toggle, exactly like
   the cards: hidden until ins-descs-on, then full text wrapping over as many
   lines as it needs (never an ellipsis), width-capped so it can't crowd out
   the numeric columns. */
.ins-home-th-desc,
.ins-home-trow-desc {
    display: none;
}

.ins-descs-on .ins-home-th-desc {
    display: table-cell;
}

.ins-descs-on .ins-home-trow-desc {
    display: table-cell;
    color: var(--ins-muted);
    max-width: 420px;
    min-width: 220px;
}

.ins-home-trow-coll,
.ins-home-trow-date {
    color: var(--ins-muted);
    white-space: nowrap;
}

.ins-home-trow-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.ins-home-trow-actions {
    text-align: right;
    white-space: nowrap;
}

/* Inline the shared star/delete forms inside the actions cell (they're absolute
   on cards), and give the open-arrow the same ghost footprint. */
.ins-home-trow-actions .ins-fav-form,
.ins-home-trow-actions .ins-saved-delete-form {
    position: static;
    display: inline-flex;
    vertical-align: middle;
    margin: 0;
}

.ins-home-trow-go {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    color: var(--ins-muted);
    vertical-align: middle;
}

.ins-home-trow-go:hover {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 9%, var(--ins-surface));
}

/* Tint the row icon like its card twin */
/* Phones: hold a readable minimum width and let the wrap's overflow-x carry it */
@media (max-width: 720px) {
    .ins-home-table {
        min-width: 640px;
    }
}

/* ---------- Home: Overview ----------
   The landing section: the KPI strip, the per-aspect favorites card rows, the
   favorite + recent saved-view lists and the quiet quick-link pills. */
/* The Overview's head (the welcome block) carries the same rule as the other
   sections' toolbars, so scrolled content cuts off at a clean line instead of
   sliding bare into the welcome text's edge. */
.ins-home-panel[data-tab="overview"] .ins-home-panel-head {
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--ins-border);
}

.ins-home-panel[data-tab="overview"] .ins-page-head {
    margin-bottom: 0;
}

.ins-overview-block {
    margin-top: 1.6rem;
}

.ins-overview-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.7rem;
}

.ins-overview-head .ins-home-heading {
    margin-bottom: 0;
}

.ins-overview-hint {
    color: var(--ins-muted);
    font-size: 0.85rem;
    margin: 0;
}

/* Recent saved views: one surface, one row per view */
.ins-recent-list {
    display: flex;
    flex-direction: column;
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    overflow: hidden;
}

.ins-recent-row {
    --card-accent: var(--ins-primary);
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.85rem;
    text-decoration: none;
    color: var(--ins-text);
    border-top: 1px solid color-mix(in srgb, var(--ins-border) 60%, transparent);
}

.ins-recent-row:first-child {
    border-top: 0;
}

.ins-recent-row:hover {
    background: color-mix(in srgb, var(--ins-primary) 5%, transparent);
    color: var(--ins-text);
}

/* Shared tinted kind chip: saved-view rows on the Overview AND the table
   views' title cells - one recipe, colored by the host row's --card-accent. */
.ins-kind-icon,
.ins-tip-row-icon {
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    font-size: 0.85rem;
    color: var(--ins-muted);
    background: color-mix(in srgb, var(--ins-muted) 12%, var(--ins-surface));
}

.ins-recent-name {
    font-weight: 600;
    font-size: 0.86rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ins-recent-parent {
    color: var(--ins-muted);
    font-size: 0.8rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ins-recent-date {
    margin-left: auto;
    color: var(--ins-muted);
    font-size: 0.78rem;
    white-space: nowrap;
}

/* The two saved-view lists (starred / recent) sit side by side when the width
   allows; the wrapper carries the block gap, so the sections inside don't
   double it up with their own. */
.ins-overview-lists {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
    gap: 1rem 1.5rem;
    margin-top: 1.6rem;
}

.ins-overview-lists > .ins-overview-block {
    margin-top: 0;
}

/* Quick links at the Overview's tail: quiet bordered pills with no filled
   surface - jump-off points into the remaining sections, not content cards,
   so they stay visually suppressed under the lists above. */
.ins-overview-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 1.6rem;
}

/* Equal-width boxes sharing the full row; the min-width lets them wrap into a
   2x2 (and eventually a stack) instead of crushing on narrow screens. */
.ins-overview-link {
    flex: 1 1 0;
    min-width: 11rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.55rem 0.9rem;
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    background: transparent;
    color: var(--ins-muted);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}

.ins-overview-link:hover {
    color: var(--ins-primary);
    border-color: color-mix(in srgb, var(--ins-primary) 45%, var(--ins-border));
    background: color-mix(in srgb, var(--ins-primary) 6%, transparent);
}

.ins-overview-link:focus-visible {
    outline: 2px solid var(--ins-primary);
    outline-offset: 2px;
}

.ins-overview-link:focus:not(:focus-visible) {
    outline: none;
}

.ins-overview-link-go {
    font-size: 0.8rem;
    opacity: 0.7;
}

/* ---------- Home: Tips & Tricks (themed groups of scannable rows) ----------
   The scroll region holds one .ins-tips-cat group per theme, each a heading
   over compact icon-chip rows, with a docs-style mini TOC riding sticky on the
   right (click jumps, initTips's scrollspy highlights the group being read).
   Width-capped so a one-line tip stays readable. */
.ins-tips-layout {
    display: flex;
    align-items: flex-start;
    gap: 2.0rem;
    /*margin-top: 0.9rem;*/
}

/* The section IS the guidance surface, so its lightbulb - in the panel title and
   in the sidebar entry that opens it - burns in the hint color. Scoped to this
   one section: the other sidebar icons stay neutral, or the rail would turn into
   a color chart. The sidebar bulb steps aside while its pill is ACTIVE, where the
   whole entry (label, icon, tint) is primary like every other active section. */
.ins-home-panel[data-tab="tips"] .ins-home-toolbar-title .bi,
.ins-home-tab[data-tab="tips"]:not(.ins-home-tab--active) .bi {
    /*color: var(--ins-hint);*/
    opacity: 1;
}

.ins-tips-main {
    flex: 1 1 auto;
    min-width: 0;
}

.ins-tips-toc {
    flex: 0 0 190px;
    position: sticky;
    top: 0.25rem;
}

.ins-tips-toc-label {
    display: block;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ins-hint);
    margin: 0.4rem 0 0.4rem 0;
    padding-left: 0.8rem;
}

.ins-tips-toc-link {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-left: 2px solid color-mix(in srgb, var(--ins-border) 70%, transparent);
    padding: 0.32rem 0 0.32rem 0.75rem;
    font: inherit;
    font-size: 0.82rem;
    color: var(--ins-muted);
    cursor: pointer;
}

.ins-tips-toc-link:hover {
    color: var(--ins-text);
}

.ins-tips-toc-link--active {
    color: var(--ins-primary);
    font-weight: 600;
    border-left-color: var(--ins-primary);
}

/* Narrow screens use document scroll and the rows read linearly - a sticky
   in-page TOC has no anchor point there, so it steps aside. */
@media (max-width: 900px) {
    .ins-tips-toc {
        display: none;
    }
}

.ins-tips-cat {
    margin-top: 1.5rem;
    max-width: 860px;
    /* a TOC jump leaves a breath of room above the landed heading */
    scroll-margin-top: 0.5rem;
}

.ins-tips-main > .ins-tips-cat:first-child {
    margin-top: 0;
}

.ins-tips-cat-title {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ins-secondary);
    margin: 0.7rem 0 0.35rem;
}

.ins-tip-row {
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    padding: 0.5rem 0.15rem;
    border-top: 1px solid color-mix(in srgb, var(--ins-border) 60%, transparent);
}

.ins-tips-cat-title + .ins-tip-row {
    border-top: 0;
}

.ins-tip-row-text {
    min-width: 0;
    font-size: 0.82rem;
}

.ins-tip-row-title {
    display: block;
    font-size: 0.85rem;
    line-height: 1.35;
}

.ins-tip-row-desc {
    color: var(--ins-muted);
    line-height: 1.45;
}

/* Card link: the wrapper card keeps the star form a sibling of the link, not nested */
.ins-home-card-link {
    flex: 1;
    display: flex;
    flex-direction: column;
    text-decoration: none;
    color: var(--ins-text);
    min-width: 0;
}

.ins-home-card-link:hover {
    color: var(--ins-text);
}

/* Keep the title clear of the floating star, which sits top-right on every card.
   The star's footprint is 0.55rem (right offset) + 32px (button) ≈ 2.6rem, so the
   gutter must exceed that or a single-line title's tail slides under the star. */
.ins-home-card--fav .ins-home-card-body {
    padding-right: 3rem;
}

/* Every card carrying a star needs a positioning context for the absolute star form */
.ins-home-card--fav {
    position: relative;
}

/* Favorite star on a home card: a ghost-button footprint, tinted to the card's accent
   when active. Absolutely positioned top-right (overridden to inline in saved lists). */
.ins-fav-form {
    position: absolute;
    top: 0.55rem;
    right: 0.55rem;
    margin: 0;
}

.ins-fav-btn {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.ins-fav-btn:hover {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 9%, var(--ins-surface));
}

.ins-fav-btn--on {
    color: var(--card-accent);
}

.ins-fav-btn--on:hover {
    color: var(--card-accent);
    background: color-mix(in srgb, var(--card-accent) 12%, var(--ins-surface));
}

/* Saved-view cards carry TWO floating controls: the star sits exactly where it
   does on every other card and delete stacks BENEATH it; the forms drop their
   card-absolute positioning inside the stack. The body keeps the usual one-
   control gutter but guarantees enough height that the stack never reaches
   into the footer bar. */
.ins-home-card-actions {
    position: absolute;
    top: 0.55rem;
    right: 0.55rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
}

.ins-home-card-actions .ins-fav-form,
.ins-home-card-actions .ins-saved-delete-form {
    position: static;
    margin: 0;
}

.ins-home-card--saved .ins-home-card-body {
    padding-right: 3rem;
    min-height: 4.9rem;
}

/* The footer names the parent the view belongs to - ellipsize long titles
   rather than pushing the go-arrow out of the bar */
.ins-home-card--saved .ins-home-card-meta,
.ins-home-card--saved .ins-home-card-meta > span {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
}

.ins-home-card--saved .ins-home-card-meta > span {
    text-overflow: ellipsis;
    display: inline-block;
}

.ins-saved-delete {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.ins-saved-delete:hover {
    color: var(--ins-danger);
    background: color-mix(in srgb, var(--ins-danger) 9%, var(--ins-surface));
}

/* ---------- Page-head actions & save-view dialog ---------- */
.ins-page-head--split {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.6rem 1rem;
}

/* A title + description block is taller than the action buttons, so top-aligning
   is what lines the buttons up with the title. A bare title is shorter than they
   are and the same rule leaves it riding ~8px high, so centre that case instead. */
.ins-page-head--split:not(:has(.ins-page-desc)) {
    align-items: center;
}

/* Compact modifier: tighter padding and smaller text, but the shared control
   height, so page-head actions line up with the search box and other buttons. */
.ins-btn-sm {
    padding: 0 0.85rem;
    font-size: 0.84rem;
}

.ins-save-modal {
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
}

.ins-save-modal .modal-header,
.ins-save-modal .modal-footer {
    border-color: var(--ins-border);
}

/* Close ✕ sized down to the saved-view / pin remove buttons (32px hit area, small
   glyph, quiet until hovered) - Bootstrap's default btn-close is much chunkier. */
.ins-save-modal .btn-close {
    width: 2rem;
    height: 2rem;
    padding: 0;
    background-size: 0.9rem;
    border-radius: 8px;
    opacity: 0.55;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

.ins-save-modal .btn-close:hover {
    opacity: 0.9;
    background-color: color-mix(in srgb, var(--ins-text) 8%, transparent);
}

.ins-save-title {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 1rem;
    font-weight: 600;
    margin: 0;
}

.ins-save-title .bi {
    color: var(--ins-hint);
}

.ins-save-hint {
    color: var(--ins-muted);
    font-size: 0.84rem;
    margin-bottom: 1rem;
}

.ins-save-modal .ins-login-field {
    margin-bottom: 0.9rem;
}

.ins-save-desc {
    width: 100%;
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    background: var(--ins-surface);
    color: var(--ins-text);
    font-size: 0.9rem;
    padding: 0.5rem 0.85rem;
    resize: vertical;
}

/* ---------- Admin ---------- */
.ins-admin-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 36px is the app's control height (toolbar selects, segmented switches, the
       button variants) - the header's utilities match it, square. */
    width: 36px;
    height: 36px;
    border: 1px solid var(--ins-border);
    border-radius: 8px;
    background: var(--ins-surface);
    color: var(--ins-muted);
    font-size: 1rem;
    text-decoration: none;   /* Bootstrap underlines anchors; these are buttons */
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.ins-admin-link:hover {
    color: var(--ins-primary);
    border-color: color-mix(in srgb, var(--ins-primary) 35%, var(--ins-border));
    background: color-mix(in srgb, var(--ins-primary) 7%, var(--ins-surface));
}

/* The AI Assistant entry carries a label - it is a feature destination, not a
   utility - so it widens from the 36px icon square into a pill (same height). */
.ins-ai-nav {
    width: auto;
    padding: 0 0.7rem;
    gap: 0.4rem;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--ins-text);
    white-space: nowrap;
}

.ins-ai-nav .bi {
    color: var(--ins-secondary);
    font-size: 1rem;
}

/* Phones: the picker and search need the room - back to the bare sparkle */
@media (max-width: 720px) {
    .ins-ai-nav {
        width: 36px;
        padding: 0;
        gap: 0;
    }

    .ins-ai-nav-label {
        display: none;
    }
}

/* The admin page reuses the home page's sidebar and toolbar classes wholesale
   (.ins-home-nav / .ins-home-tab / .ins-home-nav-group / .ins-home-toolbar) -
   only the shell differs: admin keeps document scroll, so its rail sticks
   under the header instead of living in a viewport-locked shell. The <=900px
   horizontal-rail behavior comes from the shared .ins-home-nav rules. */
.ins-admin {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
    margin-top: 0.5rem;
}

@media (min-width: 901px) {
    .ins-admin > .ins-home-nav {
        position: sticky;
        top: calc(var(--ins-header-min-height) + 1rem);
    }
}

@media (max-width: 900px) {
    .ins-admin {
        display: block;
    }
}

.ins-admin-content {
    flex: 1 1 auto;
    min-width: 0;
}

/* The section toolbar (title + rule) leads each admin tab's content */
.ins-admin-content .ins-home-toolbar {
    margin-bottom: 1.1rem;
}

.ins-admin-card {
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    padding: 1.2rem 1.4rem;
    margin-bottom: 1.1rem;
}

/* Phones: desktop-scale card padding was spending ~15% of a 375px row on air. */
@media (max-width: 640px) {
    .ins-admin-card {
        padding: 0.9rem 0.9rem;
    }
}

.ins-admin-card-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--ins-secondary);
    margin: 0 0 0.9rem;
}

.ins-admin-card-title .bi {
    color: var(--ins-secondary);
}

/* A button inside a card title (e.g. Add User) keeps its own icon color - the
   primary title-icon color would be invisible on the button's filled background. */
.ins-admin-card-title .ins-btn-primary .bi {
    color: inherit;
}

.ins-admin-count {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--ins-muted);
    background: color-mix(in srgb, var(--ins-primary) 10%, var(--ins-surface));
    border-radius: 999px;
    padding: 0.1rem 0.55rem;
}

.ins-admin-card-hint {
    color: var(--ins-muted);
    font-size: 0.85rem;
    line-height: 1.5;
    margin: -0.3rem 0 1.1rem;
}

.ins-admin-fields {
    margin: 0;
}

.ins-admin-row {
    display: flex;
    justify-content: space-between;
    gap: 1.2rem;
    padding: 0.4rem 0;
    border-bottom: 1px solid color-mix(in srgb, var(--ins-border) 60%, transparent);
}

.ins-admin-row:last-child {
    border-bottom: none;
}

.ins-admin-row dt {
    color: var(--ins-muted);
    font-size: 0.85rem;
    white-space: nowrap;
}

.ins-admin-row dd {
    margin: 0;
    text-align: right;
    font-weight: 600;
    font-size: 0.85rem;
    color: var(--ins-text);
    overflow-wrap: anywhere;
}

.ins-admin-note {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-warning) 12%, var(--ins-surface));
    border: 1px solid color-mix(in srgb, var(--ins-warning) 35%, var(--ins-border));
    border-radius: var(--ins-radius-control);
    padding: 0.6rem 0.85rem;
    font-size: 0.85rem;
}

.ins-admin-note .bi {
    color: var(--ins-warning);
}

.ins-admin-table-wrap {
    overflow-x: auto;
}

.ins-admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.85rem;
}

.ins-admin-table th,
.ins-admin-table td {
    text-align: left;
    padding: 0.5rem 0.7rem;
    border-bottom: 1px solid color-mix(in srgb, var(--ins-border) 60%, transparent);
    white-space: nowrap;
}

.ins-admin-table th {
    color: var(--ins-muted);
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.ins-admin-table tbody tr:last-child td {
    border-bottom: none;
}

.ins-admin-num {
    text-align: right !important;
}

.ins-admin-mono {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.8rem;
    color: var(--ins-muted);
    white-space: normal;
    overflow-wrap: anywhere;
}

.ins-admin-form {
    margin-top: 0.4rem;
}

/* Short fields pair up across a full-width card instead of each stretching the
   whole way - a company name in a 1150px box is mostly empty box. Falls back to
   one column as soon as a pair would drop under a readable width, and the submit
   row spans both so the button keeps its place under the fields. */
.ins-admin-form--split {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
    gap: 0.95rem 1.4rem;
    align-items: start;
}

/* A multi-line field wants the whole row - and so do the access checkboxes,
   the grant fieldsets, the action row and the submit, which also keeps its
   natural width rather than stretching across the grid cell. */
.ins-admin-form--split > .ins-login-field:has(textarea),
.ins-admin-form--split > .ins-admin-check,
.ins-admin-form--split > .ins-admin-grants,
.ins-admin-form--split > .ins-admin-actions {
    grid-column: 1 / -1;
}

.ins-admin-form--split > .ins-admin-save {
    grid-column: 1 / -1;
    justify-self: start;
}

/* ---- Users tab: list actions + add/edit form ---- */
.ins-admin-add {
    margin-left: auto;   /* push the Add User button to the right of the card title */
}

/* ---- Data API tab: collection picker + the action buttons ---- */
.ins-dataapi {
    margin-top: 0.7rem;
}

.ins-dataapi-collection {
    max-width: 320px;
    margin-bottom: 1.1rem;
}

.ins-dataapi-collection .ins-dash-select {
    width: 100%;
}

/* Add / re-time row above the schedule table: the two fields and the button share a
   line, wrapping on a narrow card. flex-end so the button sits on the selects' base
   line rather than beside their labels. */
.ins-schedule-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.8rem 1.2rem;
    margin: 1.2rem 0;
}

.ins-schedule-form .ins-login-field {
    flex: 0 1 15rem;
}

.ins-schedule-form .ins-dash-select {
    width: 100%;
}

/* One labelled block per action group (Import / Delete / Refresh), laid out like
   the Collection field above them. */
.ins-dataapi-groups {
    display: flex;
    flex-wrap: wrap;
    gap: 1.3rem 2.2rem;
}

.ins-dataapi-group {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.ins-dataapi-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

/* Back link above a detail card (e.g. a collection detail page). */
.ins-admin-back {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--ins-muted);
    text-decoration: none;
    margin-bottom: 0.7rem;
}

.ins-admin-back:hover {
    color: var(--ins-primary);
}

.ins-admin-row-actions {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
}

.ins-admin-action-form {
    display: inline-flex;
    margin: 0;
}

.ins-admin-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 1px solid var(--ins-border);
    border-radius: 7px;
    background: var(--ins-surface);
    color: var(--ins-muted);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.ins-admin-action:hover {
    color: var(--ins-primary);
    border-color: color-mix(in srgb, var(--ins-primary) 35%, var(--ins-border));
    background: color-mix(in srgb, var(--ins-primary) 7%, var(--ins-surface));
}

.ins-admin-action--danger:hover {
    color: var(--ins-danger);
    border-color: color-mix(in srgb, var(--ins-danger) 35%, var(--ins-border));
    background: color-mix(in srgb, var(--ins-danger) 8%, var(--ins-surface));
}

.ins-admin-check {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    font-size: 0.85rem;
    color: var(--ins-text);
    cursor: pointer;
    padding: 0.15rem 0;
}

.ins-admin-hint-inline {
    color: var(--ins-muted);
    font-weight: 400;
    font-size: 0.8rem;
}

/* A group name in the Users table's Groups column. */
.ins-admin-chip {
    display: inline-block;
    font-size: 0.76rem;
    line-height: 1.5;
    color: var(--ins-secondary);
    background: color-mix(in srgb, var(--ins-secondary) 10%, var(--ins-surface));
    border: 1px solid color-mix(in srgb, var(--ins-secondary) 22%, transparent);
    border-radius: 999px;
    padding: 0.05rem 0.5rem;
    margin: 0.1rem 0.25rem 0.1rem 0;
}

/* "via <group>" on a user-form row a membership already grants. Access is a union,
   so the checkbox keeps showing the account's OWN grant - this only says the row is
   covered either way. Muted on purpose: it is an explanation, not a control. */
.ins-admin-inherit {
    font-size: 0.72rem;
    color: var(--ins-muted);
    background: color-mix(in srgb, var(--ins-primary) 9%, transparent);
    border-radius: 999px;
    padding: 0.02rem 0.42rem;
    white-space: nowrap;
}

.ins-admin-check--inherited > span:first-of-type {
    color: var(--ins-muted);
}

/* In the narrow grant columns the marker would otherwise squeeze the item's own
   label into two lines. Let it drop underneath instead: the label keeps its width
   (flex-basis auto, no shrink) and the marker wraps to the next line. The wide
   Administrator / AI rows have room, so they stay on one line. */
.ins-admin-grant-list .ins-admin-check--inherited {
    flex-wrap: wrap;
}

.ins-admin-grant-list .ins-admin-check--inherited > span:first-of-type {
    flex: 1 0 auto;
}

/* ---- User form: group access vs direct access ---- */

/* Access is a union of two different things, and a column of identical
   checkboxes cannot say which is which - so each half gets a lead-in label,
   the same uppercase-label idiom the sidebar uses for its nav groups. */
.ins-admin-form--split > .ins-admin-section {
    grid-column: 1 / -1;
}

.ins-admin-section {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.2rem 0.7rem;
    /* pulled toward the box it introduces, so the label reads as its heading
       rather than as something floating between two boxes */
    margin: 0.55rem 0 -0.35rem;
    font-size: 1rem;      /* neutralize the h3 default; the title sizes itself */
    font-weight: 400;
}

.ins-admin-section-title {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ins-secondary);
}

.ins-admin-section-rule {
    flex: 1 1 2rem;
    height: 1px;
    background: color-mix(in srgb, var(--ins-border) 75%, transparent);
}

.ins-admin-section-hint {
    font-size: 0.8rem;
    color: var(--ins-muted);
}

/* The membership box needs no head of its own - the Group Access label above is
   its title - and carries a faint tint so the "not this account's own" block
   stays distinguishable once its label has scrolled away. */
.ins-admin-grants--groups {
    background: color-mix(in srgb, var(--ins-primary) 5%, var(--ins-surface));
    border-color: color-mix(in srgb, var(--ins-primary) 22%, var(--ins-border));
}

.ins-admin-grants--groups > .ins-admin-grant-list {
    margin-top: 0;
}

.ins-admin-grants {
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    padding: 0.5rem 0.75rem 0.5rem 0.75rem;
    margin: 0;
}

/* Header row: group title on the left, the "All" toggle on the right, with a
   separator line dividing it from the individual grant checkboxes below. */
.ins-admin-grants-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-bottom: 0.3rem;
    border-bottom: 1px solid color-mix(in srgb, var(--ins-border) 65%, transparent);
}

.ins-admin-grants-title {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ins-muted);
}

.ins-admin-grant-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 0.1rem 1rem;
    margin-top: 0.6rem;
    max-height: 220px;
    overflow-y: auto;
}

/* Administrator / AI Assistant each carry an explanatory hint, so their columns
   are wider than the bare titles in the dashboard and grid lists. */
.ins-admin-grant-list--wide {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
}

/* These columns have room, so the "via <group>" marker stays beside its label
   instead of being pushed to the far edge of the cell (the narrow dashboard and
   grid columns keep the grow-and-wrap behaviour below). */
.ins-admin-grant-list--wide .ins-admin-check--inherited > span:first-of-type {
    flex: 0 1 auto;
}

.ins-admin-grant-empty {
    color: var(--ins-muted);
    font-size: 0.84rem;
    margin: 0.2rem 0 0;
}

.ins-admin-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 1rem;
}

/* Compact, content-width save button (like "Save Dashboard") rather than the
   full-width login submit; align-self keeps it left in the flex-column form. */
.ins-admin-save {
    align-self: flex-start;
}

/* Clickable value inside an admin table/field row, e.g. a collection name. */
.ins-admin-link-value {
    color: var(--ins-primary);
    font-weight: 600;
    text-decoration: none;
    overflow-wrap: anywhere;
}

.ins-admin-link-value:hover {
    text-decoration: none;
    color: var(--ins-secondary);
}

/* Admin console shares the home page's comfortable max width on large monitors. */
.ins-main--admin {
    max-width: 1500px;
}

.ins-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.ins-admin-center {
    text-align: center !important;
}

.ins-admin-yes {
    color: var(--ins-success);
}

.ins-admin-dash {
    color: var(--ins-muted);
    opacity: 0.5;
}

/* ---------- Admin: Theme editor ---------- */
.ins-theme-heading {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ins-muted);
    margin: 1.3rem 0 0.7rem;
}

.ins-theme-heading:first-child {
    margin-top: 0;
}

/* Colors: a labelled column per role - name above a swatch chip - so each cell is
   self-contained and never runs into the next column. */
.ins-theme-colors {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.9rem 1rem;
    margin-bottom: 0.4rem;
}

.ins-color-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}

.ins-color-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--ins-text);
}

.ins-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-bottom: 0.4rem;
}

/* A swatch chip: color picker box + editable hex field, shared by colors,
   chart palette and accents. */
.ins-swatch {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.25rem 0.5rem 0.25rem 0.3rem;
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius-control);
    background: var(--ins-surface);
}

/* Uniform, rounded color box across browsers for the native picker. */
.ins-swatch-picker {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    width: 26px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--ins-border);
    border-radius: 6px;
    background: none;
    cursor: pointer;
    flex: 0 0 auto;
}

.ins-swatch-picker::-webkit-color-swatch-wrapper {
    padding: 2px;
}

.ins-swatch-picker::-webkit-color-swatch {
    border: none;
    border-radius: 4px;
}

.ins-swatch-picker::-moz-color-swatch {
    border: none;
    border-radius: 4px;
}

.ins-hex-input {
    width: 4.8rem;
    border: none;
    background: none;
    padding: 0;
    color: var(--ins-muted);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.78rem;
}

.ins-hex-input:focus {
    outline: none;
    color: var(--ins-text);
}

.ins-hex-input:invalid {
    color: var(--ins-danger);
}

.ins-theme-actions {
    margin-top: 1.4rem;
}

/* ---------------------------------------------------------------- AI page */

/* The assistant is an app shell: the page itself never scrolls - the transcript
   does - so the composer stays put at the foot of the viewport however long the
   conversation runs. dvh, not the shared vh, or mobile browser chrome pushes the
   composer under the fold. */
.ins-body--ai {
    height: 100dvh;
    min-height: 100dvh;
    overflow: hidden;
}

.ins-main--ai {
    max-width: 1200px;
    margin-left: auto;
    margin-right: auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* The /embed variant: the same shell inside a host application's iframe panel,
   where the surrounding chrome is the host's to spend. The template already
   skips our header and footer; what remains slims to fit a side panel - the
   page head becomes one compact row and the description goes (the host names
   the panel; a narrow column shouldn't open on a paragraph about it). */
.ins-body--embed .ins-main--ai {
    padding-top: 0.6rem;
}

.ins-body--embed .ins-ai-head {
    align-items: center;
}

.ins-body--embed .ins-page-head {
    margin-bottom: 0.4rem;
}

.ins-body--embed .ins-page-title {
    font-size: 1.05rem;
}

.ins-body--embed .ins-page-desc {
    display: none;
}

.ins-ai-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.ins-ai {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    flex: 1;
    min-height: 0;
}

/* Takes whatever height the composer leaves - no fixed reservation to re-tune
   as the draft grows. The bar comes from the shared "Scroll regions" section,
   which keeps its lane reserved outside the content so the thumb sits in its
   own column instead of painting over the right edge of the message cards; the
   right padding here is just the gap between a card and that lane. */
.ins-ai-transcript {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    overflow-y: auto;
    padding: 0.2rem var(--ins-scrollbar-gap) 1rem 0;
    flex: 1;
    min-height: 0;
}

.ins-ai-empty {
    margin: auto;
    text-align: center;
    color: var(--ins-muted);
    /* auto margins in the flex column would shrink this to its longest chip and
       collapse the starter grid to one column - claim the row, then cap it */
    width: 80%;
}

/* the big star only - chips carry their own, smaller icons */
.ins-ai-empty > .bi {
    font-size: 1.9rem;
    color: var(--ins-secondary);
}

.ins-ai-lead {
    font-size: 1rem;
    margin-bottom: 0;
}

/* Starters as a tidy grid of answer-type cards - a dozen chips wrap into even
   columns instead of the ragged pill rows a flex wrap made of them. */
.ins-ai-suggestions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 0.5rem;
    margin-top: 1rem;
    text-align: left;
}

/* Every collection's starters are in the page; only the picked one shows.
   Needed because the class above sets display, which beats the UA [hidden]. */
.ins-ai-suggestions[hidden] {
    display: none;
}

.ins-ai-suggestion {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    border: 1px solid var(--ins-border);
    border-radius: 10px;
    background: var(--ins-surface, #fff);
    color: var(--ins-text);
    font: inherit;
    font-size: 0.85rem;
    line-height: 1.35;
    padding: 0.5rem 0.7rem;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.15s ease, background-color 0.15s ease,
        transform 0.15s ease, box-shadow 0.15s ease;
}

.ins-ai-suggestion .bi {
    font-size: 0.95rem;
    color: var(--ins-primary);
    flex-shrink: 0;
}

.ins-ai-suggestion:hover {
    border-color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 5%, var(--ins-surface, #fff));
    transform: translateY(-1px);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--ins-primary) 10%, transparent);
}

.ins-ai-msg {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.ins-ai-msg--user {
    align-items: flex-end;
}

.ins-ai-msg--assistant {
    align-items: stretch;
}

/* The default cap keeps a bubble to a readable measure even though the page is
   wide; the assistant's answer relaxes it below (its charts sit at half beneath
   it, so the text can breathe wider). */
.ins-ai-bubble {
    border-radius: var(--ins-radius, 12px);
    padding: 0.6rem 0.85rem;
    max-width: min(85%, 72ch);
    font-size: 0.92rem;
    line-height: 1.45;
    white-space: pre-wrap;
}

.ins-ai-msg--user .ins-ai-bubble {
    background: color-mix(in srgb, var(--ins-primary) 12%, var(--ins-surface, #fff));
    border: 1px solid color-mix(in srgb, var(--ins-primary) 25%, transparent);
    align-self: flex-end;
}

.ins-ai-msg--assistant .ins-ai-bubble {
    background: var(--ins-surface, #fff);
    border: 1px solid var(--ins-border);
    align-self: flex-start;
    white-space: normal;
    /* Answers run wider than the reading-measure default - the page caps at
       1200px, so 75% stays bounded rather than sprawling on ultra-wide. */
    max-width: 80%;
}

.ins-ai-prose p { margin: 0 0 0.5rem; }
.ins-ai-prose p:last-child { margin-bottom: 0; }
.ins-ai-prose pre {
    background: color-mix(in srgb, var(--ins-text) 6%, transparent);
    border-radius: 8px;
    padding: 0.5rem 0.65rem;
    overflow-x: auto;
}
.ins-ai-prose ul, .ins-ai-prose ol { margin: 0 0 0.5rem; padding-left: 1.2rem; }

.ins-ai-bubble--error {
    border-color: color-mix(in srgb, var(--ins-danger) 45%, transparent);
    color: var(--ins-danger);
}

/* artifact cards fill the row under the assistant's prose */
.ins-ai-widget {
    max-width: 100%;
}

/* Charts don't need the whole row: a pie or a short bar/line reads better at
   about half width, left-aligned under the answer (the assistant column stretches
   its children, so the cap alone parks the card at the left edge). ai.js adds this
   to chart cards by default; it drops it when the model asks for a wide chart
   (width=full) and never adds it to the dense kinds. Falls back to full on narrow
   screens below. */
.ins-ai-widget--half {
    max-width: 55%;
}

/* Two half-width artifacts paired into one row. ai.js reuses the dashboard's
   .ins-row/.ins-row-N grid, so the responsive collapses (two columns under 1200px,
   one under 862px) come for free and the transcript can never drift from the
   dashboards.

   The max-width override is not optional: inside a grid cell that is already half
   the row, a further 55% cap would render the card at ~27% of the transcript - worse
   than the single-column stacking this replaces. The cap exists to stop a LONE card
   spanning the full width; in a pair the grid already does that job. Same reasoning,
   same fix as the pin columns at .ins-pin-item below. */
.ins-ai-row {
    width: 100%;
}

.ins-ai-row .ins-ai-widget--half {
    max-width: 100%;
}

/* A KPI strip is a band, not a card: if one ever lands in a row it spans it. */
.ins-ai-row > .ins-kpis {
    grid-column: 1 / -1;
}

/* Reserve room for the floating controls so a long title doesn't run under them.
   Scoped to the artifact CARD, not to .ins-ai-row: a lone artifact is pushed into
   the transcript unwrapped (buildArtifactRows only builds a row for two or more),
   so a row-scoped reserve missed every full-width card - which is how the switch
   ended up under the pin there (Vishal, 2026-07-31). The wider reserve applies
   only when a card carries the third control. */
.ins-ai-widget .ins-widget-head {
    padding-right: 4.75rem;
}

.ins-ai-widget .ins-widget-head:has(~ .ins-ai-toggle) {
    padding-right: 6.75rem;
}

/* The answer's one-line headline - the finding people read first, so it gets a
   callout treatment rather than a bare bold line: the page's own star icon and
   a 3px accent left border over the findings-box
   tint. Shrink-wraps to its sentence, capped at the answer bubble's own 75%
   measure so banner and bubble read as one column. */
.ins-ai-headline {
    display: flex;
    align-items: baseline;
    gap: 0.55rem;
    width: fit-content;
    max-width: 75%;
    margin: 0 0 0.45rem;
    padding: 0.6rem 0.9rem 0.6rem 0.75rem;
    border-left: 4px solid var(--ins-primary);
    border-radius: 4px;
    background: color-mix(in srgb, var(--ins-primary) 8%, var(--ins-surface, #fff));
    font-size: 1rem;
    font-weight: 650;
    line-height: 1.4;
    color: var(--ins-text);
}

.ins-ai-headline .bi {
    color: var(--ins-primary);
    font-size: 0.95rem;
    flex-shrink: 0;
}

/* The answer's scope - which window and filters the data actually ran under.
   Server-composed fact, styled as quiet metadata riding under the headline. */
.ins-ai-scope {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: -0.05rem 0 0.3rem;
    font-size: 0.78rem;
    color: var(--ins-muted);
}

.ins-ai-scope .bi {
    font-size: 0.72rem;
    flex-shrink: 0;
}

/* Interpretations the model adopted ("rate" read as volume): its own note in the
   partial-note's amber family, so a proxy reading is never mistaken for a data
   fact - but with an info icon, because nothing failed. */
.ins-ai-assumptions {
    display: flex;
    gap: 0.5rem;
    width: fit-content;
    max-width: 80%;
    padding: 0.5rem 0.8rem;
    border-radius: 10px;
    /*background: color-mix(in srgb, var(--ins-warning, #b8860b) 8%, var(--ins-surface));*/
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--ins-muted);
}

.ins-ai-assumptions > .bi {
    flex-shrink: 0;
    color: var(--ins-hint);
}

.ins-ai-assumptions-body p {
    margin: 0;
}

.ins-ai-assumptions-body p + p {
    margin-top: 0.25rem;
}

/* Recommended actions share the findings box's bones; the accent border says
   "do", not just "know". */
.ins-ai-actions {
    border-left: 3px solid var(--ins-primary);
}

/* A finding shown under the card it came from. Inside the card, so it travels into
   the PNG download and stays put when rows reflow. */
.ins-ai-caption {
    padding: 0 0.95rem 0.7rem;
    font-size: 0.82rem;
    line-height: 1.45;
    color: var(--ins-muted);
}

.ins-ai-caption p {
    margin: 0.35rem 0 0;
}

/* The findings that belong to no single card. */
.ins-ai-findings {
    margin-top: 0.2rem;
    padding: 0.75rem 0.95rem 0.85rem;
    border: 1px solid var(--ins-border);
    border-radius: 12px;
}

.ins-ai-findings-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0 0.4rem;
    font-size: 0.8rem;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ins-muted);
}

.ins-ai-findings-list {
    margin: 0;
    padding-left: 1.1rem;
    font-size: 0.88rem;
    line-height: 1.5;
}

.ins-ai-findings-list li {
    margin: 0.2rem 0;
}

/* Follow-ups are prompts, not answer content, and they say so twice: a muted
   "Ask next" mini-heading (the findings-head treatment) and GHOST chips -
   transparent, muted text - that the shared suggestion hover brings to life.
   The empty-state starters keep their solid cards: there they ARE the content. */
.ins-ai-next {
    margin-top: 0.35rem;
}

.ins-ai-next-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0;
    font-size: 0.8rem;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ins-muted);
}

/* The guidance sections' lead icons take the assumptions note's treatment - a
   hint-colored icon over quiet muted text - so the eye finds each section by its
   warm fleck while the words stay subdued. */
.ins-ai-scope .bi,
.ins-ai-findings-head .bi,
.ins-ai-next-head .bi {
    color: var(--ins-hint);
}

/* The chips reuse the empty state's .ins-ai-suggestions grid wholesale; its
   1rem margin-top is the empty state's spacing, not this section's. */
.ins-ai-followups {
    margin-top: 0.45rem;
}

.ins-ai-followups .ins-ai-suggestion {
    background: transparent;
    color: var(--ins-muted);
}

.ins-ai-followups .ins-ai-suggestion:hover {
    color: var(--ins-text);
    background: color-mix(in srgb, var(--ins-primary) 5%, var(--ins-surface, #fff));
}

/* The elapsed clock and the way out, beside the typing dots. Both wait out the
   first few seconds so a quick answer never flashes them - but invisibly
   (visibility, not display), keeping their space from the first frame: the row
   never grows on reveal, so the dots stay exactly where they started. ai.js adds
   ins-ai-typing--live once the wait is long enough to deserve them. */
.ins-ai-typing-meta {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    margin-left: 0.6rem;
    font-size: 0.78rem;
    color: var(--ins-muted);
}

.ins-ai-typing-meta,
.ins-ai-stop {
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.25s ease;
}

.ins-ai-typing--live .ins-ai-typing-meta,
.ins-ai-typing--live .ins-ai-stop {
    visibility: visible;
    opacity: 1;
}

/* The separator the rest of the app uses between peer facts ("1-10 of 120 records ·
   ask for more"), rather than a hyphen - a dash between a phrase and a number reads
   as a range or a minus sign. */
.ins-ai-typing-label::after {
    content: " ·";
}

/* The one thing on this row that changes, so it is the one thing given a fixed box:
   wide enough for "999s" (three digits outlasts any real wait), and tabular so 1 and
   7 occupy the same width. Without this the Stop button drifted on every tick, and
   again at each 10s/100s digit rollover. */
.ins-ai-elapsed {
    min-width: 1.9rem;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

.ins-ai-stop {
    margin-left: 0.5rem;
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--ins-border);
    border-radius: 999px;
    background: transparent;
    color: var(--ins-muted);
    cursor: pointer;
    font-size: 0.75rem;
    line-height: 1.5;
}

.ins-ai-stop:hover {
    color: var(--ins-text);
    border-color: var(--ins-muted);
}

/* An answer the Hub stopped short of finishing. Informational, not an error:
   everything it did build is rendered right above this. */
.ins-ai-partial {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.2rem;
    padding: 0.45rem 0.7rem;
    border-radius: 10px;
    background: color-mix(in srgb, var(--ins-warning, #b8860b) 10%, var(--ins-surface));
    color: var(--ins-muted);
    font-size: 0.82rem;
}

/* Pin control: a small button at an artifact card's top-right that opens an inline
   namer. The card is positioned so the control sits over its head without shifting
   layout; the KPI strip (no head) carries it in the same corner. */
.ins-ai-pinnable {
    position: relative;
}

.ins-ai-pin,
/* The "download this as a PNG" control, wherever a card offers one: floating on
   an AI artifact card, and riding at the end of a dashboard widget's head row.
   Same button, two placements - the appearance lives here once. */
.ins-ai-download,
.ins-ai-toggle,
.ins-widget-download,
.ins-widget-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    cursor: pointer;
    font-size: 0.9rem;
    line-height: 1;
    transition: color 0.15s ease, background 0.15s ease;
}

/* BOTH floating controls on an AI artifact card corner-anchor against the card
   (.ins-ai-pinnable is the positioned ancestor). The pin MUST be positioned here
   too: left in normal flow it becomes a flex item of whatever it was appended to
   and lands beside the content - on a KPI answer, right in the middle of the
   tile strip (Vishal, 2026-07-29). */
.ins-ai-pin,
.ins-ai-download,
.ins-ai-toggle {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 2;
}

/* The chart/table switch is the leftmost of the floating trio, so it steps aside
   for whichever of the other two the card actually carries (a pin only appears on
   a card with a re-runnable spec). Inserted BEFORE them in the DOM so these
   sibling tests can count what follows it. */
.ins-ai-toggle:has(~ .ins-ai-download) {
    right: 2.5rem;
}

.ins-ai-toggle:has(~ .ins-ai-download):has(~ .ins-ai-pin) {
    right: 4.5rem;
}

/* The widget head is a flex row (icon, title): the controls take the far end and
   never shrink, however long the title runs. On an AI or pinned card the head
   already reserves room on its right for the floating download/pin, so an in-head
   control lands just left of them without any positioning of its own. */
.ins-widget-download,
.ins-widget-toggle {
    margin-left: auto;
    flex: 0 0 auto;
}

/* Only the FIRST control pushes the group right - a second auto margin would
   share out the free space and split the pair apart. */
.ins-widget-toggle ~ .ins-widget-download {
    margin-left: 0;
}

/* Table view of a chart: capped at the height the chart occupied and scrolled
   internally, so switching never makes the dashboard row jump. The capture
   unfurls scrollers, so a downloaded image still carries every row. */
.ins-widget-table {
    max-height: 290px;
    overflow: auto;
}

/* A column of figures, header included: right-aligned on tabular digits so the
   numbers stack by place value and stay scannable down the column - the same
   treatment the home listings' number columns get. */
.ins-cell-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.ins-chart--tall + .ins-widget-table {
    max-height: 400px;
}

/* download sits left of the pin; on spec-less cards (no pin) it takes the corner */
.ins-ai-pin ~ .ins-ai-download,
.ins-ai-download:has(~ .ins-ai-pin) {
    right: 2.5rem;
}

/* A KPI answer is a bare tile strip with no head to hold the controls, so they
   would sit on the first row of tiles: give them a band of their own above it.
   Deep enough to clear a control outright - they start 0.5rem down and stand
   1.75rem tall, so anything under 2.25rem still clips the first row. */
.ins-ai-pinnable.ins-kpis {
    padding-top: 2.6rem;
}

.ins-ai-pin:hover,
.ins-ai-download:hover,
.ins-ai-toggle:hover,
.ins-ai-toggle:focus-visible,
.ins-widget-download:hover,
.ins-widget-download:focus-visible,
.ins-widget-toggle:hover,
.ins-widget-toggle:focus-visible {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 10%, var(--ins-surface));
}

.ins-ai-pin--done {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 12%, var(--ins-surface));
    cursor: default;
}

/* ---------- Pin Board (home page) ---------- */

/* Masonry: cards flow into columns and stack by height, so different-height cards
   leave no vertical gaps. Reordered by drag (pin-board.js) - the move lands on drop,
   so the columns don't churn under the pointer mid-drag. */
/* The board's one control, the column picker, is the shared segmented control
   (.ins-view-seg in the home toolbar section) - .ins-pin-col-btn survives only
   as pin-board.js's selector hook, with no styling of its own. */

/* JS-built columns in reading order (pin-board.js layout()): flex stacks instead of
   CSS multicol, so which column a card lands in is the user's order, never the
   browser's height balancing. */
.ins-pin-board {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    margin-top: 0.6rem;
}

.ins-pin-col {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.ins-pin-item {
    position: relative;
}

/* pinned cards fill their column, not the chat's half width */
.ins-pin-item .ins-ai-widget--half {
    max-width: 100%;
}

.ins-pin-item--dragging {
    opacity: 0.45;
}

.ins-pin-item--drop {
    outline: 2px dashed var(--ins-primary);
    outline-offset: 3px;
    border-radius: var(--ins-radius, 12px);
}

/* Two top-right controls: the grip handle and the "more" menu holding every
   other action (details, edit, download, remove) as named entries. */
.ins-pin-handle {
    position: absolute;
    top: 0.5rem;
    right: 2.5rem;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 8px;
    color: var(--ins-muted);
    cursor: grab;
}

.ins-pin-handle:hover {
    color: var(--ins-text);
}

.ins-pin-handle:active {
    cursor: grabbing;
}

/* The "more" menu: an anchor for the bootstrap dropdown carrying the pin's
   actions; the trigger wears the same quiet square the old icon row wore. */
.ins-pin-menu {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 2;
}

/* While its dropdown is open (class toggled by pin-board.js on bootstrap's
   dropdown events): the resting z-index of 2 caps the wrapper's stacking
   context, and the menu would paint UNDER the next card's controls, which sit
   at the same 2. */
.ins-pin-menu--open {
    z-index: 1050;
}

.ins-pin-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    cursor: pointer;
    font-size: 0.8rem;
    line-height: 1;
    transition: color 0.15s ease, background 0.15s ease;
}

.ins-pin-more:hover,
.ins-pin-more[aria-expanded="true"] {
    color: var(--ins-primary);
    background: color-mix(in srgb, var(--ins-primary) 10%, var(--ins-surface));
}

/* The menu's remove entry wears the app-wide danger tint (trash = delete). */
.ins-menu .ins-pin-remove,
.ins-menu .ins-pin-remove .bi {
    color: var(--ins-danger);
}

.ins-menu .ins-pin-remove:hover,
.ins-menu .ins-pin-remove:focus {
    background: color-mix(in srgb, var(--ins-danger) 10%, var(--ins-surface));
    color: var(--ins-danger);
}

/* reserve room for the two controls so a long title doesn't run under them */
.ins-pin-item .ins-widget-head {
    padding-right: 4.75rem;
}

/* The chart/table switch is NOT a floating control here: on a pinned card it is a
   line in the "more" menu beside the download, which is where this card's actions
   already live (Vishal, 2026-07-31). That keeps the card at two icons - the state
   it was deliberately reduced to - and leaves the head's full width to titles,
   which on pins are user-authored, long, and squeezed by a multi-column board.
   The menu's own .ins-menu .dropdown-item rules style it; it needs nothing here. */

/* The pin's label, riding beside the card title - the filter's visual cue.
   Inline (not pushed right): the floating controls own the head's right edge. */
.ins-pin-label {
    flex: 0 0 auto;
    margin-left: 0.5rem;
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--ins-border);
    border-radius: 999px;
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--ins-muted);
    background: var(--ins-background);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 9rem;
}


/* A board column is routinely narrower than a KPI tile's 180px minimum (four
   columns on a laptop leaves ~135px). A bare minmax(180px, 1fr) track cannot
   shrink past that floor, so the tiles overflowed the column and slid under the
   neighbouring cards - reading as "the other boxes vanished". min() lets the track
   fall back to the column's own width, so the tiles simply stack. */
.ins-pin-item .ins-kpis {
    grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
    gap: 0.5rem;
}

/* A pinned KPI is wrapped in a card like every other pin (pin-board.js), so its
   tiles now sit ON a surface - and a tile carries the same surface, border and
   shadow a card does, which would read as cards stacked on a card. Flatten them to
   softly tinted cells: the accent icon still marks each metric, and the rounded
   card outside is the one frame. */
.ins-pin-item .ins-widget-body > .ins-kpis .ins-kpi {
    background: color-mix(in srgb, var(--ins-text) 3.5%, transparent);
    border: 0;
    box-shadow: none;
    padding: 0.6rem 0.7rem;
}

/* Spec details dialog */
.ins-pin-spec-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgba(0, 0, 0, 0.35);
}

.ins-pin-spec {
    width: 100%;
    max-width: 560px;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    background: var(--ins-surface, #fff);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius, 12px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
    overflow: hidden;
}

.ins-pin-spec-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--ins-border);
}

.ins-pin-spec-title {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--ins-text);
}

.ins-pin-spec-close {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.8rem;
    height: 1.8rem;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--ins-muted);
    cursor: pointer;
}

.ins-pin-spec-close:hover {
    background: color-mix(in srgb, var(--ins-text) 8%, transparent);
    color: var(--ins-text);
}

.ins-pin-spec-body {
    padding: 0.25rem 1rem 1rem;
    overflow-y: auto;
}

.ins-pin-spec-row {
    padding: 0.6rem 0;
    border-bottom: 1px solid color-mix(in srgb, var(--ins-border) 60%, transparent);
}

.ins-pin-spec-row:last-child {
    border-bottom: none;
}

.ins-pin-spec-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ins-muted);
    margin-bottom: 0.25rem;
}

.ins-pin-spec-value {
    font-size: 0.9rem;
    color: var(--ins-text);
    word-break: break-word;
}

.ins-pin-spec-json {
    margin: 0;
    padding: 0.6rem 0.75rem;
    background: color-mix(in srgb, var(--ins-text) 6%, transparent);
    border-radius: 8px;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--ins-text);
    overflow-x: auto;
    white-space: pre;
}

/* The unpin form lives inside the "more" menu now - a plain wrapper around its
   Remove entry (the entry itself is a themed dropdown-item, danger-tinted above). */
.ins-pin-unpin {
    margin: 0;
}

/* No min-height on the body: the loading placeholder below carries its own, and
   once real content lands the card must hug it - a floor here left short content
   (a lone KPI tile) with invisible dead space that read as a broken gap. */
.ins-pin-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 220px;
    color: var(--ins-muted);
    font-size: 0.9rem;
    border: 1px dashed var(--ins-border);
    border-radius: var(--ins-radius, 12px);
}

.ins-pin-loading .bi {
    animation: ins-pin-spin 1s linear infinite;
}

@keyframes ins-pin-spin {
    to {
        transform: rotate(360deg);
    }
}

.ins-ai-grid-meta {
    margin-top: 0.45rem;
    font-size: 0.8rem;
    color: var(--ins-muted);
}

/* Grid detail layout: one label/value card per record - the record dialog's
   field styling (.ins-doc-*) inside a bordered card, stacked per record. */
.ins-ai-detail-card {
    background: var(--ins-surface);
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-border-radius);
    box-shadow: var(--ins-shadow-light);
    padding: 0.85rem 1rem 0.35rem;
    margin-bottom: 0.7rem;
}


/* Bare dots, no bubble - waiting is a status, not a message. The padding keeps
   them on the same left edge and rhythm as the answer that replaces them. */
.ins-ai-typing {
    display: inline-flex;
    gap: 0.3rem;
    align-items: center;
    align-self: flex-start;
    padding: 0.6rem 0.85rem;
}

.ins-ai-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ins-muted);
    animation: ins-ai-bounce 1.2s infinite;
}

.ins-ai-dot:nth-child(2) { animation-delay: 0.15s; }
.ins-ai-dot:nth-child(3) { animation-delay: 0.3s; }

@keyframes ins-ai-bounce {
    0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
    30% { transform: translateY(-4px); opacity: 1; }
}

/* One part of an answer entering the transcript. The answer arrives whole, but
   ai.js places its parts (headline, prose blocks, card rows, findings, chips)
   one by one so it reads as being written - this is each part's entrance. Only
   `from` is declared: the end state is the element's own, so the animation can
   never leave a lingering transform behind (the pin/download controls position
   against these cards). */
.ins-ai-enter {
    animation: ins-ai-enter 0.35s ease both;
}

@keyframes ins-ai-enter {
    from { opacity: 0; transform: translateY(8px); }
}

@media (prefers-reduced-motion: reduce) {
    .ins-ai-enter {
        animation: none;
    }
}

/* The row above the draft box, straight on the background: which data the
   conversation is about (icon + picker + live count) and the send-gesture hint.
   Pulling it out of the composer keeps the box itself a slim input strip. */
/* Right padding trails the transcript's scrollbar lane, so this row's controls
   stop where the message cards above them do. */
.ins-ai-context {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.3rem calc(0.7rem + var(--ins-scrollbar-inset)) 0.2rem 0;
}

.ins-ai-context-left {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

/* same accent-tinted square the widget heads use, so "this is the data" reads
   instantly next to the picker */
.ins-ai-collection-icon {
    width: 1.8rem;
    height: 1.8rem;
    display: grid;
    place-items: center;
    border-radius: 8px;
    background: color-mix(in srgb, var(--ins-primary) 12%, transparent);
    color: var(--ins-primary);
    flex-shrink: 0;
}

.ins-ai-collection-name {
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--ins-text);
}

.ins-ai-count {
    font-size: 0.78rem;
    color: var(--ins-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Just the draft and Send, side by side - all the chrome lives in the context
   row above, so the box stays light. Margin, not padding: the box has a border
   and a fill, so the inset has to move the whole box in to line its right edge
   up with the message cards above, which sit a scrollbar lane short of it. */
.ins-ai-composer {
    margin-right: var(--ins-scrollbar-inset);
    display: flex;
    /* center, not flex-end: with the 3-row starting height a bottom-hugging
       send button reads as misaligned (Vishal, 2026-07-24) */
    align-items: center;
    gap: 0.6rem;
    border: 1px solid var(--ins-border);
    border-radius: var(--ins-radius, 12px);
    background: var(--ins-surface, #fff);
    padding: 0.55rem 0.6rem 0.55rem 0.8rem;
}

/* The textarea drops its own outline, so the whole box carries the focus ring */
.ins-ai-composer:focus-within {
    border-color: var(--ins-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ins-primary) 12%, transparent);
}

.ins-ai-input {
    flex: 1;
    border: none;
    resize: none;
    background: transparent;
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.5;
    padding: 0.15rem 0.1rem;
    color: var(--ins-text);
}

.ins-ai-input:focus {
    outline: none;
}

.ins-ai-collection {
    max-width: 260px;
}

.ins-ai-collection--single {
    display: none;
}

.ins-ai-hint {
    font-size: 0.75rem;
    color: var(--ins-muted);
}

.ins-ai-reset {
    white-space: nowrap;
    margin-right: var(--ins-scrollbar-inset);
}

/* Send as a compact round action pinned to the draft's end */
.ins-ai-send {
    width: 2.4rem;
    height: 2.4rem;
    padding: 0;
    border-radius: 50%;
    display: grid;
    place-items: center;
    flex-shrink: 0;
}

.ins-ai-send:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* The send hint and the AI-reliance info icon sit together at the right of the
   context row. */
.ins-ai-meta {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

/* The reliance note (what the Terms' AI-content section points at) lives behind
   this info icon instead of a standing line under the composer - present, out of
   the way. */
.ins-ai-info {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ins-hint);
    cursor: help;
    font-size: 0.8rem;
    line-height: 1;
}

.ins-ai-info:hover,
.ins-ai-info:focus-visible {
    color: var(--ins-hint);
}

/* The bubble: revealed on hover and on focus (a click/tap focuses the button, so
   touch gets it too). Right-anchored so it never runs off the page edge; dark
   surface for contrast in either theme. */
.ins-ai-info-tip {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    width: max-content;
    max-width: 320px;
    padding: 0.5rem 0.65rem;
    border-radius: 8px;
    background: var(--ins-text);
    color: var(--ins-surface, #fff);
    font-size: 0.72rem;
    line-height: 1.4;
    text-align: left;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
    opacity: 0;
    visibility: hidden;
    transform: translateY(3px);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
    z-index: 5;
    pointer-events: none;
}

.ins-ai-info:hover .ins-ai-info-tip,
.ins-ai-info:focus .ins-ai-info-tip {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Phones: the hint costs a line it cannot spare, and Enter is not the gesture */
@media (max-width: 640px) {
    .ins-ai-hint {
        display: none;
    }

    /* No room to go half here - a chart at 55% of a phone is unreadable. */
    .ins-ai-widget--half {
        max-width: 100%;
    }

    /* The page head un-flexes: side by side, the New Conversation button was
       squeezing the title and description into a half-width column that ran
       five lines deep before the chat began. The button shrinks to its icon
       in the top corner (the label span steps aside), the title keeps clear
       of it, and the description gets the full measure back. */
    .ins-ai-head {
        display: block;
        position: relative;
    }

    /* BOTH lines keep clear of the floating corner button - the title alone
       was reserved at first and the description's opening line ran on under
       the button (the reset icon sat on "…a quick number", unreadable).
       3.5rem clears the 40px button plus the scrollbar-lane margin it keeps
       on lane-bearing (non-touch) narrow windows. */
    .ins-ai-head .ins-page-title,
    .ins-ai-head .ins-page-desc {
        padding-right: 3.5rem;
    }

    .ins-ai-reset {
        position: absolute;
        top: 0;
        right: 0;
        width: 40px;
        padding: 0;
        justify-content: center;
    }

    .ins-ai-reset .ins-ai-reset-label {
        display: none;
    }

    /* On this page the head never scrolls away (only the transcript scrolls),
       so every description line is permanent chrome - two lines is the budget. */
    .ins-ai-head .ins-page-desc {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    /* A phone column IS the readable measure - the desktop caps that keep an
       answer off ultra-wide sprawl only waste width here. The user's own
       bubble keeps its 85%: the indent is what tells the two voices apart. */
    .ins-ai-msg--assistant .ins-ai-bubble,
    .ins-ai-headline,
    .ins-ai-assumptions {
        max-width: 100%;
    }

    /* The empty state claims the whole column (its 80% cap was styling for
       the wide page) and breathes a little less vertically. */
    .ins-ai-empty {
        width: 100%;
    }

    .ins-ai-suggestions {
        gap: 0.45rem;
    }

    /* Context row: the collection field yields with an ellipsis instead of
       shoving the record count and info dot off the edge. */
    .ins-ai-context .ins-home-tool-field {
        min-width: 0;
        flex: 0 1 auto;
    }

    .ins-ai-context .ins-select-toggle {
        max-width: 100%;
    }

    .ins-ai-collection {
        max-width: 100%;
    }

    /* Home-indicator clearance when the app runs edge-to-edge (standalone /
       viewport-fit=cover); resolves to the same 0.5rem everywhere else. */
    .ins-main--ai {
        padding-bottom: max(0.5rem, env(safe-area-inset-bottom));
    }
}

/* ---------- Touch-first form controls ----------
   iOS Safari zooms the page in when a focused control's computed font-size is
   under 16px - and never zooms back out on its own. That "everything is
   suddenly cropped" jolt was most of what made the chat feel broken on a
   phone: the composer sits at 14.7px. Touch-first devices get 16px controls
   across the app; desktop keeps its compact sizes, where focus never zooms.
   (Buttons and the enhanced select TOGGLES are exempt - zoom only bites
   text-entry controls and native selects.) */
@media (hover: none) and (pointer: coarse) {
    .ins-ai-input,
    .ins-login-input input,
    .ins-search-box input[type="search"],
    .ins-home-search-input,
    .ins-save-desc,
    .ins-hash-result,
    .ins-date-field input[type="date"],
    .ins-date-field input[type="number"],
    .ins-date-field input.ins-date-input,
    .ins-date-field select,
    .ins-dash-select,
    .ins-home-tool-select,
    .ins-flow-level {
        font-size: 1rem;
    }
}
