/* ============================================================================
 * Integrations page — rebuilt 2026-08-31 (3.8.7, branch 3.8.7INT).
 *
 * Replaces `integrations.css`, which was 2,013 lines, of which 161 of 247 rules
 * were `.int-modal` descendants and 148 lines were dark-mode overrides on an app
 * that is hard light-locked. It carried nine corner radii, sixteen font sizes,
 * thirty padding values and three uncoordinated palettes — including status
 * accents that matched nothing in `:root` or the `--ipi-*` set.
 *
 * WHAT THIS FILE OWNS: the page shell, the section-rail glue, the phone drawer
 * and the Overview card grid — plus three small primitives the `cfg-*` language
 * genuinely lacks (a provider logo plate, a persistent connection-state chip,
 * and permission chips).
 *
 * WHAT IT DOES NOT OWN: anything inside a section. `settings-tabs.css` owns that,
 * in the `cfg-*` language, and `.integrations-section` was added to its six
 * scoping selectors so the whole family applies here unchanged.
 *
 * ⛔ THE SIX RULES THIS FILE HOLDS ITSELF TO, from settings-tabs.css:
 *   1. No ID selectors.
 *   2. No colour literals — every value a token, so all five palettes work.
 *   3. One breakpoint: 900px.
 *   4. ⚠ PARTIALLY. The `--cfg-*` spacing and radius scales are used INSIDE a
 *      section, because `settings-tabs.css:43` scopes them to
 *      `.settings-section, .integrations-section, .set-modal`. Everything outside a
 *      section — the card grid, the navbar, the rail sub-elements — is out of that
 *      scope and uses literal px, which is why this file still has ~30 of them. An
 *      earlier revision of this header claimed the rule was held outright; it is not,
 *      and for half the file it could not be without redeclaring the tokens, which
 *      `settings-tabs.css:29` explicitly warns pins the warm palette.
 *   5. Never `--ipi-ink-4` or `--ipi-accent` for body text — measured 3.1:1 and
 *      2.74:1, both fail WCAG AA. Gold is a mark and a border, never a label.
 *   6. No dark-mode rules. Out of scope by standing project rule.
 * ========================================================================= */

/* ===========================================================================
 * 1. Page shell
 * ======================================================================== */

.integrations-flash {
  margin-bottom: 16px;
}

/* The pane column. One section is visible at a time once the rail has taken
 * over; with no JS every section is visible and this stacks them, which is what
 * /demo/integrations and a no-JS visitor get. */
/* 🔴 THE SUPPORT FAB SITS OVER THE ACTION BAR. `layout/app.php` renders a fixed
 * "Need help?" button in the bottom-right corner on every tenant page, and a
 * section's `cfg-actions` puts its primary button in exactly that corner — so on
 * a short section (a disconnected provider is short by design) Connect was
 * underneath it at rest.
 *
 * ⚠ CORRECTED after review: the FAB is **125 × 38px** and sits **26px** from the bottom at
 * 1440 (16px at 390) — `style.css:6878`. An earlier revision of this comment said "44px tall,
 * 20px from the bottom", which was not measured despite saying it was. The 56px here plus
 * `ipi-list-area`’s own 28px still clears it comfortably, so the value stands and only the
 * reasoning was wrong. This is the fourth bottom-anchored collision found on this project by
 * driving a page rather than reading it — anything new anchored to the bottom must be
 * measured, and the measurement written down honestly. */
.integrations-sections__panes {
  display: grid;
  gap: 18px;
  min-width: 0;
  align-content: start;
  padding-bottom: 56px;
}

/* A section is a panel with a form-shaped body, so it gets more room than the
 * 16px `.ipi-panel` default. Same value Settings uses, deliberately — these two
 * pages sit next to each other in the sidebar. */
.integrations-section {
  padding: 20px 22px;
}

/* ⛔ THE OVERVIEW SECTION HAS NO ACTION BAR, so its body must not reserve room
 * for one. Without this the grid sits above 40-odd pixels of nothing. */
.integrations-section[data-integrations-section='overview'] .cfg-body {
  padding-bottom: 0;
}

/* The strip sits between the page header and the rail, exactly as it does on
   Settings and Billing. `ipi-stats-strip` owns the grid; this is only the gap to
   the rail below it. */
.integrations-stats-strip {
  margin-bottom: 18px;
}

/* ⛔ THE LOGO IS A HEAD CHILD, NOT A TOOL, and adding a third child to `cfg-head`
 * needs this rule to go with it.
 *
 * 🔴 `cfg-head` is `justify-content: space-between` and `.cfg-head__copy` sets
 * only `min-width: 0` — no `flex-grow`. With two children that is correct: copy
 * left, tools right. With THREE it pushes all three apart, and the logo ended up
 * alone at the far left with ~280px of empty space before the title. The copy has
 * to claim the free space itself. */
.integrations-head {
  align-items: flex-start;
  gap: 14px;
}

.integrations-head .cfg-head__copy {
  flex: 1 1 auto;
}

.integrations-head .cfg-head__tools {
  align-items: flex-start;
}

/* ===========================================================================
 * 2. The rail's two new sub-elements
 *
 * `ipi-sidenav__dot`, `__item`, `__group` and `__label` are the shell's and are
 * used unchanged. These two are not in the shell because Settings and Security
 * had no per-item label element and no count.
 * ======================================================================== */

/* The item is `justify-content: space-between` in the shell, which pushes a
 * lone label to the right when a dot precedes it. The label takes the free
 * space instead. */
.ipi-sidenav__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Permanently-failed jobs. The only number on the rail, so it has to read as a
 * count rather than a badge — the page already uses pills for state. */
.ipi-sidenav__count {
  flex: 0 0 auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--ipi-red-bg);
  color: var(--ipi-red);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* ===========================================================================
 * 3. Phone section navigation
 *
 * Below 900px the shell turns `.ipi-sidenav` into a horizontal chip scroller
 * with the group labels hidden. That is right for a handful of filter chips and
 * wrong for eight grouped connectors carrying status dots — the dots lose their
 * headings and the strip runs off the side.
 *
 * `tnt-filter-drawer` is the canonical tenant answer (37 views use it) and needs
 * no drawer CSS of its own: the primitive is `display: contents` above 900px, so
 * the SAME `.ipi-sidenav` markup is the desktop rail and the sheet's contents.
 * All this block does is undo the shell's chip-strip rules inside the sheet.
 *
 * ⚠ COPIED FROM settings.css DELIBERATELY, NOT SHARED. Extracting it would put a
 * fourth file in the load order of two pages to save ~40 lines, and the two
 * pages' rails differ in what they contain. If a third page grows a rail, share it then.
 * ======================================================================== */

.integrations-navbar {
  display: none;
}

@media (max-width: 900px) {
  .integrations-navbar:not([hidden]) {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 16px;
    margin-bottom: 16px;
    background: var(--ipi-bg-card);
    border-radius: 12px;
    box-shadow: var(--ipi-shadow-card);
  }

  .integrations-navbar .ipi-filter-rail__copy {
    min-width: 0;
    flex: 1 1 auto;
  }

  .integrations-navbar .ipi-filter-rail__meta {
    font-size: 14px;
    font-weight: 600;
    color: var(--ipi-ink);
  }

  .integrations-navbar .ipi-filter-rail__actions {
    flex-shrink: 0;
  }

  /* Inside the sheet the rail is a vertical grouped list again — the desktop
     shape, not the chip strip the shell switches to at this width. */
  .integrations-navdrawer .ipi-sidenav {
    display: block;
    position: static;
    max-height: none;
    overflow: visible;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    -webkit-mask-image: none;
    mask-image: none;
  }

  .integrations-navdrawer .ipi-sidenav__group {
    display: block;
    padding: 10px 0;
  }

  .integrations-navdrawer .ipi-sidenav__group + .ipi-sidenav__group {
    border-top: 1px solid var(--ipi-rule);
    border-left: 0;
  }

  .integrations-navdrawer .ipi-sidenav__label {
    display: block;
  }

  /* 44px: the tap-target floor. The shell's chip height is smaller because a
     chip strip is scrolled, not aimed at. */
  .integrations-navdrawer .ipi-sidenav__item {
    width: 100%;
    flex: 0 0 auto;
    white-space: normal;
    border-left: 2px solid transparent;
    border-radius: 0;
    min-height: 44px;
  }

  .integrations-section {
    padding: 16px;
  }
}

/* ===========================================================================
 * 4. The Overview grid
 *
 * The one part of the old stylesheet worth keeping. Trimmed from 192 lines to
 * this: the five status modifiers each set a border tint from a token instead of
 * three bespoke rgba() accents that matched nothing else in the app.
 * ======================================================================== */

.integrations-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
}

.integration-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
  background: var(--ipi-bg-card);
  border: 1px solid var(--ipi-rule);
  border-radius: 12px;
  box-shadow: var(--ipi-shadow-sm);
}

.integration-card--connected {
  border-color: var(--ipi-green);
}

.integration-card--needs_setup {
  border-color: var(--ipi-accent-border);
}

.integration-card--needs_reauth,
.integration-card--error,
.integration-card--restricted {
  border-color: var(--ipi-red-border);
}

.integration-card__head {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.integration-card__logo-box {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  background: var(--ipi-bg-inset);
  border-radius: 12px;
}

.integration-card__logo {
  width: 34px;
  height: 34px;
  object-fit: contain;
}

.integration-card__copy {
  min-width: 0;
  flex: 1 1 auto;
}

.integration-card__category {
  margin: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* ⛔ NOT --ipi-accent. Gold on card white measures 2.74:1 and fails AA; this
     eyebrow was gold in the old file at 10px, which is the worst combination of
     the two. */
  color: var(--ipi-ink-3);
}

/* ⛔ NO `flex-wrap` HERE. The row holds the one-word category and the status
   pill, both of which are short — wrapping is what made the Stripe card a
   different height from its six neighbours. */
.integration-card__title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 20px;
}

/* ⚠ Neither of these may wrap: a two-line pill made one card taller than its six
   neighbours. The card uses a SHORTER status wording than the section head to make
   that possible — see `$cardStatusLabel` in _cards.php. Truncating the category
   instead was tried and produced "ACCO…", which is worse than the wrap. */
.integration-card__status {
  flex: 0 0 auto;
  white-space: nowrap;
}

.integration-card__category {
  white-space: nowrap;
}

.integration-card__name {
  margin: 5px 0 0;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ipi-ink);
}

.integration-card__description,
.integration-card__meta {
  margin: 6px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ipi-ink-3);
}

.integration-card__meta-block {
  padding-top: 12px;
  border-top: 1px solid var(--ipi-rule);
}

.integration-card__error {
  margin: 6px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ipi-red);
  overflow-wrap: anywhere;
}

/* Pushes the footer down so every card in a row ends level, whatever its meta
   block holds. Without it a card with no sync time floats its button mid-air. */
.integration-card__footer {
  margin-top: auto;
  display: flex;
  gap: 8px;
}

/* ===========================================================================
 * 5. The three genuinely new primitives
 *
 * `cfg-*` has no image element, no persistent state chip and no chip list.
 * ⚠ `.cfg-logo` EXISTS and is NOT this: it is the workspace-branding upload
 * plate — `min-height: 132px`, `max-width: 320px`, images up to 200×100.
 * ======================================================================== */

/* 48px, matching the weight the card grid gives a connector at 56px. At the 38px
   it started life as, sitting to the right of the status chip, a provider's mark
   read as a stray coloured dot. */
.int-logo {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  background: var(--ipi-bg-inset);
  border: 1px solid var(--ipi-rule);
  border-radius: var(--cfg-r-box, 12px);
}

.int-logo__img {
  width: 30px;
  height: 30px;
  object-fit: contain;
}

/* The PERSISTENT connection state, in the section head.
 * ⛔ NOT the same thing as `.cfg-status`, which is a post-action report with
 * `aria-live="polite"`. Conflating the two is what left the old page with twelve
 * flash strips saying two unrelated kinds of thing. */
.int-state {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.int-state--ok {
  background: var(--ipi-green-bg);
  color: var(--ipi-green);
}

.int-state--todo {
  background: var(--ipi-accent-bg);
  /* The accent as a TEXT colour fails AA, so the amber state borrows the ink
     ramp and lets the background carry the signal. */
  color: var(--ipi-ink-2);
}

.int-state--bad {
  background: var(--ipi-red-bg);
  color: var(--ipi-red);
}

.int-state--mut {
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-3);
}

.int-scopes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}

.int-scopes__item {
  padding: 3px 9px;
  border: 1px solid var(--ipi-rule);
  border-radius: 999px;
  background: var(--ipi-bg-inset);
  font-size: 11.5px;
  color: var(--ipi-ink-2);
  overflow-wrap: anywhere;
}

/* ===========================================================================
 * 6. Section content that is integrations-specific
 * ======================================================================== */

/* Job and export-run state. Small enough that a `.ipi-status-pill` would be
   oversized inside a list row. */
.int-pill {
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.int-pill--ok {
  background: var(--ipi-green-bg);
  color: var(--ipi-green);
}

.int-pill--wait {
  background: var(--ipi-blue-bg);
  color: var(--ipi-blue);
}

.int-pill--bad {
  background: var(--ipi-red-bg);
  color: var(--ipi-red);
}

.int-pill--mut {
  background: var(--ipi-bg-inset);
  color: var(--ipi-ink-3);
}

/* Numbered because these ARE a sequence — you cannot choose an organisation
   before authorising. The marker is drawn rather than left to `list-style` so it
   aligns with the text baseline at any wrap. */
.int-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

.int-steps__item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ipi-ink-2);
}

.int-steps__item + .int-steps__item {
  margin-top: 10px;
}

.int-steps__num {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--ipi-bg-inset);
  border: 1px solid var(--ipi-rule);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  /* ⛔ Not --ipi-accent: gold on the inset ground measures below AA at 11px. */
  color: var(--ipi-ink-2);
}

.int-steps__text {
  min-width: 0;
}

/* What a connector does, before you connect it. A ticked list rather than
   bullets, because every line is something that WILL happen. */
.int-syncs {
  margin: 0;
  padding: 0;
  list-style: none;
}

.int-syncs__item {
  position: relative;
  padding-left: 26px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ipi-ink-2);
}

.int-syncs__item + .int-syncs__item {
  margin-top: 8px;
}

.int-syncs__item::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 6px;
  width: 10px;
  height: 6px;
  border-left: 1.6px solid var(--ipi-green);
  border-bottom: 1.6px solid var(--ipi-green);
  transform: rotate(-45deg);
}

/* The per-job error expander. Xero had this and the other four did not — they
   dumped the untruncated error inline, which made a failed row four lines tall. */
.int-job__toggle {
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--ipi-ink-2);
  text-decoration: underline;
  cursor: pointer;
}

.int-job__error {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ipi-red);
  overflow-wrap: anywhere;
}

/* Shown while a section's real controls are still loading. Matches the control
   height exactly so the row does not jump when the select replaces it. */
.int-control-placeholder {
  display: flex;
  align-items: center;
  height: var(--cfg-control-h, 40px);
  padding: 0 12px;
  border: 1px dashed var(--ipi-rule-h);
  border-radius: var(--cfg-r-control, 8px);
  background: var(--ipi-bg-inset);
  font-size: 13px;
  color: var(--ipi-ink-3);
}

.int-sync-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Export dataset rows. A checkbox row inside `cfg-list`, so the container's
   borders and rules are already right — this aligns the control and, critically,
   makes the row's two lines stack.
   🔴 `cfg-list__title` and `__meta` are `<p>` in a settings list and BLOCK by
   default; here they are spans inside a `<label>`, so they ran together and every
   row read "ClientsClient records and contact details." */
.int-dataset {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
}

.int-dataset input {
  margin-top: 3px;
  flex: 0 0 auto;
}

.int-dataset .cfg-list__main,
.int-dataset .cfg-list__title,
.int-dataset .cfg-list__meta {
  display: block;
  min-width: 0;
}

.int-dataset .cfg-list__main {
  flex: 1 1 auto;
}

.int-dataset .cfg-list__meta {
  margin-top: 2px;
}

/* ⚠ The head row inherits `cfg-list__head`'s tinted ground, which on a row that
   also carries a checkbox read as "this row is selected". Neutralise it: this is
   a control row, not a heading. */
.cfg-list__head:has(.int-dataset) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  background: var(--ipi-bg-card);
}

.int-dataset--all {
  font-size: 13px;
  font-weight: 600;
  color: var(--ipi-ink);
  text-transform: none;
  letter-spacing: 0;
}

.int-dataset--raw {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--ipi-ink-2);
  text-transform: none;
  letter-spacing: 0;
}

/* ===========================================================================
 * 7. Small gaps in the borrowed `cfg-*` set
 *
 * Three classes this page's markup uses have no rule in `settings-tabs.css` —
 * they were written here expecting one. Harmless defaults today, but a
 * `cfg-danger__copy` with no `flex` was clearly meant to share a row.
 * ======================================================================== */

.cfg-danger__copy,
.cfg-toolbar__copy {
  flex: 1 1 auto;
  min-width: 0;
}

/* `cfg-grid` is already one column by default; naming the modifier keeps the
   HubSpot pipeline row explicit rather than relying on that default. */
.cfg-grid--single {
  grid-template-columns: minmax(0, 1fr);
}
