/* No local() on any face: it is tried first, so an installed copy would replace ours. */
@font-face {
  font-family: "Toxigenesis";
  src: url("/static/fonts/Toxigenesis.44cfdead3442.otf") format("truetype");
}

/* CORE brand stack: Rajdhani headings, Inter body. */
@font-face {
  font-family: "Rajdhani";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/Rajdhani-Medium.4883a1f59937.woff2") format("woff2");
}

@font-face {
  font-family: "Rajdhani";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/Rajdhani-SemiBold.989e746dc103.woff2") format("woff2");
}

@font-face {
  font-family: "Rajdhani";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/Rajdhani-Bold.83636f24aacb.woff2") format("woff2");
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("/static/fonts/Inter-Light.3e0bae7d380a.woff2") format("woff2");
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/Inter-Regular.cef6d3a6737e.woff2") format("woff2");
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/Inter-Medium.861c7a6706fe.woff2") format("woff2");
}

/* Inter 600/700 exist so Bootstrap's .fw-bold text gets a real cut, not synthetic bold. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/Inter-SemiBold.ca03b7ee20d3.woff2") format("woff2");
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/Inter-Bold.64d12f9f7d82.woff2") format("woff2");
}

/* Base UI face: Inter is the document default. There was no baseline at all before this. */
:root {
  --bs-body-font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-family: var(--bs-body-font-family);
}

/* Headings take Rajdhani. Element selector (0,0,1), so an explicit font class still wins. */
h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: "Rajdhani", sans-serif;
}

/* Widget internals Reboot's inherit misses. Separate rules: one bad selector voids the list. */
option {
  font-family: inherit;
}

::file-selector-button {
  font-family: inherit;
}

/* WebKit paints a date input's value here; sub-fields listed as some engines skip them. */
::-webkit-datetime-edit,
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-year-field {
  font-family: inherit;
}

/* P step — paragraph text. Element selector, so a <p> that opts into a heading class keeps it. */
p {
  font-family: "Inter", sans-serif;
  font-size: 18px;
  font-weight: 400;
}
/* Muted text is the Small Body step. Headings excluded — there muted is only a colour. */
.text-muted:not(:where(h1, h2, h3, h4, h5, h6)) {
  font-family: "Inter", sans-serif;
  font-size: 15px;
  font-weight: 300;
}
/* Form controls are body text, so a field is immune to its container. Family only. */
.form-control,
.form-select,
textarea {
  font-family: "Inter", sans-serif;
}

/* Badge type comes from the CTA rule below; --bs-badge-* vars are cleared so none goes stale. */
.badge {
  --bs-badge-font-size: inherit;
  --bs-badge-font-weight: inherit;
}

/* CORE typography scale — one class per specimen step. `.type-*` avoids Bootstrap's .h1-.h6. */

/* H1 — the specimen's top step, applied to NOTHING: 65px was too large in the layout. */
.type-h1 {
  color: #FFF;
  font-family: "Rajdhani", sans-serif;
  font-size: 65px;
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

/* H2 — page sticky headers. */
.type-h2 {
  color: #FFF;
  font-family: "Rajdhani", sans-serif;
  font-size: 45px;
  font-style: normal;
  font-weight: 600;
  line-height: normal;
}

/* H3 — card titles. `.card-title` is grouped in: the only H3 surface, and the rules matched. */
.type-h3,
.card-title {
  color: #FFF;
  font-family: "Rajdhani", sans-serif;
  font-size: 36px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
}

/* H4 — the only step that is set in caps. */
.type-h4 {
  color: #FFF;
  font-family: "Rajdhani", sans-serif;
  font-size: 24px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
  text-transform: uppercase;
}

/* H6 — INFERRED, not in the specimen (which stops at H4). Group headings in narrow side panels. */
.type-h6 {
  color: #FFF;
  font-family: "Rajdhani", sans-serif;
  font-size: 16px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
}

/* P — form labels, inputs, placeholders, descriptions, notifications, message bodies. */
.type-p {
  color: #FFF;
  font-family: "Inter", sans-serif;
  font-size: 18px;
  font-style: normal;
  font-weight: 400;
  line-height: normal;
}

/* Small Body — secondary detail: phone numbers, emails, addresses in contact info. */
.type-small-body {
  color: #FFF;
  font-family: "Inter", sans-serif;
  font-size: 15px;
  font-style: normal;
  font-weight: 300;
  line-height: normal;
}

/* File names — its own step between H3 and H4. Left-aligned so it cannot inherit centring. */
.type-filename {
  color: #FFF;
  text-align: left;
  font-family: "Rajdhani", sans-serif;
  font-size: 21px;
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

/* `.type-capability-title` is retired — that title is back on `card-title fs-1`. */

/* Small Body at the list's 14px. No `color`, so .text-muted can grey a deactivated member. */
.member-list-name {
  font-family: "Inter", sans-serif;
  font-size: 14px;
  font-style: normal;
  font-weight: 300;
  line-height: normal;
}

/* Date pills — type only; the specimen's fixed 132x17 box is dropped (text varies). */
/* The .badge compound is load-bearing: .badge is in the later CTA rule and would win the tie. */
.type-date-pill,
.badge.type-date-pill {
  color: var(--Color-brand-Ion-Orange, #FF9500);
  font-family: "Inter", sans-serif;
  font-size: 15px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
}

/* Density modifier: the compact `task` variant sits in a tight table row. */
.type-date-pill.type-date-pill--sm,
.badge.type-date-pill.type-date-pill--sm {
  font-size: 11px;
}

[data-bs-theme=dark] {
  color-scheme: dark;

  --bs-body-color: #D9D9D9;
  --bs-body-color-rgb: 217, 217, 217;
  --bs-body-bg: #222832;
  --bs-body-bg-rgb: 34, 40, 50;
  --bs-emphasis-color: #FFFFFF;
  --bs-emphasis-color-rgb: 255, 255, 255;
  --bs-secondary-color: rgb(176, 176, 176);
  --bs-secondary-color-rgb: 176, 176, 176;
  --bs-tertiary-color: rgba(176, 176, 176, 0.55);
  --bs-tertiary-color-rgb: 176, 176, 176;
  --bs-heading-color: inherit;
  --bs-secondary-bg: #343942;
  --bs-secondary-bg-rgb: 52, 57, 66;
  --bs-tertiary-bg: #32353c;
  --bs-tertiary-bg-rgb: 50, 53, 60;
  --bs-border-color: #495057;
  --bs-border-color-translucent: rgba(255, 255, 255, 0.12);
  --bs-primary: #29bab2;
  --bs-primary-rgb: 41, 186, 178;
  --bs-info: #348ae5;
  --bs-info-rgb: 52, 138, 229;
  --bs-dark: #141920;
  --bs-dark-rgb: 20, 25, 32;
  --bs-link-color: #29BAB2;
  --bs-link-color-rgb: 41, 186, 178;
  --bs-link-hover-color: #4AD1C9;
  --bs-link-hover-color-rgb: 74, 209, 201;
  --bs-primary-text-emphasis: #7FE5DF;
  --bs-secondary-text-emphasis: #D0D0D0;
  --bs-info-text-emphasis: #8AA3FF;
  --bs-success: #2FBF71;
  --bs-success-text-emphasis: #7EE3A4;
  --bs-warning: #F19415;
  --bs-warning-text-emphasis: #FFD27A;
  --bs-danger: #E05D6F;
  --bs-danger-text-emphasis: #FF9AA3;
  --bs-light-text-emphasis: #F8F9FA;
  --bs-dark-text-emphasis: #D9D9D9;
  --bs-primary-bg-subtle: #0B3432;
  --bs-secondary-bg-subtle: #2A303D;
  --bs-success-bg-subtle: #0B2A1A;
  --bs-info-bg-subtle: #060C4A;
  --bs-warning-bg-subtle: #33260D;
  --bs-danger-bg-subtle: #3A1216;
  --bs-light-bg-subtle: #222836;
  --bs-dark-bg-subtle: #0B0F1A;
  --bs-primary-border-subtle: #1F8F89;
  --bs-secondary-border-subtle: #41464b;
  --bs-success-border-subtle: #2F8F57;
  --bs-info-border-subtle: #4C5FE3;
  --bs-warning-border-subtle: #8A6B1F;
  --bs-danger-border-subtle: #B23A45;
  --bs-light-border-subtle: #495057;
  --bs-dark-border-subtle: #222836;
  --bs-code-color: #FF7AD9;
  --bs-highlight-color: #D9D9D9;
  --bs-highlight-bg: #1B255E;
  --bs-form-control-bg: #22262b;
  --bs-form-select-bg: #222836;
  --bs-form-control-color: #D9D9D9;
  --bs-form-control-border-color: rgba(255, 255, 255, 0.16);
  --bs-form-control-placeholder-color: rgba(176, 176, 176, 0.55);
  --bs-focus-ring-width: 0.25rem;
  --bs-focus-ring-color: rgba(41, 186, 178, 0.35);
  --bs-form-valid-color: #2FBF71;
  --bs-form-valid-border-color: #2FBF71;
  --bs-form-invalid-color: #E05D6F;
  --bs-form-invalid-border-color: #E05D6F;
  --bs-swap-rgb: 227, 115, 131;
}

/* Every dropdown panel takes one surface — 6px radius, 1px #343942 border, #222832 fill — so a
   kebab menu, a filter tray and a standard dropdown read as the same object. Set through
   Bootstrap's own tokens, which is what `.dropdown-menu-dark` does; this lands later in the
   cascade than that class, so the ~34 markup sites still carrying it are superseded rather than
   fighting. Only the surface: a kebab's rows keep their own vocabulary (leading glyphs, a red
   destructive item), so nothing here touches link colours.

   --bs-dropdown-border-width already resolves to 1px, so the spec's border width needs no
   declaration; stating it would just be a second place to keep in step. */
.dropdown-menu {
  --bs-dropdown-bg: var(--bs-body-bg);
  --bs-dropdown-border-color: var(--bs-secondary-bg);
  --bs-dropdown-border-radius: 6px;

  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

/* Every dropdown row is 14px — a kebab's items as much as a standard dropdown's options, so a
   menu does not change size depending on which panel it opens in. Deliberately a literal and
   not a `.type-*` step: the scale has no 14px rung (18px, then 15px), so there is nothing to
   reuse; fold this in if one is ever added rather than leaving two definitions.
   `.sidebar-user-menu .dropdown-item` already resolved to the same 14px on its own. */
.dropdown-item {
  --bs-dropdown-link-color: var(--bs-body-color);
  --bs-dropdown-link-hover-color: #FFFFFF;
  --bs-dropdown-link-active-bg: var(--bs-primary);

  font-size: 14px;
}

/* A kebab's box is only as tall as its glyph, so it sat short of and above the controls beside it.
   Stretching the trigger to the row height and centring inside fixes both; inline-flex, not flex,
   so a table cell's text-align still centres it. */
.dropdown:has(> .btn > .bi-three-dots-vertical) {
  display: inline-flex;
  align-items: center;
  align-self: stretch;
}

.dropdown > .btn:has(> .bi-three-dots-vertical) {
  height: 100%;
}

.btn-primary {
  --bs-btn-bg: var(--bs-primary);
  --bs-btn-border-color: var(--bs-primary);
  --bs-btn-hover-bg: var(--bs-link-hover-color);
  --bs-btn-hover-border-color: var(--bs-link-hover-color);
  --bs-btn-active-bg: var(--bs-link-color);
  --bs-btn-active-border-color: var(--bs-link-color);
  --bs-btn-disabled-bg: var(--bs-primary-border-subtle);
  --bs-btn-disabled-border-color: var(--bs-primary-border-subtle);
}
/* CTA TEXT — this rule IS the CTA step. No .type-cta; button/badge/pill/header share it. */
/* Solid variants are always actions, so the caps live here instead of on ~230 call sites; markup
   still writes Title Case. Outline/link variants are excluded because they carry names and free
   text (.back-btn brand casing, .btn-link avatars, .btn-outline-secondary sort/view controls). */
.btn-primary,
.btn-secondary,
.btn-danger,
.btn-success,
.btn-warning,
.btn-info {
  text-transform: uppercase;
}

.btn,
.btn.btn-primary,
.badge,
.task-status-pill,
.table-header-cell {
  color: #FFF;
  font-family: "Rajdhani", sans-serif;
  font-size: 16px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
}

/* The step above declares a literal `color: #FFF` on every .btn, which is later than — and the
   same specificity as — Bootstrap's own `.btn { color: var(--bs-btn-color) }`, so it wins for
   the light variant too. Its label read white on a near-white fill until the pointer arrived,
   where Bootstrap's (0,2,0) `.btn:hover` took back over and it snapped to black. Handing the
   variant its own token back at (0,2,0) makes the two states agree; a `text-dark` utility at
   the call sites would be !important and unable to be out-specified later (see the UI guide). */
.btn.btn-light {
  color: var(--bs-btn-color);
}


.btn-outline-primary {
  --bs-btn-color: var(--bs-primary);
  --bs-btn-border-color: var(--bs-primary);
  --bs-btn-hover-bg: var(--bs-primary);
  --bs-btn-hover-border-color: var(--bs-primary);
  --bs-btn-active-bg: var(--bs-primary);
  --bs-btn-active-border-color: var(--bs-primary);
  --bs-btn-disabled-color: var(--bs-primary);
  --bs-btn-disabled-border-color: var(--bs-primary);
}

/* Primary border and fill-on-hover come from .btn-outline-primary; only the label turns
   white. Overriding that variant's own variable, rather than adding a text-white utility,
   leaves its hover, active and disabled fills intact. */
.btn-outline-primary.btn-picker-select {
  --bs-btn-color: #fff;
}

/* Floors both states at the longer label's width, so the button does not resize between
   "Select" and "Selected". Clears "SELECTED" at 16px with px-3, and fits a 2-up card. */
.link-picker-card__select {
  min-width: 7.5rem;
}

.btn-secondary {
  --bs-btn-secondary-color: #D9D9D9;
  --bs-btn-secondary-bg: var(--bs-secondary-bg);
  --bs-btn-secondary-border-color: rgba(255, 255, 255, 0.20);
  --bs-btn-secondary-hover-color: #FFFFFF;
  --bs-btn-secondary-hover-bg: #2A303D;
  --bs-btn-secondary-hover-border-color: #FFFFFF;
  --bs-btn-secondary-active-color: #FFFFFF;
  --bs-btn-secondary-active-bg: #343942;
  --bs-btn-secondary-active-border-color: #FFFFFF;
  --bs-btn-secondary-disabled-color: rgba(217, 217, 217, 0.65);
  --bs-btn-secondary-disabled-bg: var(--bs-secondary-bg);
  --bs-btn-secondary-disabled-border-color: rgba(255, 255, 255, 0.15);
}

[data-bs-theme="dark"] .card .form-control:not([disabled]),
[data-bs-theme="dark"] .card .form-select:not([disabled]) {
  background-color: var(--bs-form-control-bg);
}

.body {
  padding-top: 0;
}

label.required::after {
  content: " *";
  color: #dc3545;
}

.mobile-header {
  height: 42px;
}

.navbar-avatar {
  width: 28px;
  height: 28px;
}

.member {
  font-family: "Inter";
  font-weight: 400;
  color: #FFF;
  font-size: 18px;
  text-transform: capitalize;
  vertical-align: middle;

}

.page-item .page-link {
  background-color: #141920;
  color: #FFFFFF;
  border: 1px solid #B0B0B0;
  margin: 0 3px;
  border-radius: 8px;
  transition: all 0.2s ease-in-out;
  user-select: none;
  cursor: pointer;
}

.page-item .page-link:hover {
  background-color: #222832;
  border-color: #FFFFFF;
  color: #FFFFFF;
}

.page-item.active .page-link {
  background-color: rgba(255, 255, 255, 0.08);
  border-color: #FFFFFF;
  color: #FFFFFF;
}

.page-item.disabled .page-link {
  background-color: #141920;
  border-color: #222832;
  color: #777;
  cursor: not-allowed;
}

.desktop-header {
  position: fixed;
  top: 0;
  left: var(--sidebar-w);
  right: 0;
  height: var(--header-h);
  z-index: 1031;
}

/* ---- Global search (nav bar) ---- */
.nav-search {
  width: 100%;
  max-width: min(800px, 100% - 210px);
  margin: 0 auto;
  height: 30px;
  background-color: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.nav-search:focus-within {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.25);
}

.nav-search-icon {
  position: absolute;
  top: 50%;
  left: 0.75rem;
  transform: translateY(-50%);
  color: var(--bs-secondary-color);
  font-size: 0.9rem;
  pointer-events: none;
  z-index: 2;
}

.nav-search-body {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 2.1rem;
  right: 3.5rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}

.nav-search-pills {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* Keep horizontal scrolling but hide the scrollbar — it was overlapping the pills. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.nav-search-pills::-webkit-scrollbar {
  display: none;
}

.nav-search-filter-pill {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.15rem;
  padding: 0.1rem 0.3rem 0.1rem 0.45rem;
  /* Body face: <label> only to drive .btn-check, so the label heading rule must not claim it. */
  font-family: "Inter", sans-serif;
  font-size: 0.68rem;
  line-height: 1.3;
  border-radius: 6px;
  border: 1px solid #29bab2;
  background-color: #29bab233;
  color: #fff;
  white-space: nowrap;
  user-select: none;
}

.nav-search-filter-pill-label {
  font-family: "Inter", sans-serif;
  letter-spacing: 0;
}

.nav-search-filter-pill-prefix {
  font-weight: 400;
  opacity: 0.8;
}

.nav-search-filter-pill-prefix i,
.nav-search-filter-pill-prefix .search-pill-icon-img {
  vertical-align: -1px;
  margin-right: 0.3rem;
}

/* Glyph arm only. font-size cannot size the artwork arm — an <img> is a replaced element
   and takes .search-pill-icon-img's own width/height — so keeping it in the shared rule
   above read as though it sized both. */
.nav-search-filter-pill-prefix i {
  font-size: 0.75rem;
}

.nav-search-filter-pill-label strong {
  font-weight: 700;
}

.nav-search-filter-pill-remove {
  background: none;
  border: none;
  padding: 0;
  color: inherit;
  opacity: 0.7;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  display: flex;
  align-items: center;
}

.nav-search-filter-pill-remove:hover {
  opacity: 1;
}

/* Scope filters are blue, wherever they appear — the nav bar's pill and the Includes section's
   tags are the same filter shown twice, so they share one definition rather than two copies that
   drift. `color` is a no-op for the nav pill (its base is already #fff) and is what overrides the
   tag's accent lettering, which is the generic default (§21.7).

   The tag half is doubled up (`.select-dropdown-tag.select-dropdown-tag--scope`) because this
   rule sits with the nav pills, near the top of the file, while `.select-dropdown-tag` is
   defined far below it — at equal specificity the later rule wins, so a single class here lost
   and the tags stayed accent-coloured. */
.nav-search-filter-pill--scope,
.select-dropdown-tag.select-dropdown-tag--scope {
  border-color: #3b82f6;
  background-color: #3b82f633;
  color: #fff;
}

.nav-search-filter-pill--date {
  border-color: #ff9500;
  background-color: #ff950033;
}

.nav-search-filter-pill:focus {
  outline: none;
}

.nav-search-filter-pill:focus .nav-search-filter-pill-remove {
  color: var(--bs-danger);
  opacity: 1;
}

.nav-search-filter-pill.is-collapsed {
  display: none;
}

/* Overflow trigger for collapsed pills — gray/white so it reads as a control, not a filter. */
.nav-search-pills-more[hidden] {
  display: none;
}

.nav-search-pills-more {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  padding: 0.1rem 0.4rem;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1.3;
  border-radius: 6px;
  border: 1px solid #fff;
  background-color: #6c757d;
  color: #fff;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}

.nav-search-pills-more:hover {
  background-color: #565e64;
}

.nav-search-pills-more:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 1px;
}

.nav-search-input-wrap {
  position: relative;
  /* Floor for the text input; the 75% complement caps .nav-search-pills before it collapses. */
  flex: 1 0 25%;
  min-width: 80px;
  height: 100%;
  display: flex;
  align-items: center;
}

.nav-search-measure {
  position: absolute;
  left: 0;
  top: 0;
  visibility: hidden;
  white-space: pre;
  font-size: 0.875rem;
  font-family: inherit;
  letter-spacing: normal;
  pointer-events: none;
}

.nav-search-ghost-completion {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: var(--bs-tertiary-color);
  opacity: 0.55;
  font-size: 0.875rem;
  font-family: inherit;
  letter-spacing: normal;
  white-space: pre;
  pointer-events: none;
  z-index: 0;
}

.nav-search-input {
  flex: 1;
  min-width: 0;
  width: 100%;
  height: auto;
  padding: 0;
  font-size: 0.875rem;
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  color: var(--bs-body-color);
  position: relative;
  z-index: 1;
  text-overflow: ellipsis;
}

.nav-search-input::placeholder {
  color: var(--bs-tertiary-color);
}

.nav-search-input:focus {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  color: var(--bs-body-color);
  outline: none;
}

.nav-search-input::-webkit-search-cancel-button {
  display: none;
}

.nav-search-hint {
  position: absolute;
  top: 50%;
  right: 2.1rem;
  transform: translateY(-50%);
  font-size: 0.7rem;
  line-height: 1;
  padding: 0.15rem 0.35rem;
  color: var(--bs-secondary-color);
  background-color: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 0.25rem;
  pointer-events: none;
  opacity: 0.85;
  transition: opacity 0.15s;
  z-index: 2;
}

.nav-search:focus-within .nav-search-hint,
.nav-search.has-value .nav-search-hint {
  opacity: 0;
  pointer-events: none;
}

.nav-search-clear {
  position: absolute;
  top: 50%;
  right: 2rem;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 0.1rem 0.25rem;
  color: var(--bs-secondary-color);
  opacity: 0.7;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  display: none;
  align-items: center;
  z-index: 3;
  transition: opacity 0.15s;
}

.nav-search.has-value .nav-search-clear {
  display: flex;
}

.nav-search-clear:hover {
  opacity: 1;
  color: var(--bs-body-color);
}

/* Filter toggle icon — persistent entry point to the quick-filter tray */
.nav-search-filter-toggle {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 0.1rem 0.25rem;
  color: var(--bs-secondary-color);
  opacity: 0.8;
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  display: flex;
  align-items: center;
  z-index: 3;
  transition: color 0.15s, opacity 0.15s;
}

.nav-search-filter-toggle:hover {
  opacity: 1;
  color: var(--bs-body-color);
}

.nav-search-filter-toggle.is-active {
  color: var(--bs-primary);
  opacity: 1;
}

/* Quick-filter tray — single scrollable row of filter pills below the bar */
.nav-search-tray[hidden],
.nav-search-org-picker[hidden] {
  display: none;
}

.nav-search-tray {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 1041;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.5rem;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

/* The tray's shadow points DOWN, and once the results panel stacks underneath it (§19.13)
   that shadow lands on the panel's first rows and reads as a smudge over them rather than
   as elevation. With the panel open the two are one stacked surface, so the panel's own
   shadow is the only one that should show. Scoped to `.show` so a tray floating over page
   content — the case the shadow was written for — keeps it. */
.nav-search-tray:has(~ .nav-search-dropdown.show) {
  box-shadow: none;
}

.nav-search-tray::-webkit-scrollbar {
  display: none;
}

.nav-search-tray-pill {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  font-size: 0.72rem;
  line-height: 1.3;
  white-space: nowrap;
  border-radius: 0.75rem;
  border: 1px solid var(--bs-border-color);
  background-color: transparent;
  color: var(--bs-body-color);
  cursor: pointer;
  user-select: none;
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}

.nav-search-tray-pill i {
  font-size: 0.8rem;
  opacity: 0.85;
}

/* Curated type artwork in the results-sidebar Type pills, replacing the glyph above
   for the four categories that have it. Sized in px, not em: the selected and
   unselected pills set different font-sizes, and one mark must not change size when
   a filter is toggled. Low-detail tier — this slot is the smallest the artwork
   appears anywhere. */
.search-pill-icon-img {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  object-fit: contain;
}

/* The glyph arm of a Type pill, sized to match .search-pill-icon-img above. Without this
   it inherits the pill's own font-size, and the two pill variants set different ones
   (.nav-search-filter-pill 0.68rem, .nav-search-tray-pill 0.72rem) — so a glyph type
   rendered 22-29% smaller than an artwork type beside it in the same list, AND changed
   size when toggled, which is the exact invariant the comment above claims to hold. It
   held only for the four types with artwork. Now it holds for all seven.

   The two descendant selectors are load-bearing, not decoration: the bare class (0,1,0)
   loses to .nav-search-filter-pill-prefix i and .nav-search-tray-pill i (both 0,1,1),
   which set 0.75rem and 0.8rem. Without them this rule applied on the results sidebar
   only — and the nav bar is the surface where typeIconEl() puts a glyph and an <img>
   side by side, so it was dead on the one place that needed it. Keep any new pill
   surface's glyph inside one of these arms, or add it here. */
.search-pill-icon-glyph,
.nav-search-filter-pill-prefix .search-pill-icon-glyph,
.nav-search-tray-pill .search-pill-icon-glyph {
  font-size: 14px;
}

/* Unselected pills carry design's grayscale art, which renders ~20% dimmer than the
   Bootstrap glyphs sharing the row (those take the pill's own body colour, brighter
   than the sidebar rail's). A small lift evens the row out. Measured, not guessed.
   The selected pill's colour art is deliberately left untouched. */
.search-pill-icon-img--gray {
  filter: brightness(1.18);
}

.nav-search-tray-pill:hover {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), 0.12);
}

.nav-search-tray-pill.is-active {
  background-color: rgba(var(--bs-primary-rgb), 0.85);
  border-color: rgba(var(--bs-primary-rgb), 0.85);
  color: #fff;
}

.nav-search-tray-pill.is-active i {
  opacity: 1;
}

.nav-search-tray-pill--filter.is-active {
  background-color: rgba(59, 130, 246, 0.85);
  border-color: rgba(59, 130, 246, 0.85);
}

/* Zero-count pills are dimmed but still selectable — the count reflects the current
   query only, so the pill must stay clickable (the backing checkbox carries no
   `disabled` attribute). Hover feedback is deliberately left to the base rule. */
.nav-search-tray-pill--zero {
  opacity: 0.55;
}

/* Active org/person scope count — left empty rather than "0" so :empty can hide it. */
.nav-search-tray-pill-count:empty {
  display: none;
}

.nav-search-tray-pill-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background-color: rgba(0, 0, 0, 0.2);
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 1;
}

.nav-search-tray-pill:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}

/* Type picker popover — plain gray pills, no search input for a fixed 7-keyword list. */
.nav-search-type-picker[hidden] {
  display: none;
}

.nav-search-type-picker {
  position: absolute;
  z-index: 1042;
  width: 220px;
  max-width: calc(100% - 8px);
  padding: 0.4rem;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

/* Active content-type filters as coloured pills; collapses to nothing when empty. */
.nav-search-type-picker-active {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-bottom: 0.4rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.nav-search-type-picker-active:empty {
  display: none;
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}

.nav-search-type-picker-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.nav-search-type-picker-empty {
  padding: 0.2rem 0.1rem;
}

/* Org-scope picker popover anchored under the Organizations pill */
.nav-search-org-picker {
  position: absolute;
  z-index: 1042;
  width: 320px;
  max-width: calc(100% - 8px);
  padding: 0.4rem;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

/* Active org/person scopes in the picker; collapses so it never shows a bare divider. */
.nav-search-filter-picker-active {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-bottom: 0.4rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--bs-border-color);
  max-height: 110px;
  overflow-y: auto;
}

.nav-search-filter-picker-active:empty {
  display: none;
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}

/* The box itself is .select-dropdown-search (§22), shared with the assignee picker; only this
   surface's own spacing stays here. */
.nav-search-org-picker-input {
  margin-bottom: 0.35rem;
}

.nav-search-org-picker-list {
  max-height: 240px;
  overflow-y: auto;
}

.nav-search-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  width: auto;
  z-index: 1040;
  max-height: 70vh;
  padding: 0.25rem 0;
  overflow-y: auto;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.nav-search-dropdown.show {
  display: block;
}

.nav-search-dropdown--suggest {
  right: auto;
  width: 320px;
  max-width: calc(100% - 8px);
  max-height: 240px;
}

/* Filter keyword suggestion list — shown on bare ':' in the nav search */

.search-filter-suggest-row {
  display: block;
  width: 100%;
  padding: 0.3rem 0.75rem;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font-family: "Inter", sans-serif;
  font-size: 0.8rem;
}

/* Matches .search-filter-suggest-row so the after:/before: hint never shifts font. */
.search-suggest-hint {
  padding: 0.3rem 0.75rem;
  font-family: "Inter", sans-serif;
  font-size: 0.8rem;
}

.search-filter-suggest-row:hover,
.search-filter-suggest-row.is-active {
  background: var(--bs-tertiary-bg);
}

.suggest-prefix {
  color: var(--bs-secondary-color);
  font-weight: 400;
}

.suggest-typed {
  color: var(--bs-primary);
  font-weight: 600;
}

.suggest-rest {
  color: var(--bs-secondary-color);
  font-weight: 600;
}

.search-org-suggest-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0.45rem 0.75rem;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--bs-secondary-color);
  font-size: 14px;
  gap: 0.5rem;
}

/* Same states as a dropdown option row (§20) — muted until pointed at, then the accent. It was
   body-colour with a plain grey wash, which read as a different kind of list from every panel
   next to it. Shared with the nav bar's own suggest rows, which do the identical job. */
.search-org-suggest-row:hover,
.search-org-suggest-row.is-active {
  background: var(--bs-secondary-bg-subtle);
  color: var(--bs-primary);
}

/* A selected row wears the accent, matching the results sidebar's Includes panel — there the
   equivalent rule hangs off .dropdown-item, which these rows are not. Only ever true in the nav
   Filter picker's `picker` mode; the includes: typeahead renders no checkbox. */
.search-org-suggest-row:has(input:checked) {
  color: var(--bs-primary);
}

.search-org-suggest-icon {
  flex-shrink: 0;
  opacity: 0.7;
  font-size: 0.9rem;
}

/* overflow: hidden is required and was missing — text-overflow does nothing without it, so this
   name has never actually ellipsed; a long one just overran the panel. */
.search-org-suggest-name {
  flex: 1;
  font-weight: 500;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On a person row this trailing span is their ORGANISATION, which is data and can be long. It
   has to be allowed to shrink (it was flex-shrink: 0) before an ellipsis can engage, and it
   caps at half the row so it can never squeeze the person's own name to nothing. The full text
   is on the element's title. */
.search-org-suggest-type {
  flex-shrink: 1;
  max-width: 50%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.7rem;
}

/* Result groups / rows — shared by the nav dropdown and the full results page */

/* Search headings take Rajdhani; they are div/span/a, so the element rule cannot reach them. */
.search-result-title,
.srf-related-title,
.search-group-label,
.srf-filter-heading,
.srf-section-label {
  font-family: "Rajdhani", sans-serif;
}

.search-group-label {
  padding: 0.35rem 0.85rem 0.15rem;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bs-secondary-color);
}

.search-result-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.85rem;
  color: var(--bs-body-color);
  text-decoration: none;
}

.search-result-row:hover {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-emphasis-color);
}

.search-result-title {
  font-size: 1.15rem;
  font-weight: 600;
}

/* Matches .search-result-title's link color (--bs-link-color) rather than the
   default muted text-secondary, so the row's owner quip reads as a pair with
   the title instead of competing with it. Explicit font-size/weight (not the
   inherited .small) because .text-muted has its own brand-typography rule
   (15px) that would otherwise make the secondary quip bigger than this one. */
.search-result-quip {
  font-family: "Rajdhani";
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--bs-link-color);
}

/* Same size as .search-result-quip, muted instead of teal — the row's
   secondary detail (a date, a count), never .text-muted (see above).
   Darker than --bs-secondary-color (rgb(176,176,176)) so it reads as
   clearly secondary next to the teal primary, not just a dimmer version of it. */
.search-result-quip-secondary {
  font-family: "Rajdhani";
  font-size: 0.75rem;
  font-weight: 400;
  color: rgb(130, 130, 130);
}

/* List-view row thumbnail — a small rounded-square ("squircle") crop of the
   result's banner/logo/avatar. overflow: hidden clips a collection's
   absolutely-positioned banner mosaic to the rounded corners. */
.search-result-thumb {
  width: 4rem;
  height: 4rem;
  border-radius: 0.85rem;
  object-fit: cover;
  overflow: hidden;
}

/* Smaller placeholder glyph in the squircle's tighter box than the card media slots. */
.search-result-thumb .collection-card-banner-empty {
  font-size: 1.25rem;
}

.search-result-context {
  margin-left: auto;
  white-space: nowrap;
}

/* Result-row type badge. The per-type colour sits on the mark, not on a tile
   behind it — artwork vanished against a filled tile (teal capability mark on a
   teal tile read as a solid square). Don't re-add a background. `font-size`
   sizes the glyph fallback to sit level with artwork, which fills the slot.

   That font-size went 0.7rem -> 1rem with the restyle, growing the three glyph types'
   marks ~43%. Deliberate, and worth stating because it lands on exactly the three types
   with no artwork: at 0.7rem a lone glyph read as a smaller, weaker mark than the artwork
   filling the 1.15rem slot beside it, and removing the tile took away the only thing that
   had been giving it presence. The four artwork types are unaffected — an <img> is a
   replaced element, sized by .search-type-badge-img below. */
.search-type-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.15rem;
  height: 1.15rem;
  font-size: 1rem;
}

.search-type-badge-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* WCAG 1.4.11 needs 3:1 for a graphical object that carries meaning, and in the nav
     dropdown the badge is the only type signal on a mixed list. Measure the SHIPPED PNG
     pixels, not the .search-badge-* hexes below — those are the glyph colours, which an
     <img> ignores, so a derivation from them describes nothing that renders here.
     Each mark is a smooth one-hue ramp, not a flat fill, so the metric is area coverage:
     the share of opaque pixels at >=3:1 on white. As delivered that is capabilities 0%,
     events 0%, showcases 87.4%, projects 93.5% — all four fail, including the two whose
     dominant colour alone looks safe. 0.79 is the least darkening that puts every mark
     at >=95% coverage (the residual is the lightest tail of the ramp and the antialiased
     edge, not a fill the mark reads as). Per-type k at that bar: 0.79 / 0.85 / 0.82 /
     0.85 — kept uniform so four badges in one list are not shaded differently.
     Re-derive with the snippet in scripts/build_workflow_icons.py's docstring after any
     re-export. The nav rail sits on a dark rail and the pills on their own fills, so
     neither is in scope here. */
  filter: brightness(0.79);
}

/* Glyph colour for a type whose `badge_img` is None. For the four types that ship
   artwork today this is inert (an <img> ignores `color`), but the registry supports
   dropping any of them back to a glyph and TestContentTypeRegistryShape keeps that path
   live — so these must be the artwork's own colour, not the pre-artwork palette they
   used to hold. Each is the delivered mark's dominant hue darkened by the same 0.79 the
   <img> above takes, so a fallback glyph matches the badge beside it and clears 3:1 on
   its own: 3.65 / 5.72 / 7.91 / 3.93. The three artwork-less types keep their own
   colours (3.80 / 3.39 / 4.47, all passing). */
.search-badge-project {
  color: #bb3535;
}

.search-badge-event {
  color: #439139;
}

.search-badge-capability {
  color: #21958e;
}

.search-badge-showcase {
  color: #5c4682;
}

.search-badge-organization {
  color: #788494;
}

.search-badge-collection {
  color: #6366f1;
}

.search-badge-person {
  color: #d1699a;
}

/* List-view owner/subtext — a touch smaller than the default .small. */
.search-result-item .small {
  font-size: 0.75rem;
}

.search-filter-panel {
  position: sticky;
  top: calc(var(--header-h) + 1rem);
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.75rem;
  padding: 1rem;
}

/* Own stacking context so the panel and its dropdowns stay above #srf-results-col once columns stack. */
#srf-panel-col {
  position: relative;
  z-index: 1;
}

.srf-corner-toggle {
  border: none;
  background: transparent;
  padding: 0.15rem 0.35rem;
  line-height: 1;
  color: var(--bs-secondary-color);
}

.srf-corner-toggle:hover {
  color: var(--bs-body-color);
}

/* Small, always-underlined link with a circled × instead of a bare glyph. */
.srf-clear-filters {
  font-size: 0.7rem;
  text-decoration: none;
}

.srf-clear-filters-label {
  text-decoration: underline;
}

.srf-clear-filters-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background-color: #6c757d;
  color: #fff;
  font-size: 0.65rem;
  line-height: 1;
}

/* Fieldset-legend look: tab centered on the body's top border; min-height keeps a collapsed section visible. */
.srf-section {
  position: relative;
  margin-top: 2rem;
  min-height: 0.5rem;
}

/* Tighter than the "Filter by" gap. A modifier, not a sibling selector — the two wrappers have different parents. */
.srf-section--tight {
  margin-top: 1.5rem;
}

/* Survives Bootstrap's collapse (display:none) hiding .srf-section-body's own border when closed. */
.srf-section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid var(--bs-border-color-translucent);
}

/* Spans full width, transparent — the label/chevron below carry their own background patches. */
.srf-section-toggle {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1rem;
  transform: translateY(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border: none;
  background: transparent;
  color: var(--bs-body-color);
}

/* Flush left to align with the "Filter by" heading's own icon above. */
.srf-section-legend-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background-color: var(--bs-tertiary-bg);
  padding: 0 0.3rem 0 0;
  white-space: nowrap;
}

/* A wrapping span, not the rotating icon itself, so its padding doesn't shift the glyph sideways when rotated. */
.srf-section-legend-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  background-color: var(--bs-tertiary-bg);
  padding: 0 0.6rem 0 0.3rem;
}

.srf-filter-heading-icon {
  font-size: 1.4rem;
}

.srf-section-icon {
  font-size: 0.85rem;
}

.srf-section-body {
  border: 1px solid var(--bs-border-color-translucent);
  border-radius: 0.5rem;
  background-color: rgba(0, 0, 0, 0.12);
  padding: 1.15rem 0.6rem 0.6rem;
}

.srf-section-chevron {
  font-size: 0.65rem;
  transition: transform 0.15s;
}

.srf-section-toggle[aria-expanded="true"] .srf-section-chevron {
  transform: rotate(0deg);
}

.srf-section-toggle[aria-expanded="false"] .srf-section-chevron {
  transform: rotate(180deg);
}

/* Type/Includes/Date snap open/closed instantly, unlike the animated whole-panel collapse in results.html. */
#srf-type-body.collapsing,
#srf-includes-body.collapsing,
#srf-date-body.collapsing {
  transition: none;
}

/* Visible focus ring for the hidden checkbox's label, matching .nav-search-filter-pill:focus. */
.btn-check:focus-visible+.nav-search-filter-pill,
.btn-check:focus-visible+.nav-search-tray-pill {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}

/* Same problem in the list-page sort menus (Projects / Showcases / Capabilities): the radio is
   visually hidden by .btn-check, so its focus ring is invisible and the label beside it is what
   the user sees. Those radios carried d-none as well, which is not merely invisible but
   unfocusable — so all twelve options were mouse-only. .btn-check alone hides them and keeps them
   in the tab order, and native radio-group arrow keys then move between them; Bootstrap's own
   dropdown key handler cannot help here, because it focuses `.dropdown-item` and ours is a
   <label>, which takes no focus. */
.btn-check:focus-visible+.dropdown-item {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

/* Pager arrows — plain text links, not buttons, so they read apart from the page numbers. */
.srf-pager-arrow {
  background: none;
  border: none;
  padding: 0.25rem 0.35rem;
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.srf-pager-arrow:hover {
  text-decoration: underline;
}

/* The pager's "..." is also a jump-to-page dropdown; scrollable so it cannot grow unbounded. */
.srf-pager-ellipsis-menu {
  max-height: 220px;
  overflow-y: auto;
  min-width: 4rem;
}

/* Suggested Related Searches — each button reads as a mini search bar, not a filter pill. */
.srf-related-title {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--bs-secondary-color);
}

.srf-related-search-btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--bs-border-color);
  background-color: rgba(59, 130, 246, 0.1);
  color: var(--bs-secondary-color);
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
}

.srf-related-search-btn:hover {
  background-color: rgba(59, 130, 246, 0.18);
}

.srf-related-search-btn i {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
  flex-shrink: 0;
}

.srf-related-search-btn span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-summary-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--bs-border-color-translucent);
  text-decoration: none;
  color: var(--bs-body-color);
  font-size: 0.8rem;
}

.search-summary-row:hover {
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.search-summary-text {
  min-width: 0;
}

.search-summary-enter {
  flex-shrink: 0;
  font-size: 0.7rem;
  opacity: 0.6;
}

/* Recent Searches — session-local list above the type-grouped result sections. */
.search-recent {
  padding: 0.25rem 0;
}

.search-recent-divider {
  margin: 0.25rem 0;
  border-top: 1px solid var(--bs-border-color-translucent);
}

/* A divider exists only to separate the recent searches from whatever follows them, so with
   no recents block there is nothing to divide. It cannot be a template condition alone:
   search_recent_remove re-renders ONLY the recents block, and the divider is a SIBLING of
   that swap target, so removing the last entry left a rule floating above the Suggested
   heading with nothing above it. Same shape as the hint rule below — the block disappearing
   is the one signal available, and :has() reads it with no JS. Covers both dividers in
   dropdown.html (recents-to-suggestions and recents-to-results). */
.nav-search-dropdown:not(:has(.search-recent)) .search-recent-divider {
  display: none;
}

/* Empty-bar hint — the only content of the dropdown when the user clicks into an empty
   search field and has no history, so the click still produces feedback instead of a
   panel that never appears. Sized to .search-summary-row above (0.8rem): the two are the
   same kind of line in the same panel — the one row that speaks for the whole dropdown
   rather than listing a result — so they must not read as two different type sizes. Keep
   them in step if either moves. */
.search-dropdown-hint {
  padding: 0.4rem 0.75rem;
  font-size: 0.8rem;
  color: var(--bs-secondary-color);
}

/* The hint is a fallback, not a caption: anything real to offer means nothing to explain.
   Hiding it here rather than in the template is what makes removing the LAST recent search
   reveal it — search_recent_remove swaps only the recents block, so that block vanishing is
   the one signal available, and :has() reads it with no JS. Suggestions are listed too, so
   the hint only ever shows with NEITHER block: no history and nothing reachable to suggest.
   See dropdown.html. */
.nav-search-dropdown:has(.search-recent, .search-suggested) .search-dropdown-hint {
  display: none;
}

.search-recent-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.85rem;
}

.search-recent-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  padding: 0.35rem 0;
  color: var(--bs-body-color);
  text-decoration: none;
}

.search-recent-link:hover {
  color: var(--bs-emphasis-color);
}

.search-recent-link i {
  flex-shrink: 0;
  color: var(--bs-secondary-color);
  font-size: 0.85rem;
}

.search-recent-query {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-recent-remove {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0.25rem;
  color: var(--bs-secondary-color);
  opacity: 0.7;
  cursor: pointer;
  line-height: 1;
}

.search-recent-remove:hover {
  opacity: 1;
  color: var(--bs-danger);
}

/* Mobile global search overlay ------------------------------------------- */
/* Additive rules for nav_search in the mobile offcanvas; the desktop bar is untouched. */
.mobile-search-overlay .nav-search {
  max-width: none;
  height: 44px;
  /* iOS minimum touch target */
}

/* `.nav-search-input` must stay >=16px — Safari zooms the page on focus below that. */
@media (max-width: 767.98px) {
  .nav-search-input {
    font-size: 16px;
  }
}

/* Give the pills their own row: at iPhone width one long pill squeezes the input down to ~80px. */
.mobile-search-overlay .nav-search-body {
  flex-wrap: wrap;
}

.mobile-search-overlay .nav-search-pills {
  order: 2;
  flex-basis: 100%;
}

/* >=44px tap area for icon-only overlay controls, which inherit compact desktop padding. */
.mobile-search-overlay .nav-search-clear,
.mobile-search-overlay .nav-search-filter-toggle {
  min-width: 44px;
  min-height: 44px;
  justify-content: center;
}

/* Full-screen panel; `top` is set in JS from the bar's rect to clear the offcanvas header. */
.mobile-search-overlay .nav-search-dropdown {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  width: auto;
  max-height: none;
  border-radius: 0;
  border-left: 0;
  border-right: 0;
}

/* Suggest mode is full-width on mobile; the desktop caret-tracking offset is off in JS. */
.mobile-search-overlay .nav-search-dropdown--suggest {
  width: auto;
}

/* Taller touch targets for tray pills and org/person suggest rows. */
.mobile-search-overlay .nav-search-tray-pill,
.mobile-search-overlay .search-org-suggest-row {
  min-height: 40px;
}

/* Filter picker fills the width instead of the desktop 320px popover. */
.mobile-search-overlay .nav-search-org-picker {
  position: fixed;
  left: 8px;
  right: 8px;
  width: auto;
  max-width: none;
}

/* Type picker fills the width the same way. */
.mobile-search-overlay .nav-search-type-picker {
  position: fixed;
  left: 8px;
  right: 8px;
  width: auto;
  max-width: none;
}

/* Collection list cards -------------------------------------------------- */
/* Shell is the shared .card-modern/.card-grid-media/.card-list-media system
   (see project/event cards) — nothing collection-specific to size here. */

/* Banner mosaic from a collection's first items: one fills, two split, three go 1 + 2.
   Fills whichever box the caller puts it in — .card-grid-media's aspect-ratio for the
   grid variant, .card-list-media's stretched height for the list variant. */
.collection-card-banner {
  display: grid;
  gap: 2px;
  height: 100%;
  background-color: var(--bs-dark);
  overflow: hidden;
}

.collection-card-banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.collection-card-banner--1 {
  grid-template-columns: 1fr;
}

.collection-card-banner--2 {
  grid-template-columns: 1fr 1fr;
}

.collection-card-banner--3 {
  grid-template-columns: 1.7fr 1fr;
  grid-template-rows: 1fr 1fr;
}

/* First (largest) banner spans both rows on the left. */
.collection-card-banner--3>img:first-child {
  grid-row: 1 / span 2;
}

.collection-card-banner-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--bs-dark-bg-subtle);
  color: var(--bs-secondary-color);
  font-size: 2.5rem;
}

/* Smaller placeholder glyph in the narrower list-row thumbnail. */
.card-list-media .collection-card-banner-empty {
  font-size: 1.5rem;
}

/* .showcase-collection-kebab is retired: every kebab trigger is the bare glyph, no box. Don't re-add one. */

.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--sidebar-w);
  height: 100vh;
  display: flex;
  flex-direction: column;
  z-index: 1030;
  border-right: 1px solid rgba(255, 255, 255, 0.08);
}

.sidebar-nav {
  flex: 1;
  flex-wrap: nowrap;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 8px;
  min-height: 0;
  scrollbar-width: none;
}

.sidebar-nav::-webkit-scrollbar {
  display: none;
}

.sidebar-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.55);
  text-decoration: none;
  gap: 4px;
  padding: 6px 4px;
  width: 100%;
  background-color: transparent;
  transition: color 0.15s, background-color 0.15s;
  border-radius: 12px;
}

.sidebar-tab-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 46px;
}

.sidebar-tab svg {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

/* Curated nav artwork, both rails. ONE layer per tab, not two: nav_icon.html emits
   design's colour variant on the selected tab and its grayscale variant everywhere else,
   so the mark that is painted is the only one fetched. This used to ship both and hide
   one with opacity, which cost ~36 KB of never-rendered image per page load.

   That is why there is no stacking context here any more, and no opacity toggle between
   the variants — the swap happens server-side, at the only moment it can happen at all.
   .active is rendered from request.resolver_match.view_name and nothing boosts the rail,
   so a tab's selected state never changes without a full navigation; there was never a
   crossfade to observe. The transition below is for :hover, which is real.

   The size floor is the width/height attributes in nav_icon.html, not these rules — the
   derived art is intrinsically 84px, so without the attributes a stale stylesheet
   overruns the rail. object-fit matches the other two image classes
   (.search-type-badge-img, .search-pill-icon-img); the derived canvas is square today, so it
   is insurance against a future non-square one distorting nav while they stay correct. */
.nav-icon-img {
  display: block;
  flex-shrink: 0;
  object-fit: contain;
  transition: opacity 0.2s ease;
}

/* 0.8 puts design's #bdbdbd grayscale at the same rendered luminance as the
   Bootstrap glyphs still in the rail, which are rgba(255,255,255,0.55). Measured,
   not guessed — at 0.7 the curated marks read visibly recessive beside them. */
.nav-icon-img--gray {
  opacity: 0.8;
}

.sidebar-tab .nav-icon-img {
  width: 36px;
  height: 36px;
}

.bottom-tab .nav-icon-img {
  width: 28px;
  height: 28px;
}

.sidebar-tab:hover .nav-icon-img--gray,
.bottom-tab:hover .nav-icon-img--gray {
  opacity: 1;
}

/* No .active rule for either variant. A selected tab emits ONLY the colour layer and an
   unselected one ONLY the grayscale layer, so there is nothing left to hide: the pair of
   opacity toggles that used to live here had no element to act on once the render was
   gated. The reason the two were never simply stacked-and-faded — design's variants are
   not drawn on the same geometry, and 7-14% of the grayscale mark's ink falls outside its
   colour sibling's coverage — is now handled by never rendering them together at all. */

.sidebar-tab.active {
  color: #fff;
  background-color: rgb(25, 63, 70);
}

.sidebar-tab:hover:not(.active) {
  color: rgba(255, 255, 255, 0.85);
  background-color: rgba(255, 255, 255, 0.1);
}

.sidebar-tab-label {
  font-size: 0.72rem;
  font-family: "Rajdhani";
  font-weight: 700;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  line-height: 1.2;
}

/* Top counterpart to .sidebar-user, but with its own insets: the 13px/9px pair is the
   design's box for this band, not that band's 16px/12px. */
.sidebar-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 13px 9px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.sidebar-user {
  position: relative;
  flex-shrink: 0;
  padding: 16px 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

/* Remove default <details> triangle marker */
.sidebar-user>summary {
  list-style: none;
}

.sidebar-user>summary::-webkit-details-marker {
  display: none;
}

.sidebar-user-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  width: 100%;
  border-radius: 12px;
  cursor: pointer;
  transition: background-color 0.15s;
}

.sidebar-user-btn:hover,
.sidebar-user[open] .sidebar-user-btn {
  background-color: rgba(255, 255, 255, 0.1);
}

.sidebar-user-menu {
  position: absolute;
  bottom: 0;
  left: calc(100% + 8px);
  min-width: 220px;
  background-color: var(--bs-dark);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  padding: 4px 0;
  list-style: none;
  margin: 0;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.sidebar-user-menu .dropdown-item,
.sidebar-user-menu summary.dropdown-item {
  display: block;
  width: 100%;
  padding: 0.5rem 1rem;
  color: #d9d9d9;
  font-size: 0.875rem;
  text-decoration: none;
  background-color: transparent;
  border: 0;
  white-space: nowrap;
}

.sidebar-user-menu .dropdown-item:hover,
.sidebar-user-menu summary.dropdown-item:hover {
  color: #fff;
  background-color: rgba(255, 255, 255, 0.08);
}

.sidebar-user-menu .dropdown-divider {
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  margin: 4px 0;
}

/* My Organization nested submenu */
.sidebar-submenu {
  list-style: none;
  padding: 0;
  margin: 0;
}

.sidebar-submenu>ul {
  background-color: rgba(255, 255, 255, 0.04) !important;
}

.sidebar-submenu>summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}

.sidebar-submenu>summary::-webkit-details-marker {
  display: none;
}

.sidebar-submenu>summary .bi-chevron-down {
  transition: transform 0.15s;
}

.sidebar-submenu[open]>summary .bi-chevron-down {
  transform: rotate(180deg);
}

.sidebar-avatar {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
}

.core-font {
  font-family: "Rajdhani";
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

.toxigenesis {
  font-family: "Toxigenesis";
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

.core-subtitle {
  color: #FFF;
  font-family: "Rajdhani";
  font-size: clamp(14px, 2.5vw, 25px);
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

/* `.core-p` is retired: the body step at 500 while .type-p declared the specimen's 400. */

/* Line-clamp with ellipsis — Bootstrap does one or the other, not both, so this needs the WebKit box. */
.truncate-description {
  overflow: hidden;
  display: -webkit-box;
  line-clamp: 4;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
}

/* 3-line variant, applied alongside .truncate-description (grid project cards). */
.truncate-description-3 {
  line-clamp: 3;
  -webkit-line-clamp: 3;
}

/* 2-line variant: Event.address is one field, so the split falls wherever the clamp does. */
.truncate-description-2 {
  line-clamp: 2;
  -webkit-line-clamp: 2;
}

.min-w-0 {
  min-width: 0;
}

.text-break-anywhere {
  overflow-wrap: anywhere;
}

.avatar-container {
  margin-left: 200px;
}

.company-avatar {
  transition: opacity 0.3s ease;
  /* Transition for opacity */
}

.company-avatar:hover {
  opacity: 0.7;
}

.avatar-container:hover .company-icon i {
  visibility: visible;
}

.company-icon {
  visibility: hidden;
  color: #fff;
}

.image-placeholder {
  border: 3px dashed #B0B0B0;
  background: rgba(217, 217, 217, 0.10);
}

.header-company-title {
  font-family: "Rajdhani";
  font-size: clamp(1rem, 2.5vw, 1.5rem);
  font-weight: 700;
  text-transform: uppercase;
}

.header-company-subtitle {
  font-family: "Toxigenesis";
  font-style: normal;
  font-weight: 700;
  font-size: clamp(0.9rem, 4vw, 2rem);
  line-height: normal;
  text-transform: uppercase;
}

.rajdhani {
  font-family: "Rajdhani", sans-serif;
  font-style: normal;
  line-height: normal;
  text-transform: uppercase;
}

.home-card-title {
  font-size: clamp(1rem, 2.5vw, 1.5rem);
}

/* .card-title is declared with .type-h3 near the top of this file — card titles are the H3 step. */

/* Card headers are heading-tier chrome: a bare <span>, so no other rule could reach them. */
.card-header {
  font-family: "Rajdhani", sans-serif;
}

/* Field labels are the P step. Bare `label` because label_tag emits a classless <label>. */
.form-label,
label {
  color: #FFF;
  font-family: "Inter", sans-serif;
  font-size: 18px;
  font-style: normal;
  font-weight: 400;
  line-height: normal;
}

/* Body text. Inter is never all caps. */
.inter {
  font-family: "Inter", sans-serif;
  font-style: normal;
  line-height: normal;
}

.header-underline::after {
  content: " ";
  display: block;
  height: 5px;
  background-color: #222832;
}

.active::after {
  background-color: var(--bs-primary);
  border-radius: 10px;
}

/* Superseded by .card-heading/.card-list-media everywhere except
   collection/templates/collection/partials/collection_item_row.html — keep until that migrates. */
.capability-title {
  color: #ffffff;
  text-align: center;
  text-transform: uppercase;
  font-family: "Rajdhani";
  font-size: clamp(0.75rem, 2.5vw, 1rem);
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

.capability-card-img-wrap {
  flex-shrink: 0;
  border-right: 1px solid var(--bs-border-color);
  overflow: hidden;
  width: clamp(80px, 15vw, 150px);
  height: clamp(60px, 10vw, 100px);
}

.rhombus {
  margin: 0.5rem;
  border: 2px solid var(--bs-warning);
  height: 40px;
  min-width: 150px;
  transform: skewX(-30deg);
}

/* Rhombus chips and tab pills take Rajdhani — they read as tab/section labels, not body copy. */
.rhombus-text-correction {
  transform: skewX(30deg);
  font-family: "Rajdhani", sans-serif;
}

.rhombus-btn {
  cursor: pointer;
  border-color: var(--bs-secondary-color) !important;
  color: var(--bs-body-color);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
}

.rhombus-btn:hover {
  border-color: var(--bs-emphasis-color) !important;
  color: var(--bs-emphasis-color);
}

.btn-check:checked+.rhombus-btn {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary) !important;
  color: var(--bs-emphasis-color);
}

.company-card-start {
  width: 100px;
  height: 100px;
}

.company-banner-img {
  width: 150px;
  height: 100px;
  transition: opacity 0.2s ease-in-out;
}

.company-banner-img:hover {
  opacity: 0.8;
}

.tree-sidebar {
  width: 60px;
}

.tree-elbow {
  position: absolute;
  top: 10%;
  bottom: 50%;
  left: 50%;
  width: 30%;
  border-left: 2px solid var(--bs-secondary);
  border-bottom: 2px solid var(--bs-secondary);
  border-bottom-left-radius: 15px;
}

.file-card {
  height: 100%;
  position: relative;
  transition: box-shadow 0.2s ease-in-out;
}

.file-card:hover {
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
}

.hover-btn {
  opacity: 0;
  transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}

.file-card:hover .hover-btn {
  opacity: 1;
  pointer-events: auto;
}

/* Poster-style file card (partials/files/file_thumb_card.html): a live page
   render and the filename. Used where a single document is the content rather
   than a row in a list. */
.file-thumb-card {
  overflow: hidden;
  border-radius: 12px;
  background-color: var(--bs-dark);
  max-width: 14rem;
}

/* Fixed aspect so every card is the same size whatever the page proportions,
   and so the render is cropped to its top rather than scaled down to illegible. */
.file-thumb-card-preview {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background-color: #fff;
}

/* The PDF is sized by CSS, not by the viewer. None of the `#zoom`/`view=FitH`
   fragment parameters are honoured (they are Adobe's; Chrome and Firefox pdf.js
   both ignore them), so the viewer picks its own zoom — which at this card's width
   rendered the page wider than the frame and pushed it off-centre.

   Instead the iframe is laid out at a large fixed logical width and scaled down to
   the box, which makes the result deterministic: `width` is `100% / scale`, so the
   scaled render is exactly the box width whatever the card size, and
   `translateX(-50%)` about a top-center origin keeps the page centred.

   The same trick hides the viewer's chrome, which also cannot be switched off: the
   frame is lifted by the toolbar's scaled height so the toolbar lands above the
   crop, and `--pdf-bleed` overshoots the width so the scrollbar falls off the right
   edge (split evenly by the centring, so the page stays centred). Adjust these
   variables if a strip of browser UI shows; fragment parameters will not help. */
.file-thumb-card-preview iframe {
  --pdf-scale: 0.28;
  --pdf-chrome-top: 56px;
  --pdf-bleed: 24px;

  position: absolute;
  top: calc(var(--pdf-chrome-top) * var(--pdf-scale) * -1);
  left: 50%;
  width: calc((100% + var(--pdf-bleed)) / var(--pdf-scale));
  height: calc(200% / var(--pdf-scale));
  border: 0;
  display: block;
  transform: translateX(-50%) scale(var(--pdf-scale));
  transform-origin: top center;
  /* An iframe consumes every pointer event, which would kill both the card's
     hover state and the buttons layered over it. It is decorative here. */
  pointer-events: none;
}

.file-thumb-card-actions {
  position: absolute;
  inset: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  padding: 0.5rem;
  background-color: rgba(20, 25, 32, 0.65);
  opacity: 0;
  transition: opacity 0.2s ease-in-out;
}

/* focus-within keeps the controls reachable by keyboard, which hover alone
   would not: the iframe is tabindex="-1" so Tab lands straight on Preview. */
.file-thumb-card:hover .file-thumb-card-actions,
.file-thumb-card-actions:focus-within {
  opacity: 1;
}

/* Flush footer, not an inset box: it runs the full width straight off the bottom
   of the render, and the card's own overflow/radius rounds its lower corners. */
.file-thumb-card-name {
  padding: 0.625rem 0.875rem;
  color: var(--bs-emphasis-color);
  font-weight: 700;
  font-size: 0.9375rem;
  overflow-wrap: anywhere;
}

/* Firefox and Safari need the quill editor's height modified to render correctly. */
.vLargeTextField .ql-container,
.vLargeTextField .ql-container .ql-editor {
  height: unset;
}

.vLargeTextField .ql-container .ql-editor {
  min-height: 150px;
  max-height: 540px;
  overflow: auto;

}

/* Compact comment editor in the showcase review modal. */
#showcase-review-form .vLargeTextField .ql-container,
#showcase-review-form .vLargeTextField .ql-container .ql-editor {
  height: unset;
}

#showcase-review-form .vLargeTextField .ql-container .ql-editor {
  min-height: 80px;
  max-height: 200px;
  overflow: auto;
}

.unread-message {
  box-shadow: 0 0 8px #942a35;
}

.hidden-checkbox {
  position: absolute;
  transform: scale(2);
  cursor: pointer;
  opacity: 0;
}

.project-card-title {
  color: #ffffff;
  font-family: "Rajdhani";
  font-size: clamp(18px, 3vw, 38px);
  font-style: bold;
  font-weight: 700;
  line-height: normal;
}

.project-header-description-wrap {
  --project-header-description-clamp: 14rem;
}

.project-header-description-panel {
  max-height: var(--project-header-description-clamp);
  /* Also overrides Quill's own `.ql-editor { overflow-y: auto }` on the body inside — style.css
     loads after the Quill sheet — so the cut is a clean crop, not an inner scrollbar. */
  overflow: hidden;
}

.project-header-description-wrap.is-overflowing .project-header-description-panel {
  -webkit-mask-image: linear-gradient(to bottom, #000 65%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 65%, transparent 100%);
}

.project-header-description-check:checked ~ .project-header-description-panel {
  max-height: none;
  overflow: visible;
  -webkit-mask-image: none;
  mask-image: none;
}

.project-header-description-wrap.is-measured:not(.is-overflowing) label {
  display: none;
}

.project-header-description-less {
  display: none;
}

.project-header-description-check:checked ~ label .project-header-description-more {
  display: none;
}

.project-header-description-check:checked ~ label .project-header-description-less {
  display: inline;
}

/* Legacy deliverables subtitle. Do not merge with .card-subheading — different vocabularies. */
.project-card-subtitle {
  color: #D9D9D9;
  font-family: "Rajdhani";
  font-size: 20px;
  font-style: normal;
  font-weight: 700;
  line-height: normal;
}

/* Back links take the Small Body step, on the component class so all call sites follow. */
.back-btn {
  text-decoration: none;
  /* `none`, not `capitalize`: blocks inherited uppercase without title-casing every word. */
  text-transform: none;
  color: #FFF;
  font-family: "Inter", sans-serif;
  font-size: 15px;
  font-style: normal;
  font-weight: 300;
  line-height: normal;
}

/* One hover colour for every Back link, independent of source order — hence the :not()s rather than
   relying on where each rule sits. Both exclusions borrow .back-btn only for its typography and own
   their :hover. An !important colour utility still beats this, so style Back link colour here. */
.back-btn:not(.btn-reactivate):not(.btn-outline-secondary):hover,
.back-btn:not(.btn-reactivate):not(.btn-outline-secondary):focus-visible {
  color: var(--bs-link-hover-color);
}

.tab-button {
  background: none;
  border: none;
  padding: 0.8rem 1.2rem;
  font-weight: 600;
  color: #B0B0B0;
  position: relative;
  cursor: pointer;
}

.tab-button.active {
  color: #29BAB2;
}

.nav-pills .nav-link {
  color: #adb5bd;
  transition: all 0.2s;
}

.nav-pills .nav-link:hover {
  color: #fff;
  background-color: rgba(255, 255, 255, 0.05);
}

.nav-pills .nav-link.active {
  background-color: #29bab2;
  color: #fff;
}

/* Thumbnail container for Dropzone */
.dz-image {
  width: 100px;
  height: 100px;
  overflow: hidden;
}

/* Thumbnail image for Dropzone */
.dz-image img {
  width: 140px;
  height: 140px;
  object-fit: cover;
  transition: filter 0.2s ease-in-out;
  position: static !important;
}

#id_banner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  z-index: 10;
}

/* min-height, not height: the band holds the brand mark, which is a fixed pixel size, so a
   viewport-relative height clips it whenever 40vh falls below the mark — most visibly when
   zooming in, which shrinks the viewport in CSS pixels while the mark stays put. */
.gradient-header {
  min-height: 40vh;
  border-bottom: 2px solid rgba(255, 255, 255, 1);
  background: linear-gradient(94.53deg, #1D5657 -10.88%, #01031A 42.17%);
  display: flex;
  align-items: center;
  justify-content: center;
}

.core-logo-login {
  width: 340px;
  height: 340px;
  opacity: 1;
  filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
}

.login-card-frame {
  border: 2px solid rgba(255, 255, 255, 1);
  width: 534px;
  max-width: 100%;
  height: fit-content;
  opacity: 1;
}

@media (max-width: 576px) {
  .gradient-header {
    min-height: 30vh;
  }

  .core-logo-login {
    height: auto;
  }
}

.input-login {
  font-family: "Inter";
  font-weight: 400;
  font-size: 16px;
  scroll-margin-top: 16px;
}

.custom-tooltip {
  --bs-tooltip-bg: var(--bs-primary-bg-subtle);
  --bs-tooltip-color: var(--bs-dark-text-emphasis);
}

#cropperModal {
  z-index: 1070 !important;
}

.cropper-circle .cropper-view-box,
.cropper-circle .cropper-face {
  border-radius: 50%;
}

.cropper-modal {
  opacity: 0.8;
}

#cropperModal {
  z-index: 1070 !important;
}

.cropper-circle .cropper-view-box,
.cropper-circle .cropper-face {
  border-radius: 50%;
}

.cropper-modal {
  opacity: 0.8;
}

/* Phones: the modal is full-screen, so the image area fills header-to-footer. */
@media (max-width: 575.98px) {
  #cropperModal .img-container {
    height: 100%;
    max-height: none;
  }
}

.locked-milestone-card {
  min-height: 10rem;
  overflow: hidden;
  cursor: not-allowed;
  background: rgba(255, 255, 255, 0.03);
}

.locked-milestone-content {
  position: relative;
  z-index: 2;
  min-height: 10rem;
  color: rgba(255, 255, 255, 0.9);
}

@media (max-width: 1200px) {
  .project-card-title {
    white-space: normal;
    overflow: visible;
    text-overflow: unset;
  }
}

@media (max-width: 980px) {
  .project-card-title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

.accordion-button::after {
  filter: invert(1) grayscale(100%) brightness(200%);
  margin-right: 20px;
}

.accordion-button:focus {
  box-shadow: none;
  border-color: white;
}

.accordion-button:not(.collapsed) {
  background-color: #212529;
  color: white;
  box-shadow: none;
}

.accordion-button:hover {
  background-color: #2c3034;
}

.btn-reactivate {
  background-color: transparent;
  border: 2px solid var(--bs-primary);
  color: white;
  padding: 10px 24px;
  letter-spacing: 0.5px;
  border-radius: 6px;
  transition: all 0.3s ease;
}

.btn-reactivate:hover {
  background-color: rgba(94, 234, 212, 0.1);
  box-shadow: 0 0 15px rgba(94, 234, 212, 0.4);
  color: #ffffff;
  border-color: var(--bs-primary);
}

.btn-reactivate:active {
  background-color: rgba(94, 234, 212, 0.2);
  transform: translateY(1px);
}

/* Confirm/dismiss pair. Shared box so both line up: the outline's border is drawn
   inside the same 48px height as the filled button's fill, not added to it. */
.btn-modal-filled,
.btn-modal-outline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  min-width: 104px;
  padding: 0 24px;
  border-radius: 6px;
  font-family: "Rajdhani", sans-serif;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  transition: all 0.2s;
}

.btn-modal-filled {
  background-color: var(--bs-primary);
  border: 3px solid var(--bs-primary);
  color: #fff;
}

.btn-modal-filled:hover,
.btn-modal-filled:focus-visible {
  background-color: var(--bs-primary-border-subtle);
  border-color: var(--bs-primary-border-subtle);
  color: #fff;
}

.btn-modal-outline {
  background: transparent;
  border: 3px solid var(--bs-primary);
  color: var(--bs-primary);
}

.btn-modal-outline:hover,
.btn-modal-outline:focus-visible {
  background-color: rgba(var(--bs-primary-rgb), 0.12);
  border-color: var(--bs-primary);
  color: var(--bs-primary);
}

/* No divider above a modal's button row: the footer reads as part of the panel rather
   than a separate strip. The confirm dialogs below have always drawn no line, so this
   makes every other modal agree with them. */
.modal-footer {
  border-top: 0;
}

/* base.html emits the bootstrap link/script tags ahead of its own doctype, so pages
   render in quirks mode, where the UA sheet gives every form a 1em bottom margin. In a
   modal that lands under the button row and reads as stray padding no class accounts
   for. Zeroed here rather than per modal; it disappears on its own once the doctype
   leads the document. */
.modal form {
  margin-bottom: 0;
}

/* One uniform 1.5rem inset on every footer, matching the bodies above them, in place of
   Bootstrap's 0.75rem and the per-modal padding utilities that used to patch it. Utilities
   would win over this (spacing utilities are !important), so a footer that needs a
   different inset has to say so with a class, not by relying on this. */
.modal-footer {
  padding: 1.5rem;
}

/* Bootstrap spaces footer buttons by giving every child a 0.25rem margin on all four
   sides, so the row sits 4px clear of the footer's padding box above and below. Only
   the horizontal half earns its keep — the vertical half reads as extra padding under
   the buttons — so the side margins stay and the rest goes. */
.modal-footer > * {
  margin-top: 0;
  margin-bottom: 0;
}

/* #core-modal-2 stacks OVER #core-modal (Swap/Remove Showcase from inside the
   showcase review modal, coreConfirm, the media preview), and the modal beneath
   must dim so focus lands on the overlay.

   Bootstrap 5 does NO z-index management for stacked modals — verified: its JS
   never writes zIndex. Every .modal is 1055 and every .modal-backdrop 1050, so
   EVERY modal paints above EVERY backdrop: the overlay's backdrop physically
   cannot dim the modal underneath it, whatever the DOM order. Without the rules
   below the review modal stays at full brightness behind the Swap/Remove dialog.

   So slot the overlay's backdrop BETWEEN the two modals (1055 < 1070 < 1075).
   Bootstrap reads both values from custom properties, so set those rather than
   z-index directly, and the backdrop keeps deriving from --bs-backdrop-zindex.

   Only #core-modal-2 ever stacks, so key off it by id rather than counting
   backdrops with :has() alone — a sibling-count selector can't tell which
   backdrop belongs to which modal, and would bump a lone backdrop above
   #core-modal in the ordinary single-modal case. Each backdrop is
   body.append()-ed, so the overlay's is the last in the DOM; matched with
   :last-of-type rather than `+`, since the two are only adjacent for as long as
   nothing else appends to <body> between them. */
#core-modal-2 {
  --bs-modal-zindex: 1075;
}

body:has(> #core-modal-2.show) .modal-backdrop:last-of-type {
  --bs-backdrop-zindex: 1070;
}

/* Confirmation panel, applied to `.modal-content`. The heading clamps rather than
   fixing at 35px — these titles carry an entity name and would wrap badly. */
.confirm-dialog {
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-secondary);
  border-radius: 0;
  color: #fff;
}

/* Sized to content between a floor and a cap, so short dialogs stay compact without
   each landing on a different width. !important beats the inline max-width that
   #core-modal's show handler pins on the shared dialog. */
.modal-dialog:has(> .confirm-dialog) {
  width: fit-content;
  min-width: min(560px, calc(100vw - 2rem));
  max-width: min(900px, calc(100vw - 2rem)) !important;
  /* Bootstrap only centres the dialog from the sm breakpoint up; a fit-content box
     would otherwise sit against the left edge on phones. */
  margin-left: auto;
  margin-right: auto;
}

.confirm-dialog .modal-header {
  justify-content: center;
  padding: 2rem 2.5rem 0;
  border-bottom: 0;
}

.confirm-dialog .modal-header .btn-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  margin: 0;
}

.confirm-dialog .modal-title {
  width: 100%;
  font-family: "Rajdhani", sans-serif;
  font-size: clamp(1.5rem, 3.5vw, 2.1875rem);
  font-weight: 700;
  line-height: normal;
  text-align: center;
  text-transform: uppercase;
  color: #fff;
}

/* text-transform is reset explicitly: some of these dialogs sit inside a `.klamp`
   region, whose inherited uppercase would otherwise shout the body copy. */
.confirm-dialog .modal-body {
  padding: 0.75rem 2.5rem 0;
  font-family: "Inter", sans-serif;
  font-size: 19px;
  font-weight: 400;
  line-height: normal;
  text-align: center;
  text-transform: none;
  color: #fff;
}

/* The dialogs are plain prose plus the occasional inline form control, so the muted
   greys they inherit from Bootstrap would read as disabled against this panel. */
.confirm-dialog .modal-body .text-body-secondary,
.confirm-dialog .modal-body .text-secondary,
.confirm-dialog .modal-body .text-muted {
  color: #fff !important;
}

/* Horizontal padding is deliberately small: the button row is centred, and in several
   dialogs the footer is nested inside the body (the buttons live in the form), where a
   wide inset would compound with the body's own. */
.confirm-dialog .modal-footer {
  justify-content: center;
  gap: 26px;
  padding: 1.5rem 1rem 2rem;
  border-top: 0;
}

/* Bootstrap's footer gives every child a 0.25rem margin; the flex gap owns the
   spacing here so the pair sits at the design's 26px. */
.confirm-dialog .modal-footer > * {
  margin: 0;
}

@media (max-width: 575.98px) {
  .confirm-dialog .modal-header {
    padding: 1.5rem 1.25rem 0;
  }

  .confirm-dialog .modal-body {
    padding: 0.75rem 1.25rem 0;
    font-size: 17px;
  }

  .confirm-dialog .modal-footer {
    padding: 1.25rem 1rem 1.5rem;
  }
}

.deliverable-table-list .table {
  --bs-table-bg: transparent;
  --bs-table-striped-bg: transparent;
  --bs-table-hover-bg: transparent;
}

.text-primary {
  color: var(--bs-primary);
}

.deliverable-table-list thead th {
  font-family: "Rajdhani";
  font-size: 20px;
  font-style: bold;
  font-weight: 700;
}

.deliverable-table-list tbody tr {
  font-family: "Rajdhani";
  font-size: 17px;
  font-style: normal;
  font-weight: 400;
}

.bg-primary {
  background-color: var(--bs-primary);
}

.bold-icon {
  -webkit-text-stroke-width: 1px;
}

.icon-wrapper {
  width: 35px;
  height: 35px;
  background-color: #0b0f14;
  border-radius: 50%;
  border: 2px solid #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.member-avatar {
  height: 30px;
  width: 30px;
  border-radius: 50%;
  object-fit: scale-down;
  background-color: #ffffff;
  filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.25));
  border: 1px solid #ffffff;
}

.member-link {
  transition: background-color 0.2s ease, transform 0.2s ease;
  color: inherit;
}

.member-link:hover {
  background-color: rgba(255, 255, 255, 0.1);
  text-decoration: none;
  transform: translateX(5px);
}

.toggle-icon {
  transition: transform 0.2s ease;
  display: inline-block;
}

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

/* flex-basis: 0 gives every row the same name-column width, so a long name ellipsizes instead of
   pushing the count and chevron out of alignment. */
.member-org-link {
  flex: 1 1 0;
  min-width: 0;
}

/* Right-aligned over a min-width, so the digits line up across rows and a
   three-digit count grows leftward rather than breaking the column. */
.member-org-count {
  min-width: 1.5rem;
  margin-left: 0.5rem;
  text-align: right;
}

/* Revealed on hover/focus/open only — its own class, since .toggle-icon is shared with lists that
   show chevrons at rest. Keeps its width while hidden so rows don't shift. */
.member-toggle-btn {
  display: inline-flex;
  justify-content: center;
  width: 1.5rem;
  margin-left: 0.5rem;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.member-row-hover:hover .member-toggle-btn,
.member-toggle-btn:focus-visible,
.member-toggle-btn[aria-expanded="true"] {
  opacity: 1;
}

/* No hover to reveal it with on touch, so it has to stay visible there. */
@media (hover: none) {
  .member-toggle-btn {
    opacity: 1;
  }
}

.member-row-hover {
  transition: background-color 0.2s ease;
}

.member-row-hover:hover {
  background-color: rgba(255, 255, 255, 0.05);
}

.table-dark-preview {
  background: rgba(34, 40, 50, 1);
}

.action-btn {
  width: 40px;
  height: 40px;
}

.skip-button {
  font-family: "Inter", sans-serif;
  font-size: 0.85rem;
  border: none;
}

.sidebar-title {
  font-family: "Rajdhani";
  font-size: 18px;
  font-weight: 700;
  font-style: bold;
  text-transform: uppercase;
}

/* Conversation rows — no text-transform (proper nouns). `font-style: bold` was invalid. */
.conversation-sidebar-title {
  font-family: "Inter";
  font-size: 16px;
  font-weight: 700;
  font-style: normal;
}

.chat-text {
  font-family: "Inter";
  font-weight: 400;
}

.full-height-container {
  height: calc(100vh - 100px - var(--back-link-h, 0px));
  display: flex;
  flex-direction: column;
}

/* Set on the page that renders a Back link above the box, so it keeps its bottom border in view. */
.has-back-link {
  --back-link-h: 2.75rem;
}

.chat-list-btn {
  transition: background-color 0.2s ease;
}

.chat-list-btn:hover {
  background-color: rgba(255, 255, 255, 0.2) !important;
  cursor: pointer;
}

.active-chat {
  background-color: rgba(255, 255, 255, 0.2);
}

/* Open-conversation header, both thread types. No text-transform: these are proper nouns. */
.chat-title {
  font-family: "Rajdhani", sans-serif;
  font-size: 19px;
  font-weight: 700;
  font-style: normal;
}

.chat-box-content {
  background-color: rgba(255, 255, 255, 0.05);
}

.chat-box-company {
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 0 0 6px 6px;
}

/* Border matches the .border-secondary utility the rest of the messages chrome uses, rather than
   the utility itself: this rule already owns border-color, so a class on the card would leave a
   dead declaration here that only wins by being !important. */
#member-offcanvas-content .card {
  border-color: rgba(var(--bs-secondary-rgb), 1);
  /* Fills the offcanvas body rather than the viewport: the body is already shorter than
     the screen by the close-button header, so a 100vh card would run off the bottom. */
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

#member-offcanvas-content .card::-webkit-scrollbar {
  width: 6px;
}

#member-offcanvas-content .card::-webkit-scrollbar-track {
  background: transparent;
}

#member-offcanvas-content .card::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.4);
  border-radius: 10px;
}

#member-offcanvas-content .card::-webkit-scrollbar-thumb:hover {
  background-color: rgba(255, 255, 255, 0.7);
}

.bg-table-header {
  background-color: rgba(255, 255, 255, 0.25) !important;
}

/* A sort-header button must read as the header text it replaced, not as a button.
   .btn-table-header is listed alongside the .bg-table-header descendant selector so
   this also reaches the sortable tables that carry no such background — the PM
   Administrative panels and onboarding queues. */
.bg-table-header th button,
.bg-table-header th button *,
.btn-table-header,
.btn-table-header * {
  font: inherit !important;
  text-transform: inherit;
  letter-spacing: inherit;
  line-height: inherit;
}

/* Type comes from the CTA rule above; font-* here would override it at equal specificity. */
.table-header-cell {
  text-transform: uppercase;
  text-align: left !important;
  padding: 1rem !important;
}

.btn-table-header {
  padding: 0;
  width: 100%;
  /* Follow the <th>'s own alignment rather than forcing left: the PM Administrative
     tables centre several headers, and every other consumer's cell resolves to left
     anyway, so this changes nothing for them. */
  text-align: inherit;
  background: transparent;
  border: 0;
  color: white;
}

/* A muted <thead> (the PM Administrative tables, the Tasks list) keeps its colour — the
   button's own white would otherwise repaint only the sortable columns and split the
   header row. */
.text-muted .btn-table-header,
.text-body-secondary .btn-table-header {
  color: inherit;
}

/* .btn-invite-action is retired: a row's actions belong in that row's kebab. Don't reintroduce it. */

.asteriskField {
  color: var(--bs-danger);
}

.company-checkbox:not(:checked)+.card .btn-added-text,
.company-checkbox:not(:checked)+.card .btn-hover-remove-text {
  display: none;
}

.company-checkbox:checked+.card .btn-add-text,
.company-checkbox:checked+.card .btn-hover-remove-text {
  display: none;
}

.company-checkbox:checked+.card .btn-select-company:hover .btn-added-text {
  display: none;
}

.company-checkbox:checked+.card .btn-select-company:hover .btn-hover-remove-text {
  display: inline-block;
}

.company-checkbox:not(:checked)+.card .btn-select-company {
  background-color: var(--bs-primary);
  color: white;
  border-color: var(--bs-primary);
}

.company-checkbox:checked+.card .btn-select-company {
  background-color: var(--bs-success);
  color: white;
  border-color: var(--bs-success);
}

.company-checkbox:checked+.card .btn-select-company:hover {
  background-color: var(--bs-danger);
  border-color: var(--bs-danger);
}

.btn-select-company {
  cursor: pointer;
  transition: background-color 0.2s, border-color 0.2s;
  text-align: center;
}

/* Showcase capability picker: the pill overlays the catalog's list card instead of sitting
   beside it. It's a sibling of .card-host, not a descendant, so .card-host:hover can't reach it —
   both the pill's lift and the card's own lift/shadow/zoom are re-triggered here off the shared
   .capability-picker-card ancestor instead, so hovering either one moves both. */
.capability-picker-card .capability-picker-add {
  position: absolute;
  top: 50%;
  right: 0.75rem;
  transform: translateY(-50%);
  z-index: 2;
  transition: transform 0.25s ease;
}

.capability-picker-card:hover .capability-picker-add {
  transform: translateY(calc(-50% - 4px));
}

.capability-picker-card:hover .card-modern {
  transform: translateY(-4px);
}

.capability-picker-card:hover .card-host {
  filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
}

.capability-picker-card:hover .card-banner-img {
  transform: scale(1.06);
}

/* Clearance so a long title can't wrap under the overlaid pill. */
.capability-picker-card .card-list-body {
  padding-right: 6.5rem;
}

.chat-filter-btn {
  border-color: rgba(var(--bs-secondary-rgb), 1);
}

.chat-filter-btn:hover {
  border-color: rgba(var(--bs-secondary-rgb), 1);
  background-color: rgba(255, 255, 255, 0.1);
}

.active-tab {
  background-color: rgba(41, 186, 178, 1);
  color: #ffffff;
}

.active-tab:hover {
  background-color: rgba(41, 186, 178, 0.8);
  border-color: rgba(var(--bs-secondary-rgb), 1);
  color: #ffffff;
}

.ql-editor.ql-view {
  height: auto;
  padding: 0;
}

#pillScroll {
  --node-size: 66px;
  --node-half: 33px;
  --bar-width: clamp(400px, 30vw, 500px);

  container-type: inline-size;
  scrollbar-width: none;
  scroll-behavior: smooth;
  scroll-snap-type: x mandatory;
  height: 400px;
  padding-top: 50px;
}

#pillScroll::-webkit-scrollbar {
  display: none;
}

#pillScroll[data-align="left"] {
  --bar-width: 70cqw;
  --finish-bar-width: 40cqw;
  scroll-padding-right: 50%;
}

#pillScroll[data-align="left"] > div:first-child {
  width: calc(25cqw - var(--node-half)) !important;
}

#pillScroll[data-align="left"] > div:last-child {
  width: calc(75cqw - var(--node-half)) !important;
}

.milestone-node {
  transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  transform: scale(0.9);
  filter: grayscale(1) brightness(0.7);
  z-index: 2;
}

.milestone-node.is-centered {
  transform: scale(1.3);
  filter: grayscale(0) brightness(1.1);
  z-index: 10;
}

.milestone-node .core-subtitle {
  transition: opacity 0.3s;
  opacity: 0.5;
}

.milestone-node.is-centered .core-subtitle {
  opacity: 1;
  font-weight: bold;
}

.timeline-finish-badge {
  background: linear-gradient(180deg, #4b5563 0%, #374151 100%);
}

.timeline-finish--done .timeline-finish-badge {
  background: linear-gradient(180deg, #f5d76e 0%, #e0a800 100%);
  box-shadow: 0 0 14px 3px rgba(224, 168, 0, 0.55);
}

.milestone-link-overdue {
  background: linear-gradient(180deg, #e05d6f 0%, #a52834 100%) !important;
  border-color: var(--bs-danger) !important;
  box-shadow: 0 0 12px 2px rgba(var(--bs-danger-rgb), 0.65);
}

.milestone-link-overdue:hover {
  box-shadow: 0 0 20px 5px rgba(var(--bs-danger-rgb), 0.95);
}

.dual-list {
  min-height: 150px;
  padding: 0.5rem;
  -webkit-appearance: none;
  appearance: none;
  background-color: transparent !important;
  box-shadow: none !important;
  text-shadow: none !important;
}

.dual-list option {
  padding: 6px 12px;
  cursor: pointer;
  border-radius: 4px;
  margin-bottom: 2px;
}

.dual-listbox-panel {
  flex: 1 1 0%;
  min-width: 250px;
  min-height: 250px;
  background-color: var(--bs-form-control-bg);
  border-color: var(--bs-border-color);
}

.dual-list option:checked {
  background: linear-gradient(0deg, var(--bs-primary) 0%, var(--bs-primary) 100%) !important;
}

.dual-list option:hover:not(:checked) {
  background-color: rgba(255, 255, 255, 0.05);
}

.errorlist {
  color: red;
  font-weight: bold;
}

.invalid-feedback {
  display: block;
  color: #dc3545;
  margin-top: 0.25rem;
}

.accordion-button,
.accordion-button:not(.collapsed) {
  background: transparent !important;
  box-shadow: none !important;
}

[data-bs-toggle="collapse"] .bi-chevron-down {
  display: inline-block;
  transition: transform 0.2s ease;
}

[data-bs-toggle="collapse"][aria-expanded="true"] .bi-chevron-down {
  transform: rotate(180deg);
}

.status-completed {
  background: rgba(34, 197, 94, 1);
}

.status-review {
  background: rgba(245, 158, 11, 1);
}

.status-changes {
  background: rgba(239, 68, 68, 1);
}

.status-todo {
  background: rgba(168, 170, 173, 1);
}

.status-swap {
  background: rgba(227, 115, 131);
}

.status-inprogress {
  background: rgba(var(--bs-primary-rgb), 1);
}

.task-status-pill {
  min-width: auto;
  width: fit-content;
  padding: 0 10px;
  border: 1px solid;
  border-radius: 50px;
  height: 27px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.task-status-pill--todo {
  background-color: rgba(var(--bs-secondary-color-rgb), 0.3);
  border-color: var(--bs-secondary-color);
  border-width: 1px;
}

.task-status-pill--todo i {
  color: var(--bs-secondary-color);
}

.task-status-pill--changes {
  background-color: rgba(var(--bs-danger-rgb), 0.3);
  border-color: rgba(239, 68, 68, 1);
  border-width: 1px;
}

.task-status-pill--changes i {
  color: rgba(239, 68, 68, 1);
}

.task-status-pill--review {
  background-color: rgba(var(--bs-warning-rgb), 0.3);
  border-color: rgba(var(--bs-warning-rgb), 1);
  border-width: 1px;
}

.task-status-pill--review i {
  color: rgba(var(--bs-warning-rgb), 1);
}

.task-status-pill--completed {
  background-color: rgba(var(--bs-success-rgb), 0.3);
  border-color: rgba(var(--bs-success-rgb), 1);
  border-width: 1px;
}

.task-status-pill--completed i {
  color: rgba(var(--bs-success-rgb), 1);
}

.task-status-pill--swap {
  background-color: rgba(var(--bs-swap-rgb), 0.3);
  border-color: rgba(227, 115, 131, 1);
  border-width: 1px;
}

/* Card status pill — separate from .task-status-pill, the task-table vocabulary. */
.card-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px;
  border: 1px solid;
  border-radius: var(--bs-border-radius);
  color: #fff;
  font-family: "Rajdhani", sans-serif;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Opaque, not the design's 15% tint — the banner photo read through a translucent fill. */
.card-status-pill--completed {
  background-color: #264035;
  border-color: var(--bs-success);
}

.card-status-pill--completed i {
  color: var(--bs-success);
}

/* Opaque like --completed. --action-required shares it: one token, two distinct labels. */
.card-status-pill--past-due,
.card-status-pill--action-required {
  background-color: #412c35;
  border-color: var(--bs-danger);
}

.card-status-pill--past-due i,
.card-status-pill--action-required i {
  color: var(--bs-danger);
}

/* Pending-invitation pill — amber, not red: an invitation is not an overdue obligation. */
.card-status-pill--invited {
  background-color: #40382c;
  border-color: var(--bs-warning);
}

.card-status-pill--invited i {
  color: var(--bs-warning);
}

/* The check glyph reads heavier than the label at a shared size, so step it down. */
.card-status-pill i {
  font-size: 0.6875rem;
}

.task-status-pill--inprogress {
  background-color: rgba(var(--bs-primary-rgb), 0.3);
  border-color: rgba(var(--bs-primary-rgb), 1);
  border-width: 1px;
}

.task-status-pill--inprogress i {
  color: rgba(var(--bs-primary-rgb), 1);
}

/* Card status pill — separate from .task-status-pill, the task-table vocabulary. */
.card-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px;
  border: 1px solid;
  border-radius: var(--bs-border-radius);
  color: #fff;
  font-family: "Rajdhani", sans-serif;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Opaque, not the design's 15% tint — the banner photo read through a translucent fill. */
.card-status-pill--completed {
  background-color: #264035;
  border-color: var(--bs-success);
}

.card-status-pill--completed i {
  color: var(--bs-success);
}

/* Opaque like --completed. --action-required shares it: one token, two distinct labels. */
.card-status-pill--past-due,
.card-status-pill--action-required {
  background-color: #412c35;
  border-color: var(--bs-danger);
}

.card-status-pill--past-due i,
.card-status-pill--action-required i {
  color: var(--bs-danger);
}

/* Pending-invitation pill — amber, not red: an invitation is not an overdue obligation. */
.card-status-pill--invited {
  background-color: #40382c;
  border-color: var(--bs-warning);
}

.card-status-pill--invited i {
  color: var(--bs-warning);
}

/* The check glyph reads heavier than the label at a shared size, so step it down. */
.card-status-pill i {
  font-size: 0.6875rem;
}

.circle-slash {
  color: rgba(var(--bs-primary-rgb), 1);
}

.filter-btn {
  width: 33px;
  height: 33px;
  border-radius: 6px;
}

/* A header carrying both a filter and a sort lays them out inline so the pair honours the
   cell's own text-align. The sort button gives up its full-width click target there — at
   100% it stretched across the rest of the cell and pushed its label away from the funnel
   that belongs to it. Unfiltered headers keep the full-width target. */
.table-header-controls .btn-table-header {
  width: auto;
}

/* The one filter menu, shared by every table that has one (see
   components/table_filter_dropdown.html). It replaced .filter-dropdown-menu, which the two
   milestone task tables used with a fixed `height: 173px` — the height four status options
   happen to fill, so a shorter list rendered dead space below itself. Two declarations here
   are the reason the class exists at all:

   * max-height, not height. Identical once the content reaches 173px, and it stops padding
     a menu whose column offers fewer options.
   * The type baseline. A menu hangs inside a .table-header-cell, so it inherits the 16px
     Rajdhani the CTA rule puts there and its `small` heading resolves against that. The
     tables whose <thead> is styled differently — the PM Administrative queues, the Team
     page — would otherwise resolve the same markup against a different number and drift.
     Restating it means every filter menu computes from the same numbers wherever it hangs;
     for the task tables, which inherited exactly these values, it is a no-op.

   The rows INSIDE the menu are the customizable dropdown's (.select-dropdown--multi, below):
   a <label class="dropdown-item"> wrapping its own checkbox. So this class sizes the box and
   nothing else — padding, gap, hover fill and the checked accent all come from there, which
   is what keeps a table filter and every other multi-select panel reading as one control. */
.table-filter-menu {
  min-width: 140px;
  max-height: 173px;
  overflow-y: auto;
  font-family: "Rajdhani", sans-serif;
  font-size: 16px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
  text-transform: none;
}

.hover-message {
  transition: box-shadow 0.3s ease;
}

.hover-message:hover {
  box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
}

.hover-message .message-actions {
  opacity: 0;
  transform: translateY(-10px);
  transition: all 0.3s ease;
  pointer-events: none;
}

.hover-message:hover .message-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

#conversation-search-results mark {
  background-color: #1d5657;
  color: #fff;
  padding: 0 0.15em;
  border-radius: 0.15rem;
}

.message-highlight {
  border-radius: 0.5rem;
  animation: messageHighlightFade 2.5s ease;
}

@keyframes messageHighlightFade {

  0%,
  25% {
    background-color: rgba(29, 86, 87, 0.45);
  }

  100% {
    background-color: transparent;
  }
}

#mobile-conversation-search::-webkit-search-cancel-button,
#desktop-conversation-search::-webkit-search-cancel-button {
  display: none;
}

.message-wrapper .ql-container {
  height: auto !important;
  border: 0;
}

.message-wrapper .ql-editor:not(.ql-view) {
  height: auto !important;
  min-height: 36px !important;
  max-height: 120px;
  overflow-y: hidden;
}

.message-wrapper .ql-toolbar {
  border: 0;
}

.transition-transform {
  transition: transform 0.2s ease-in-out;
}

.rotate-90 {
  transform: rotate(90deg);
}

.django-quill-widget-container>.ql-toolbar:not(:first-child) {
  display: none !important;
}

.django-quill-widget-container>.django-quill-widget.ql-container:not(:last-of-type) {
  display: none !important;
}

/* Sticky header; the background bleeds to the container edges to hide overhanging badges. */
.smart-header {
  position: sticky;
  top: var(--header-h);
  z-index: 1020;
  background-color: var(--bs-body-bg);
  transition: transform 0.3s ease-in-out;
  margin-left: calc(-0.5 * var(--bs-gutter-x, 1.5rem));
  margin-right: calc(-0.5 * var(--bs-gutter-x, 1.5rem));
  padding-left: calc(0.5 * var(--bs-gutter-x, 1.5rem));
  padding-right: calc(0.5 * var(--bs-gutter-x, 1.5rem));
}

.smart-header--hidden {
  transform: translateY(-100%);
}

/* Workflow mark in a list page's title row. 40px spans .type-h2's 38.8px tallest word ink while
   staying under its 57.5px line box, so the mark never sets the row height. The size floor is the
   width/height attributes in list_page_title.html. No brightness() — this sits on bg-dark, not the
   white the search badge corrects for. */
.list-title-icon {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  object-fit: contain;
}

/* One height for every list toolbar control — the 35px the view toggles and the search results
   sort button already resolve to, so no toolbar borrows it from whichever child is tallest.
   Direct-child selectors: a bare .list-toolbar .btn would also hit the layout toggle, which takes
   its own 35px further down, and any control a dropdown menu holds. */
.list-toolbar > .input-group > .form-control,
.list-toolbar > .input-group > .input-group-text,
.list-toolbar > .dropdown > .btn {
  height: 35px;
}

/* Inter at 16px is a 24px line box, which does not fit 35px minus Bootstrap's .375rem block
   padding. An input centres its own value, so zero the padding rather than shrink the text.
   Buttons keep theirs — .btn is Rajdhani at line-height normal, which fits. */
.list-toolbar > .input-group > .form-control,
.list-toolbar > .input-group > .input-group-text {
  padding-top: 0;
  padding-bottom: 0;
}

/* Replaces #projectFilterForm .btn, which existed only because that row stretched its children. */
.list-toolbar > .dropdown > .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The create action takes the same 35px as a toolbar control, so a page header and the toolbar
   under it read as one system. py-2 resolved to 36px, which is off by one against every other
   control. The mobile rule below overrides this with a 36px square.

   Scoped to the sticky header: `.plus-btn` alone also reached the org profile's card-title button
   and the capabilities empty-state's large centred CTA, neither of which sits beside a toolbar
   control. `.smart-header` and not `#main-header` — an id would outrank the later `.btn.plus-btn`
   mobile rule and keep 35px where that has to win with a 36px square. */
.smart-header .plus-btn {
  height: 35px;
}

/* The step illustration inside a guide modal. One value restated inline at 118 call sites across
   16 *_guide_steps templates, where an inline font-size beats every class and cannot be retuned
   from here. Icon sizing, not type, so it is a component class rather than a step. */
.guide-step-icon {
  font-size: 2.5rem;
}

/* The guide trigger carries no box, so bi-question-square IS its visible edge — and that glyph
   draws at exactly 1.0x its font-size. Sizing the button alone left a 24px square beside a 35px
   create action, so the glyph is what has to take the token. Baseline keeps the old 1.5rem for any
   trigger outside a list header. */
.guide-trigger-btn > i {
  font-size: 1.5rem;
}

/* Scoped to #main-header: the chat header's copy sits beside a 24px icon button and would become
   the odd one out there instead. It passes icon_size explicitly, so it is unaffected either way. */
#main-header .guide-trigger-btn {
  height: 35px;
  justify-content: center;
}

#main-header .guide-trigger-btn > i {
  font-size: 35px;
}

#event-form label {
  font-family: "Rajdhani";
  font-style: normal;
  line-height: normal;
  text-transform: uppercase;
  font-size: 1.25rem;
}

/* .three-dots-menu is retired — it was the event card's 34x34 boxed kebab trigger. Kebab triggers
   carry no box, background or border; every one uses the same bare `btn btn-link p-0 text-light`
   classes. Do not reintroduce it. */

.picker-item {
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color 0.15s, border-color 0.15s;
}

.picker-item:hover {
  background-color: rgba(255, 255, 255, 0.05);
}

.picker-item.selected {
  background-color: rgba(255, 255, 255, 0.08);
  border-color: var(--bs-border-color);
}

.email-tag-input {
  min-width: 140px;
  flex: 1;
  outline: none;
  font-size: 0.85rem;
}

.tab-button.header-underline {
  background-color: transparent !important;
  border: none !important;
  color: var(--bs-secondary-color) !important;
  position: relative;
  transition: color 0.2s ease;
  text-decoration: none;
}

.tab-button.header-underline.active {
  color: var(--bs-primary) !important;
}

.tab-button.header-underline::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 5px;
  background-color: var(--bs-primary);
  border-radius: 10px;
  transform: scaleX(0);
  transition: transform 0.3s ease;
}

.tab-button.header-underline.active::after {
  transform: scaleX(1);
}

.nav-link:focus,
.nav-link:hover {
  color: var(--bs-primary);
}

.form-check-input:checked {
  background-color: var(--bs-primary) !important;
  border-color: var(--bs-primary) !important;
}

.form-check-input:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);
}

.btn-content-picker {
  background-color: var(--bs-card-cap-bg, rgba(var(--bs-body-color-rgb), 0.03)) !important;
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: white;
  border-radius: 12px;
  height: 220px;
  transition: all 0.2s ease;
}

.btn-content-picker:hover {
  background-color: rgba(var(--bs-body-color-rgb), 0.08) !important;
  border-color: rgba(255, 255, 255, 0.5);
}

.custom-dot {
  width: 12px;
  height: 12px;
  background-color: var(--bs-secondary);
  transition: all 0.2s ease-in-out;
}

.custom-dot.active {
  background-color: white;
  border: 3px solid #5bc0be;
  transform: scale(1.3);
}

.timeline-icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  z-index: 1;
}

.task-status-pill--sm {
  font-size: 0.72rem;
  text-transform: none;
}

.task-kanban-lane-count--todo {
  background-color: rgba(var(--bs-secondary-color-rgb), 0.3) !important;
  color: var(--bs-secondary-color);
  border: 1px solid var(--bs-secondary-color);
}

.task-kanban-lane-count--inprogress {
  background-color: rgba(var(--bs-primary-rgb), 0.3) !important;
  color: rgba(var(--bs-primary-rgb), 1);
  border: 1px solid rgba(var(--bs-primary-rgb), 1);
}

.task-kanban-lane-count--review {
  background-color: rgba(var(--bs-warning-rgb), 0.3) !important;
  color: rgba(var(--bs-warning-rgb), 1);
  border: 1px solid rgba(var(--bs-warning-rgb), 1);
}

.task-kanban-lane-count--completed {
  background-color: rgba(var(--bs-success-rgb), 0.3) !important;
  color: rgba(var(--bs-success-rgb), 1);
  border: 1px solid rgba(var(--bs-success-rgb), 1);
}

.activity-name {
  font-family: "Inter";
  font-size: 0.82rem;
  text-transform: none;
}

.activity-date {
  font-size: 0.75rem;
  text-transform: none;
}

.review-label {
  font-size: 0.82rem;
  letter-spacing: 0.06em;
}

/* A review decision option's title. Was an inline font-size repeated on every option, which
   §14.2 calls the most common reason a step "does not work" — and it had to be restated by hand
   for each new option. */
.review-decision-label {
  font-size: 0.79rem;
}

/* A decision group's validation message. Same size the showcase modal's own already used, as a
   class rather than the inline font-size it had. */
.review-decision-error {
  font-size: 0.78rem;
}

.status-banner-review {
  background-color: rgba(var(--bs-warning-rgb), 0.55);
  border-color: rgba(245, 158, 11, 1);

  background-color: rgba(var(--bs-warning-rgb), 0.2);
  border-color: rgba(var(--bs-warning-rgb), 1);
  border-style: solid;
  border-width: 1px;
}

.status-banner-changes {
  background-color: rgba(var(--bs-danger-rgb), 0.55);
  border-color: rgba(239, 68, 68, 1);
  border-style: solid;
  border-width: 1px;
}

.status-banner-approved {
  background-color: rgba(var(--bs-success-rgb), 0.55);
  border-color: rgba(34, 197, 94, 1);
  border-style: solid;
  border-width: 1px;
}

.showcase-new-block-btn {
  font-size: 15px;
  border: 1px dashed var(--bs-primary);
  border-radius: 0.375rem;
  cursor: pointer;
  background-color: rgba(41, 186, 178, 0.03);
  transition:
    background-color 0.15s ease-in-out,
    border-color 0.15s ease-in-out;
}

.showcase-new-block-btn:hover {
  background-color: rgba(41, 186, 178, 0.08);
  border-color: var(--bs-link-hover-color);
}

.showcase-cancel-btn:hover {
  font-size: 15px;
  background-color: #5c2d33 !important;
  border-color: #7a3d44 !important;
  color: #d9a0a6 !important;
}

/* Carousel Filmstrip Styles */
.fs-slot {
  flex: 1 1 0;
  min-width: 68px;
  max-width: 110px;
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  position: relative;
  transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
  user-select: none;
  -webkit-user-select: none;
  flex-shrink: 0;
}

.fs-slot.fs-filled {
  border: 1px solid rgba(255, 255, 255, .12);
  overflow: hidden;
  cursor: grab;
}

.fs-slot.fs-filled:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .55);
  border-color: rgba(255, 255, 255, .25);
  z-index: 1;
}

.fs-slot.fs-filled:active {
  cursor: grabbing;
}

.fs-slot.fs-filled.fs-dragging {
  opacity: .3;
  transform: scale(.92);
  box-shadow: none;
}

.fs-slot.fs-filled.fs-drop-before {
  box-shadow: -3px 0 0 0 var(--bs-primary);
}

.fs-slot.fs-filled.fs-drop-after {
  box-shadow: 3px 0 0 0 var(--bs-primary);
}

.fs-slot.fs-empty {
  border: 1.5px dashed rgba(255, 255, 255, .12);
  background: rgba(255, 255, 255, .02);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

.fs-slot.fs-upload {
  border-color: var(--bs-primary);
  background: rgba(13, 110, 253, .05);
  cursor: pointer;
}

.fs-slot.fs-upload:hover,
.fs-slot.fs-upload:focus-visible {
  background: rgba(13, 110, 253, .12);
  outline: none;
}

.fs-slot.fs-upload.fs-dragover {
  background: rgba(13, 110, 253, .2);
  border-style: solid;
}

.fs-badge {
  position: absolute;
  top: 5px;
  left: 5px;
  background: rgba(0, 0, 0, .6);
  color: #fff;
  border-radius: 4px;
  font-size: .58rem;
  font-weight: 700;
  line-height: 1;
  padding: 2px 5px;
  letter-spacing: .06em;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
  z-index: 2;
}

.fs-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  background: rgba(0, 0, 0, .65);
  border: none;
  color: rgba(255, 255, 255, .85);
  border-radius: 4px;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .68rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, background .1s ease;
  z-index: 3;
  padding: 0;
  line-height: 1;
}

.fs-slot.fs-filled:hover .fs-remove {
  opacity: 1;
}

.fs-remove:hover {
  background: rgba(220, 53, 69, .85);
}

.fs-drag-hint {
  position: absolute;
  bottom: 5px;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, .4);
  font-size: .65rem;
  opacity: 0;
  transition: opacity .12s ease;
  pointer-events: none;
  z-index: 2;
  line-height: 1;
}

.fs-slot.fs-filled:hover .fs-drag-hint {
  opacity: 1;
}

.fs-slot-num {
  position: absolute;
  top: 5px;
  left: 7px;
  font-size: .58rem;
  font-weight: 600;
  letter-spacing: .08em;
  opacity: .28;
  color: #fff;
  pointer-events: none;
}

/* Static sidebar */
:root {
  --sidebar-w: 100px;
  /* Tall enough to leave real clearance around the 30px global search bar the desktop
     header centres (it had 3px a side at 36px, which read as the bar being jammed into
     the chrome). Everything else that keys off the header follows automatically — the
     body's padding-top, the sidebar's top/height, and the sticky offsets — so this
     variable is the only knob. The mobile override below is separate on purpose: the
     search bar lives in an offcanvas there, not in the header. */
  --header-h: 48px;
  --tab-bar-h: 60px;
  --showcase-row-w: 1250px;
}

.main-content {
  margin-left: var(--sidebar-w);
  padding-top: var(--header-h);
}

/* Pending actions banners (shared component — company + PM home) */
.pending-action-row {
  transition: background 0.15s ease;
}

.pending-action-row:hover {
  background: rgba(var(--bs-primary-rgb), 0.07) !important;
}

.pending-action-row:hover .pending-action-review {
  background: rgba(var(--bs-primary-rgb), 0.2) !important;
}

.pending-actions-banner-toggle[aria-expanded="true"] .pending-action-chevron {
  transform: rotate(0deg);
}

.pending-actions-banner-toggle[aria-expanded="false"] .pending-action-chevron {
  transform: rotate(180deg);
}


#admin-company-list .list-group-item.active .selected-check {
  opacity: 1 !important;
}

/* Shared card typography. `.card-subheading`, not `.card-subtitle` — Bootstrap owns that. */
.card-heading {
  font-family: "Rajdhani", sans-serif;
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.15;
  /* Clamp to 3 lines + ellipsis; title must not also carry .d-block or .text-truncate. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  line-clamp: 3;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

.card-subheading {
  font-family: "Rajdhani", sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  line-height: 1.3;
  letter-spacing: 0.02em;
}

/* Dark pill for a card-subheading stat (e.g. an item count) — same type scale as its
   siblings so it lines up, just boxed in the design's #141920 (--bs-dark). */
.card-meta-pill {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  background-color: var(--bs-dark);
  font-family: "Rajdhani", sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  line-height: 1;
}

.card-desc {
  font-family: "Inter", sans-serif;
  font-weight: 400;
  font-size: 0.8125rem;
  line-height: 1.45;
}

/* Project card participant stack — up to three overlapping org avatars then a "+N".
   26px to match .card-status-pill, so the stack and the PAST DUE pill beside it agree on the
   card row's height instead of one of them setting it. */
.card-avatar-stack {
  display: inline-flex;
  align-items: center;
  height: 26px;
}

.card-avatar-stack-avatar {
  width: 26px;
  height: 26px;
  /* A ring in the card's own background, so overlapping circles stay separable. box-shadow
     rather than a border: a border would shrink the logo inside the same 26px box, and these
     are already small. */
  box-shadow: 0 0 0 2px var(--bs-body-bg);
}

/* The overlap. Applied from the second avatar on, so the stack's left edge stays put however
   many it holds — and paints in DOM order, leaving the last avatar on top. */
.card-avatar-stack-avatar + .card-avatar-stack-avatar {
  margin-left: -8px;
}

/* Clears the ring the last avatar paints over its own right edge. */
.card-avatar-stack-overflow {
  margin-left: 6px;
}

/* A thin rail with a badge centered on the end of the completed portion.
   Vertical overflow lets the badge straddle the rail and the list card edge. */
.card-progress {
  --bs-progress-height: var(--card-progress-h, 3px);
  --bs-progress-bg: var(--bs-dark);
  --bs-progress-bar-bg: var(--Color-brand-Ion-Orange, #FF9500);
  position: relative;
  overflow: visible;
  border-radius: 0;
}

.card-progress-label {
  /* Reserve room for 100% and clamp the center to keep the whole badge on the rail. */
  --card-progress-label-width: 2.25rem;
  width: var(--card-progress-label-width);
  box-sizing: border-box;
  position: absolute;
  z-index: 1;
  top: 50%;
  left: clamp(
    calc(var(--card-progress-label-width) / 2),
    var(--card-progress-value, 0%),
    calc(100% - var(--card-progress-label-width) / 2)
  );
  transform: translate(-50%, -50%);
  padding: 0.1rem 0.25rem;
  border: 1px solid var(--bs-progress-bar-bg);
  border-radius: 0.375rem;
  background-color: #65430f;
  color: #fff;
  font-family: "Rajdhani", sans-serif;
  font-size: 0.75rem;
  line-height: 1;
  white-space: nowrap;
}

/* List-card variant: pinned flush across the foot of the body column, so it starts where the
   banner ends. Absolutely positioned rather than made a flow row of its own, because
   .card-list-body is the padded element here — a media query re-sets that padding below sm —
   and splitting the padding off it to get a flush child would mean forking that rule too.
   left/right/bottom resolve against the column's padding box, which carries no border, so this
   is its full width and its true bottom edge. */
.card-progress--flush {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
}

/* Shared min-height so a completed list card is exactly as tall as an active one. */
/* Takes the width beside the banner; basis must be 0 or the banner wraps to its own row. */
.card-list-body {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
  min-height: var(--card-list-h, 6rem);
}

/* Below sm, stack metadata under the title: min-width:0 let it shrink below min-content. */
.card-list-body--stack-sm {
  column-gap: 1rem;
}

@media (max-width: 575.98px) {
  .card-list-body--stack-sm {
    flex-wrap: wrap;
    row-gap: 0.25rem;
  }

  .card-list-body--stack-sm > .card-list-main {
    flex: 1 0 100%;
  }
}

/* Gold frame as a gradient border, because border-image ignores border-radius. */
.card-gold {
  border: 3px solid transparent;
  border-radius: var(--bs-border-radius);
  background-image:
    linear-gradient(var(--bs-body-bg), var(--bs-body-bg)),
    linear-gradient(135deg,
      #6f6428 0%,
      #d8cc9c 18%,
      #fcf0cc 34%,
      #a89c48 52%,
      #f0e4b4 70%,
      #d8cc9c 86%,
      #6f6428 100%);
  background-origin: border-box;
  background-clip: padding-box, border-box;
}

/* The medal caps the right end, so that corner is square; the border keeps its width. */
.card-gold--capped {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  margin-right: var(--card-border-trim);
}

/* A completed card must not clip overflow — the medal overhangs it. */
/* Sized by height, width:auto — the artwork is 782x1080 and a square box would squash it. */
.card-host {
  --card-list-h: 6rem;
  /* Progress bar geometry. Here rather than on .card-progress because the list card's body has
     to reserve the same height (see .card-list-body--progress-foot), and custom properties only
     inherit downward. */
  --card-progress-h: 3px;
  --card-medal-h: 6.5rem;
  --card-medal-w: 4.7rem;
  /* % of medal width poking past the card edge. Tweak to nudge the medal left/right. */
  --card-medal-offset: 50%;
  /* Border trim so the medal's pointy tip fully covers the corner. */
  --card-border-trim: 2px;
  /* Must exceed the medal's reach over the card. Hand-tuned, not derived. */
  --card-pill-clearance: 3.5rem;
  /* Hover-lift shadow lives here, not on .card-modern: filter is applied to an element's box
     BEFORE its own clip-path/mask, so a drop-shadow on the same element the showcase notch or
     capability chamfer clips (.card-modern) gets clipped away identically to how box-shadow
     did. The host is never itself clipped, so the shadow survives — see .card-host:hover. */
  transition: filter 0.25s ease;
}

/* Below sm the horizontal layout is kept by scaling the card down rather than stacking it. */
@media (max-width: 575.98px) {
  .card-host {
    --card-list-h: 4.5rem;
    --card-medal-h: 5rem;
    --card-medal-w: 3.6rem;
    --card-pill-clearance: 4rem;
  }

  .card-list-body {
    padding: 0.625rem 0.75rem;
  }
}

/* Reserve the medal's overhang inside the column; unreserved it overlapped the next card. */
.card-host--medal-gutter {
  --card-medal-gutter: 1.8rem;
  padding-right: var(--card-medal-gutter);
}

.card-medal {
  position: absolute;
  z-index: 2;
  width: auto;
  pointer-events: none;
}

/* Event medal is a different shape; width follows it. Set on the HOST. */
.card-host.card-host--event-medal {
  --card-medal-w: 6rem;
}

@media (max-width: 575.98px) {
  /* Tracks the smaller --card-medal-h at this breakpoint; only the width changes. */
  .card-host.card-host--event-medal {
    --card-medal-w: 4.6rem;
  }
}

/* right cancels the card's border-trim margin so the medal's position stays independent of it. */
.card-list-medal {
  top: 50%;
  right: calc(-1 * var(--card-border-trim));
  width: var(--card-medal-w);
  height: var(--card-medal-h);
  transform: translate(var(--card-medal-offset), -50%);
}

/* The flush bar is absolutely positioned, so it overlays the body's own bottom padding and the
   content ends up sitting on it. Give that padding back, so the row stays optically centred in
   the space above the bar rather than pressed against it. */
.card-list-body--progress-foot {
  padding-bottom: calc(var(--bs-card-spacer-y, 1rem) + var(--card-progress-h, 3px));
}

/* Keeps the COMPLETED pill clear of the medal, which reaches ~38px back over the card. */
.card-list-body--medal {
  padding-right: var(--card-pill-clearance);
}

/* Grid: top-right corner, centred on the card's top edge; z-index 3 keeps it over the pill. */
.card-grid-medal {
  top: 0;
  right: 0;
  height: 2.5rem;
  z-index: 3;
  transform: translate(28%, -50%);
}

/* Straddles the card's top edge, so it must be a child of the CARD; the media box clips. */
.card-edge-pill {
  position: absolute;
  top: 0;
  right: 1rem;
  z-index: 2;
  transform: translateY(-50%);
}

/* Extra inset for a pill sharing the corner with a medal. Keyed on the medal, not COMPLETED. */
.card-edge-pill--medal {
  right: 1.75rem;
}

/*
 * Bootstrap's .carousel-inner is overflow:hidden on both axes, so a .card-edge-pill
 * straddling the card's top edge gets clipped instead of overhanging like it does
 * everywhere else (the plain grid/list pages have no clipping ancestor). This carousel
 * (#ongoing-events-carousel, shared markup on every event home-dashboard carousel) has no
 * prev/next controls and data-bs-interval="false", so nothing ever triggers the slide
 * transform overflow:hidden exists to hide — safe to open it up so the pill overhangs
 * exactly like the non-carousel cards, instead of shrinking the pill's own offset inward.
 * margin-top gives the freed overhang room instead of colliding with whatever sits above.
 */
#ongoing-events-carousel.carousel {
  margin-top: 0.75rem;
}

#ongoing-events-carousel .carousel-inner {
  overflow: visible;
}

/* Active mode reads as primary. :checked for the project list, .active for the event list. */
.card-view-toggle .btn-check:checked + .btn,
.card-view-toggle .btn.active {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
}

/* A view toggle's .btn centres its own icon. (The projects-toolbar arms went with that row's
   align-items-stretch — .list-toolbar declares the heights instead.) */
.srf-view-toggle .btn,
.card-view-toggle .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* List/grid toggles — one box across search, projects and events. Height is declared on the toggle
   AND the sort dropdown so an icon line box and a text line box cannot resolve differently; width
   is left to default .btn padding, so never add a px-/py- utility (!important + border-box). */
#srf-sort-toggle,
.srf-view-toggle .btn,
.card-view-toggle .btn {
  height: 35px;
}

/* Guards a toggle in any flex row that stretches its children. The projects toolbar no longer
   does, but the search results header still resolves through this. */
.srf-view-toggle,
.card-view-toggle {
  align-self: center;
}

/* Hide the layout toggle in the Companies view_mode — it renders accordion groups, not cards, and
   ignores card_mode. CSS not a template {% if %}, since only #search-results swaps on view_mode
   change. */
#searchForm:has(#mode-company:checked) .card-view-toggle {
  display: none;
}

/* Absolute in the media column so height resolves there; Safari else sizes it to the viewport. */
.card-banner-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Solid placeholder so a not-yet-decoded banner reads as part of the card rather than a hole. */
  background-color: var(--bs-body-bg);
  transition: transform 0.4s ease;
}

/* Media column positions and clips the image; it needs an explicit height when stacked. */
.card-list-media {
  position: relative;
  overflow: hidden;
  width: calc(var(--card-list-h, 6rem) * 4 / 3);
  height: auto;
  min-height: var(--card-list-h, 6rem);
  align-self: stretch;
  border-radius: var(--card-inner-radius) 0 0 var(--card-inner-radius);
}

/* Lift-and-zoom hover for the horizontal project cards. */
.card-modern {
  transition: transform 0.25s ease;
  /* Banner inner radius = outer radius minus border width, so the curves are concentric. The
     compound selector below carries the gold card's thicker border, specificity-based rather than
     order-based so moving either rule can't silently flip which width wins. */
  --card-border-w: 1px;
  --card-inner-radius: calc(var(--bs-border-radius) - var(--card-border-w));
}

.card-modern.card-gold {
  --card-border-w: 3px;
}

/* Hover lift is driven from .card-host: the count badge is a SIBLING of the card link, so off the
   card's own :hover the badge stayed put while the card rose. The shadow lives on .card-host too —
   see the comment there. */
.card-host:hover .card-modern {
  transform: translateY(-4px);
}

.card-host:hover {
  filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
}

/* The home dashboards' ongoing-events carousel clipped both halves of that hover: Bootstrap's
   .carousel-inner is overflow: hidden, and the cards sit flush against its top edge, so the 4px
   lift was cut off and the drop-shadow never painted at all — the same card hovered correctly on
   the events page and beside the project cards below it. Clipping is only needed while a slide is
   actually moving (the incoming/outgoing .carousel-item is translated outside the box), so it is
   restored for exactly that, rather than traded away. */
#ongoing-events-carousel .carousel-inner {
  overflow: visible;
}

#ongoing-events-carousel .carousel-inner:has(.carousel-item-next, .carousel-item-prev) {
  overflow: hidden;
}

/* Drops the hovered card's shadow below its neighbours instead of bleeding across them (hosts
   paint in DOM order otherwise). Scoped off .card-host so the project/event medal overhang, which
   relies on that NOT being a stacking context, is unaffected. */
.capability-card-host,
.showcase-card-host {
  position: relative;
  z-index: 1;
}

.capability-card-host:hover,
.showcase-card-host:hover {
  z-index: 0;
}

.card-host:hover .card-banner-img {
  transform: scale(1.06);
}

/* Straddles the top-right corner. Its transform IS the positioning, so the lift composes. */
.card-count-badge {
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(50%, -50%);
  transition: transform 0.25s ease;
}

/* Re-anchor to the CARD's edge — `right` resolves against the padding box, not the gutter.
   Qualified on the medal being present, because the rule further down drops the padding when it
   is not: unqualified, this subtracted a gutter that was no longer reserved and pulled the badge
   1.8rem INSIDE the card instead of straddling its corner. Today no card renders both (a badge is
   gated to active cards, a medal to completed ones), so this only ever fires if that changes. */
.card-host--medal-gutter:has(.card-medal) .card-count-badge {
  right: var(--card-medal-gutter);
}

/* 29px circle. Qualified with .badge to outrank .notification-count-badge, defined later. */
.badge.card-count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.5rem;
  min-width: 1.5rem;
  padding: 0 0.4rem;
  font-family: "Rajdhani", sans-serif;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
}

.card-host:hover .card-count-badge {
  transform: translate(50%, calc(-50% - 4px));
}

/* Owning-company line on a capability card (list row and grid card, one partial:
   partials/project/capability_card_company.html). Type and colour come from .card-subheading
   .text-secondary, shared with showcase_card.html's org line and event_card_location.html's
   venue — this class only adds the spacing above it.

   It was briefly a bare logo pinned to the bottom-RIGHT corner. Two things were wrong with
   that and are worth recording, because both are traps for the next corner ornament:
   .card-chamfered clips a 12px diagonal off every corner, so a mark flush to one is cut away
   by the clip-path; and in the grid card the mark sat inside .card-body's 1rem padding while
   the list row's did not, so the two layouts disagreed about where the corner was by 16px.
   Left-aligned under the title, both align to the CONTENT instead and neither applies.

   Still a flex item in normal flow, never absolutely positioned — the title is a 3-line clamp
   that can fill the card, and an out-of-flow line would sit on top of it.

   The gap from the title is PADDING, not margin, and that is not interchangeable here: the grid
   card puts Bootstrap's `mt-auto` on this same element to drop it to the foot of a stretched
   card, and the utility is `!important`, so a `margin-top` would be silently dropped. It would
   be dropped exactly when it matters most — `mt-auto` only produces a gap out of SPARE height,
   and a title long enough to fill the body leaves none, so the line would sit flush against the
   last line of the title on precisely the cards that are hardest to read. As padding it is a
   floor the auto margin adds to rather than replaces. */
.capability-card-company {
  padding-top: 0.875rem;
}

/* Grid-mode card: the banner caps the card at the design's 315x235 ratio, with the body below. */
.card-grid-media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 315 / 235;
  border-radius: var(--card-inner-radius) var(--card-inner-radius) 0 0;
}

/* Org/person grid card: same 315x235 media slot as .card-grid-media (so it lines up in a
   row with project/event/capability cards), but centers a padded circular avatar/logo
   instead of an edge-to-edge banner — org/person have no banner image of their own. */
.card-grid-media--circle {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Org/person avatar inside .card-grid-media--circle. Sized as a percentage of the
   media slot's own width rather than a fixed px value: .card-grid-media's height is
   locked to it via aspect-ratio (315/235), so a fixed-size circle stops fitting once a
   narrow grid column shrinks the slot below that height, and gets clipped top/bottom
   by the slot's overflow:hidden. width% + aspect-ratio:1 keeps it circular and scales
   it down in lockstep with the slot at every column width; max-width caps it at the
   size it renders at today's typical card width. */
.card-grid-avatar {
  width: 70%;
  aspect-ratio: 1;
  max-width: 190px;
}

/* border-* can't follow a cut corner, so the card's own background IS the border, clipped to an
   octagon with 1px padding; .card-chamfered-inner paints the surface over a smaller, inset
   octagon. Inner chamfer = outer minus p×(2−√2) (≈0.5858×p) — a diagonal edge inset by p shifts
   p×√2, not p. */
.card-chamfered {
  --card-chamfer: 12px;
  position: relative;
  padding: 1px;
  background: #343942;
  border: none;
  clip-path: polygon(
    var(--card-chamfer) 0,
    calc(100% - var(--card-chamfer)) 0,
    100% var(--card-chamfer),
    100% calc(100% - var(--card-chamfer)),
    calc(100% - var(--card-chamfer)) 100%,
    var(--card-chamfer) 100%,
    0 calc(100% - var(--card-chamfer)),
    0 var(--card-chamfer)
  );
}

/* Inner surface for a card whose 1px frame is its own clipped background (chamfered capability
   card, notched showcase row). Must be clipped to the inset shape, not just padded — padding
   alone leaves an opaque child (the banner) covering the frame at a cut/notched corner. */
.card-chamfered-inner,
.showcase-card-inner,
.showcase-row-inner {
  flex: 1 1 auto;
  min-width: 0;
  background: #222832;
}

.card-chamfered-inner {
  clip-path: polygon(
    calc(var(--card-chamfer) - 0.5858px) 0,
    calc(100% - var(--card-chamfer) + 0.5858px) 0,
    100% calc(var(--card-chamfer) - 0.5858px),
    100% calc(100% - var(--card-chamfer) + 0.5858px),
    calc(100% - var(--card-chamfer) + 0.5858px) 100%,
    calc(var(--card-chamfer) - 0.5858px) 100%,
    0 calc(100% - var(--card-chamfer) + 0.5858px),
    0 calc(var(--card-chamfer) - 0.5858px)
  );
}


@media (prefers-reduced-motion: reduce) {

  .card-modern,
  .card-host:hover .card-modern,
  .capability-picker-card:hover .card-modern,
  .card-banner-img,
  .card-host:hover .card-banner-img,
  .capability-picker-card:hover .card-banner-img,
  .showcase-card-kebab-anchor,
  .card-host:hover .showcase-card-kebab-anchor {
    transition: none;
    transform: none;
  }

  /* The lift's shadow still applies on hover under reduced motion (a static effect, not motion) —
     only the transition is dropped, same as every other rule in this block. */
  .card-host {
    transition: none;
  }

  /* The badge's transform IS its positioning, so it keeps it — only the lift is dropped.
     transform: none would shove it out of the card's corner. Same for the kebab anchors below. */
  .card-count-badge,
  .card-host:hover .card-count-badge {
    transition: none;
    transform: translate(50%, -50%);
  }

  .showcase-row-kebab-anchor,
  .card-host:hover .showcase-row-kebab-anchor {
    transition: none;
    transform: translateY(-50%);
  }

  .capability-picker-add,
  .capability-picker-card:hover .capability-picker-add {
    transition: none;
    transform: translateY(-50%);
  }
}

/* Raise a card's chrome above the title's .stretched-link ::after, which swallows clicks. */
.card-raised {
  position: relative;
  z-index: 2;
}

/* A card with its kebab menu open must outrank later siblings, which paint over it. */
.card-host:has(.dropdown-menu.show) {
  z-index: 5;
}

/* Event/showcase/collection card kebabs reveal on hover, focus or open — a permanently visible dot competes with the banner art and status pills. :focus-within/:has(.show) hold it visible while the menu is open, after the pointer leaves. */
.card-host .event-card-kebab,
.card-host .collection-card-kebab {
  opacity: 0;
  transition: opacity 0.15s ease-in-out;
}

.card-host:hover .event-card-kebab,
.card-host:focus-within .event-card-kebab,
.card-host:has(.dropdown-menu.show) .event-card-kebab,
.card-host:hover .collection-card-kebab,
.card-host:focus-within .collection-card-kebab,
.card-host:has(.dropdown-menu.show) .collection-card-kebab {
  opacity: 1;
}

/* Card metadata glyphs read heavier than their label at a shared size, so step them down. */
.card-meta-icon {
  font-size: 0.75rem;
}

/* Mobile: hide sidebar, show bottom tab bar */
@media (max-width: 767.98px) {
  :root {
    --header-h: 42px;
    --tabs-visible: 4;
  }

  .sidebar {
    display: none !important;
  }

  .main-content {
    margin-left: 0 !important;
    padding-top: var(--header-h) !important;
    padding-bottom: calc(var(--tab-bar-h) + 4px + env(safe-area-inset-bottom));
  }

  .bottom-tab-bar {
    display: flex;
  }

  .mobile-header {
    height: 42px;
    padding-top: 0;
    padding-bottom: 0;
    align-items: center;
  }

  .body,
  body {
    padding-top: 0;
    overflow-x: hidden;
  }

  .btn-reactivate {
    font-size: 0.6rem;
    padding: 3px 8px;
  }

  /* `.btn.` prefix: the CTA rule is (0,2,0), and specificity beats order even in a media query. */
  .btn.invite-action-btn {
    font-size: 0.75rem;
    padding: 8px 16px;
    min-height: 36px;
  }

  .container-fluid {
    padding-left: 8px;
    padding-right: 8px;
  }

  .container {
    padding-left: 8px;
    padding-right: 8px;
  }

  .card-body {
    padding: 0.65rem;
  }

  .full-height-container {
    height: calc(100vh - var(--header-h) - (var(--tab-bar-h) + env(safe-area-inset-bottom)) - var(--back-link-h, 0px));
    overflow: hidden !important;
  }

  /* `.btn.` prefix as above — icon-only on mobile, so the CTA size would overflow the box. */
  .btn.plus-btn {
    width: 36px;
    height: 36px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
  }

  .rhombus-btn {
    font-size: 0.8rem;
  }

  #project-tabs .nav {
    font-size: 0.8rem;
    white-space: nowrap;
  }

  .conversation-sidebar-title {
    font-size: 14px;
  }
}

/* Horizontal scroll utility */
.overflow-x-auto {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Tablet: consistent container gutter */
@media (min-width: 768px) and (max-width: 991.98px) {
  .container {
    padding-left: 12px;
    padding-right: 12px;
  }

  .container-fluid {
    padding-left: 12px;
    padding-right: 12px;
  }
}

/* Bottom tab bar */
.bottom-tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(var(--tab-bar-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background-color: var(--bs-dark);
  border-top: 1px solid var(--bs-border-color);
  align-items: stretch;
  justify-content: flex-start;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  scroll-snap-type: x mandatory;
  z-index: 1030;
}

.bottom-tab-bar::-webkit-scrollbar {
  display: none;
}

/* Horizontal scroll indicator for bottom nav */
.bottom-tab-bar-scroll-indicator {
  position: absolute;
  bottom: 18px;
  right: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: auto;
  font-size: 18px;
  color: var(--bs-primary);
  cursor: pointer;
}

.bottom-tab-bar-scroll-indicator::before {
  content: "›";
  display: block;
  line-height: 1;
  animation: pulse-scroll-arrow 1.5s ease-in-out infinite;
}

.bottom-tab-bar-scroll-indicator.visible {
  opacity: 0.9;
}

@keyframes pulse-scroll-arrow {

  0%,
  100% {
    opacity: 0.4;
  }

  50% {
    opacity: 1;
  }
}

.bottom-tab-bar-scroll-indicator-left {
  position: absolute;
  bottom: 18px;
  left: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: auto;
  font-size: 18px;
  color: var(--bs-primary);
  cursor: pointer;
}

.bottom-tab-bar-scroll-indicator-left::before {
  content: "‹";
  display: block;
  line-height: 1;
  animation: pulse-scroll-arrow-left 1.5s ease-in-out infinite;
}

.bottom-tab-bar-scroll-indicator-left.visible {
  opacity: 0.9;
}

@keyframes pulse-scroll-arrow-left {

  0%,
  100% {
    opacity: 0.4;
  }

  50% {
    opacity: 1;
  }
}


.bottom-tab {
  flex: 0 0 auto;
  width: calc(100vw / var(--tabs-visible, 4));
  min-width: 64px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.55);
  text-decoration: none;
  font-size: 0.6rem;
  font-family: "Rajdhani";
  text-transform: uppercase;
  gap: 3px;
  padding: 6px 2px 4px;
  transition: color 0.15s;
}

.bottom-tab.active {
  color: var(--bs-primary);
}

.bottom-tab:hover {
  color: var(--bs-link-hover-color);
}

.notification-count-badge {
  background-color: var(--bs-primary);
  color: #fff;
  font-size: 12px;
  line-height: 1.2;
  padding: 2px 6px;
  min-width: 18px;
  text-align: center;
}

.htmx-request.htmx-indicator {
  opacity: 1;
}

/* Swap cross-fade allowlist — a new target gets no transition until added here. */
#search-results.htmx-swapping,
#project-list-content.htmx-swapping,
#event-sections.htmx-swapping,
#collection-sections.htmx-swapping,
#conversation-search-results.htmx-swapping,
#user-search-results.htmx-swapping,
#member-table-body.htmx-swapping,
#showcase-list-content.htmx-swapping {
  opacity: 0;
  transition: opacity 150ms ease-out;
}

#search-results .htmx-added,
#project-list-content .htmx-added,
#event-sections .htmx-added,
#collection-sections .htmx-added,
#conversation-search-results .htmx-added,
#user-search-results .htmx-added,
#member-table-body .htmx-added,
#showcase-list-content .htmx-added {
  opacity: 0;
  transition: opacity 20ms ease-in;
}

.media-preview-trigger .preview-expand-icon {
  opacity: 0;
  transition: opacity 0.15s;
}

.media-preview-trigger:hover .preview-expand-icon {
  opacity: 1;
}

#media-preview-body {
  display: flex;
  align-items: center;
  justify-content: center;
}

#media-preview-body img,
#media-preview-body video {
  max-width: 100%;
  max-height: 80vh;
}

.modal .card {
  transition: none;
  transform: none;
  box-shadow: none;
}

/* Cards in the showcase link pickers. One fixed size for both, so an event card (banner,
   name, date pill, button) and a project card (the same minus the pill) come out
   identical — the body centres its contents instead of stacking from the top. */
.link-picker-card {
  display: flex;
  flex-direction: column;
  height: 260px;
}

.link-picker-card__banner {
  flex: 0 0 100px;
  overflow: hidden;
}

.link-picker-card__banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* bg-body is the surface every other card sits on, so the border is what separates these
   from the panel. Centred on both axes because the height is fixed: a missing date pill
   must recentre the group rather than leave dead space under it. */
.link-picker-card__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: var(--bs-body-bg);
}

/* Two lines, then ellipsis — the -webkit- pair is what implements line-clamp today. The
   width gives it something to clamp against inside the centred column; min-height holds
   both lines open (3em at line-height 1.5) so a one-line name cannot shift the pill. */
.link-picker-card__title {
  display: -webkit-box;
  width: 100%;
  min-height: 3em;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  font-size: 0.9375rem;
  letter-spacing: 0.05em;
}


.event-section-label {
  font-size: 12px;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

.event-empty-icon {
  font-size: 50px;
}

.sc-drag-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
  cursor: grab;
  color: rgba(255, 255, 255, .4);
  font-size: .9rem;
  z-index: 11;
  user-select: none;
  -webkit-user-select: none;
}

.sc-block:hover .sc-drag-handle {
  opacity: 1;
  pointer-events: auto;
}

.sc-drag-handle:active {
  cursor: grabbing;
}

.sc-block.sc-dragging {
  opacity: 0.25;
}

.sc-drop-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 3px;
  border-radius: 2px;
  background: var(--bs-primary);
  pointer-events: none;
  z-index: 20;
}


#add-to-collection-modal .atc-row:has(input:checked) {
  background-color: rgba(255, 255, 255, 0.06);
  border-color: var(--bs-primary) !important;
}

#add-to-collection-modal .atc-note-wrapper {
  display: none;
}

#add-to-collection-modal .atc-row:has(input:checked) .atc-note-wrapper {
  display: block;
}

.collection-note-accordion .accordion-button::after {
  margin-right: 0;
}

/* The "Added" chip/kebab sits outside each catalog card's own .card-host (which
   project/event/capability/showcase own internally), so it can't ride that CSS's
   .card-host:hover lift — nor can the note footer below, a sibling of the whole card
   block. Lift card + chip + note together off the shared outer wrapper instead, and
   cancel the nested .card-host's own lift/shadow so hovering the card directly doesn't
   compound into a double lift. */
.collection-item-card,
.collection-item-note {
  transition: transform 0.25s ease;
}

.collection-item-wrap:hover .collection-item-card {
  transform: translateY(-4px);
  filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
}

.collection-item-wrap:hover .collection-item-note {
  transform: translateY(-4px);
}

.collection-item-card .card-host:hover .card-modern {
  transform: none;
}

.collection-item-card .card-host:hover {
  filter: none;
}

/* The hover-lift transform above promotes the card to its own stacking context, so an open
   kebab dropdown paints under the note (a later sibling) unless outranked explicitly — same
   fix as .card-host:has(.dropdown-menu.show) elsewhere in this file. */
.collection-item-card:has(.dropdown-menu.show) {
  z-index: 5;
}

/* event_card.html adds .card-host--medal-gutter in list mode for every event, past or not, but
   only a past event's card (event_card_list_completed.html) actually renders a .card-medal —
   an upcoming event reserves the same 1.8rem gutter for a medal it will never show. Drop the
   reservation when there's nothing to protect, so the card matches the note footer's width. */
.card-host--medal-gutter:not(:has(.card-medal)) {
  padding-right: 0;
}

/* List mode only, when a medal IS present: the chip is a sibling of the host, anchored to the
   outer wrapper's full-width edge, so it needs its own inset past the medal artwork (which
   overhangs further than the reserved padding). Custom properties don't inherit across siblings,
   so this repeats the geometry rather than reading it; the event rule comes second to win on a
   host carrying both classes (.card-host--event-medal alone would also match event's grid host,
   so it's paired with --medal-gutter to stay list-scoped). */
.card-host--medal-gutter:has(.card-medal) ~ .collection-item-chip {
  right: 3.5rem !important;
}

.card-host--event-medal.card-host--medal-gutter:has(.card-medal) ~ .collection-item-chip {
  right: 3.5rem !important;
}

/* .collection-item-card is a flex-column (card above note), so a direct child's height is
   governed by flex-grow, not the cross-axis "stretch" event_card.html/showcase_card.html's
   .card-host relies on elsewhere. Without this they'd sit at natural height while the outer
   wrapper stretches to the row's tallest sibling, detaching the note below a shorter card.
   No-op for project (already carries flex-grow-1 inline) and capability (not a direct child). */
.collection-item-card > .card-host {
  flex-grow: 1;
}

/* Same reveal-on-hover pattern as .event-card-kebab/.collection-card-kebab above.
   :has(.dropdown-menu.show) holds it visible while its own menu is open after the pointer leaves.
.collection-item-chip {
  opacity: 0;
  transition: opacity 0.15s ease-in-out;
}

.collection-item-wrap:hover .collection-item-chip,
.collection-item-wrap:has(:focus-visible) .collection-item-chip,
.collection-item-card:has(.dropdown-menu.show) .collection-item-chip {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .collection-item-card,
  .collection-item-wrap:hover .collection-item-card,
  .collection-item-note,
  .collection-item-wrap:hover .collection-item-note {
    transition: none;
    transform: none;
  }
}

#add-to-collection-modal .atc-new-toggle~.atc-new-wrapper {
  display: none;
}

#add-to-collection-modal .atc-new-toggle:checked~.atc-new-wrapper {
  display: block;
}

/* Capped to a normal card's width (matches showcase's fixed 300px catalog size) — the rail
   column is often wider, and without the cap cards stretched to fill it. */
.home-rail-sticky {
  position: sticky;
  top: 16px;
  max-width: 300px;
}

/* The rail widget always renders the grid variant (fixed card_mode="grid").
   No size overrides needed beyond the width cap above — at a normal card's
   width, .card-grid-media's own aspect-ratio already gives a normal card's
   proportions. */
.home-widget .card-host {
  flex-shrink: 0;
}

.content-fluid-capped {
  max-width: 2300px;
  margin-inline: auto;
}

/* Icon-only guide-tour trigger buttons (search, messaging, project, event,
   capability, showcase guides) — a circle question-mark icon with no visible
   text. No hover/focus color or scale effect. */

/* Task list (tasking app) — row description preview. quill_html|safe emits block
   elements (p / ul / headings / br); preserve that line structure but clamp the whole
   preview with line-clamp so a multi-line description doesn't collapse onto one line. */
.task-desc-preview {
  pointer-events: none;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  overflow: hidden;
}
.task-desc-preview :is(p, ul, ol, li, h1, h2, h3, h4, h5, h6, blockquote, pre) {
  margin: 0;
}
.task-desc-preview img { display: none; }

/* Read-only task detail modal — cap the description box and scroll it internally so the
   modal height stays bounded (header / meta / footer stay put) instead of the whole modal
   growing or scrolling. */
.task-detail-scroll { max-height: 40vh; overflow-y: auto; }

/* Task list filter bar (tasking app) — Status/Source dropdown toggles collapse to just
   their icon once a filter is applied (task-filter-toggle-collapsed, set server-side
   alongside `active`), freeing horizontal space in the bar for the active-count badge
   instead of an icon + label + badge. */
.task-filter-toggle-collapsed .task-filter-toggle-label {
  display: none;
}
/* The active-count badge is `translate-middle`'d onto the button's top-right corner,
   so it pokes out past the button's own edge — with the filter bar's `gap-2` alone that
   overlap lands on the next button/dropdown in the bar. Reserve extra clearance on the
   parent `.dropdown` only while the badge is actually rendered (same
   task-filter-toggle-collapsed flag), so unfiltered buttons keep their normal spacing. */
.dropdown:has(> .task-filter-toggle-collapsed) {
  margin-right: 0.5rem;
}
/* Status/Source dropdown toggles apply Bootstrap's `.active` state to signal a live filter.
   The caret (`dropdown-toggle::after`, a border-only triangle) inherits that active styling
   too — the global `.active::after` rule above paints a filled background on it, and
   `.btn-outline-secondary.active`'s color tints the border — so it needs to be redeclared
   from scratch here rather than patched. `--bs-btn-active-color` (not `--bs-secondary`,
   which matches the active button's own background and would render invisibly) keeps the
   triangle visible against the active state. */
#status-filter-toggle.active::after,
#source-filter-toggle.active::after {
  display: inline-block;
  width: 0;
  height: 0;
  margin-left: 0.255em;
  vertical-align: 0.255em;
  content: "";
  background-color: transparent;
  border-radius: 0;
  border-top: 0.3em solid var(--bs-btn-active-color, #fff);
  border-right: 0.3em solid transparent;
  border-bottom: 0;
  border-left: 0.3em solid transparent;
}

/* Tasks kanban board (tasking app) — the four UserTaskStatus values as lanes.
   The board scrolls horizontally when the lanes exceed the viewport width; each lane is a
   fixed-width column that grows vertically to fit its cards. Cards move between lanes via
   the per-card status control OR by dragging a card into a different lane. Bootstrap CSS
   variables keep it theme-aware in light and dark. */
.task-kanban-board {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  overflow-x: auto;
  padding-bottom: 0.5rem;
}
.task-kanban-lane {
  flex: 1 1 0;
  min-width: 240px;
  background-color: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
}
.task-kanban-lane-header {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--bs-border-color);
}
.task-kanban-lane-body {
  padding: 0.75rem;
  min-height: 4rem;
  transition: background-color 0.1s ease-in-out;
}
.task-kanban-lane-body-dragover {
  background-color: var(--bs-primary-bg-subtle);
  outline: 2px dashed var(--bs-primary-border-subtle);
  outline-offset: -2px;
}
.task-kanban-card {
  cursor: grab;
}
/* Only a generic card is draggable. A derived card carries its own inline cursor:pointer (it
   deep-links to its source), which already wins over the rule above; scoping the grabbing state
   keeps the two from ever both claiming a drag cursor. */
.task-kanban-card:not(.task-kanban-card--derived):active {
  cursor: grabbing;
}
/* The drag marker in a generic card's footer — see task_card.html for why it is on that branch
   only. Muted so it reads as an affordance cue, not another action. */
.task-kanban-card-grip {
  color: var(--bs-secondary-color);
  cursor: grab;
}
.task-kanban-card-dragging {
  opacity: 0.5;
}
/* Only the name/label text underlines on hover, not the avatar — a text-only hover
   affordance reads as "this label is clickable" without turning the whole
   avatar+name cluster into a button-shaped block. See _assignee_trigger.html. */
.assignee-trigger:hover .assignee-trigger-text,
.assignee-trigger:focus-visible .assignee-trigger-text {
  text-decoration: underline;
}

.tag-input-hint {
  font-size: 0.75rem;
}

/* Tag editor (tag_input.html + tag_editor.js): tag pills as light blue chips,
   shared by every model's tag widget. position: relative so the suggestions
   dropdown below can anchor to it. */
.tag-editor {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  padding: 0.375rem 0.5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.375rem;
  background-color: var(--bs-body-bg);
}

.tag-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background-color: var(--bs-secondary-bg, #e9ecef);
  border: 1px solid var(--bs-border-color);
  color: var(--bs-link-color, #0d6efd);
  font-size: 0.85rem;
  white-space: nowrap;
}

.tag-pill-remove {
  background: none;
  border: none;
  padding: 0;
  color: inherit;
  opacity: 0.7;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
}

.tag-pill-remove:hover {
  opacity: 1;
  color: var(--bs-danger);
}

.tag-editor-text-input {
  flex: 1 1 120px;
  min-width: 120px;
  border: none;
  outline: none;
  background: transparent;
  font-size: 0.9rem;
  padding: 0.15rem 0;
  color: var(--bs-body-color);
}

.tag-editor-text-input:focus {
  outline: none;
  box-shadow: none;
}

/* Inline list that extends the editor's box downward rather than floating over it,
   capped in height with a scrollbar once full. Rows highlight on hover or
   .is-active, which arrow-key navigation sets. */
.tag-suggestions-dropdown {
  max-height: 200px;
  overflow-y: auto;
  margin-top: 0.35rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.375rem;
}

.tag-suggestions-dropdown:empty {
  display: none;
  border: none;
}

.tag-suggestion-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 0.4rem 0.75rem;
  color: var(--bs-body-color);
  font-size: 0.85rem;
}

.tag-suggestion-icon {
  flex-shrink: 0;
  opacity: 0.7;
  font-size: 0.8rem;
  color: var(--bs-link-color, #0d6efd);
}

.tag-suggestion-item:hover,
.tag-suggestion-item.is-active {
  background: var(--bs-tertiary-bg);
}

.tag-display-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.50rem;
  margin-top: 0.75rem;
}

.tag-display-item {
  color: var(--bs-link-color, #0d6efd);
  font-size: 0.85rem;
  text-decoration: none;
}

.tag-display-item:hover {
  text-decoration: underline;
}

/* Shared notch mechanism for the grid card and list row; each supplies its own
   --showcase-notch-radius. Each corner is punched out by a mask-image of 4 radial-gradient
   circles centered exactly on the box's corners (not inset), so it re-draws correctly at any
   size. The card element itself is the border (background clipped to the notch, 1px padding);
   the inner box paints the surface, masked to the same outline inset by that 1px. */
.showcase-card-notched,
.showcase-row-notched {
  padding: 1px;
  background: #343942;
  border: 0;
  mask-image:
    radial-gradient(
      circle at 0 0,
      transparent var(--showcase-notch-radius),
      #000 calc(var(--showcase-notch-radius) + 0.5px)
    ),
    radial-gradient(
      circle at 100% 0,
      transparent var(--showcase-notch-radius),
      #000 calc(var(--showcase-notch-radius) + 0.5px)
    ),
    radial-gradient(
      circle at 0 100%,
      transparent var(--showcase-notch-radius),
      #000 calc(var(--showcase-notch-radius) + 0.5px)
    ),
    radial-gradient(
      circle at 100% 100%,
      transparent var(--showcase-notch-radius),
      #000 calc(var(--showcase-notch-radius) + 0.5px)
    );
  mask-composite: intersect;
}

/* Same notch, inset by the 1px padding: circles stay centered on the card's own corner (hence the
   -1px/100%+1px offsets), radius grown by 1px. */
.showcase-card-inner,
.showcase-row-inner {
  --showcase-notch-inner-radius: calc(var(--showcase-notch-radius) + 1px);
  mask-image:
    radial-gradient(
      circle at -1px -1px,
      transparent var(--showcase-notch-inner-radius),
      #000 calc(var(--showcase-notch-inner-radius) + 0.5px)
    ),
    radial-gradient(
      circle at calc(100% + 1px) -1px,
      transparent var(--showcase-notch-inner-radius),
      #000 calc(var(--showcase-notch-inner-radius) + 0.5px)
    ),
    radial-gradient(
      circle at -1px calc(100% + 1px),
      transparent var(--showcase-notch-inner-radius),
      #000 calc(var(--showcase-notch-inner-radius) + 0.5px)
    ),
    radial-gradient(
      circle at calc(100% + 1px) calc(100% + 1px),
      transparent var(--showcase-notch-inner-radius),
      #000 calc(var(--showcase-notch-inner-radius) + 0.5px)
    );
  mask-composite: intersect;
}

/* Fixed 300x346 — height covers the worst case .card-heading allows (3 title lines + org name). */
.showcase-card-notched {
  --showcase-notch-radius: 11px;
}

/* Fixed-size cards, so a row-cols grid left a gap wherever its column was wider than the card;
   flex-wrap lets them sit at their own width. This gap matches the project/event grid's .row.g-3
   gutter, and card width plus it fits 4 per row. */
.showcase-card-grid {
  gap: 16px;
}

/* Kebab anchor, grid: a sibling of .card-modern rather than nested inside it — nesting it would
   put the dropdown menu under the card's mask and cut the menu off. Being a sibling, it does not
   inherit the card's hover lift, so that transform has to be repeated here. */
.showcase-card-kebab-anchor {
  transition: transform 0.25s ease;
}

.card-host:hover .showcase-card-kebab-anchor {
  transform: translateY(-4px);
}

/* Kebab anchor, list: same sibling-of-.card-modern constraint, but this one is also vertically
   centered on the row (previously Bootstrap's .translate-middle-y, which sets its transform
   !important and so cannot be composed with a hover rule). Owning the base transform here
   instead lets the hover rule fold the lift into the same translateY(), mirroring
   .card-count-badge's calc() composition below. */
.showcase-row-kebab-anchor {
  transform: translateY(-50%);
  transition: transform 0.25s ease;
}

.card-host:hover .showcase-row-kebab-anchor {
  transform: translateY(calc(-50% - 4px));
}

/* Shared section-divider header: label + a growing <hr> to the row's end. Used by
   the showcase landing page's Events/Projects/Showcases/Drafts sections and the events page's
   Current/Future/Past Events sections — one standard for both instead of each page carrying its
   own font/size/spacing.
   Both margins live here rather than splitting "space above" onto the section wrapper (e.g.
   .showcase-section) and "space below" onto the header — with both in play the section-to-
   section gap read as the two stacking, so this is the one place that owns the section's
   vertical rhythm on both sides, including above the very first header. */
.list-section-header {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 16px;
  margin-bottom: 16px;
}

.list-section-header span {
  flex-shrink: 0;
  color: var(--color-gray-10, #b0b0b0);
  font-family: "Rajdhani", sans-serif;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.list-section-header hr {
  flex-grow: 1;
  margin: 0;
  border-top: 1px solid var(--bs-secondary, #343942);
  opacity: 1;
}

/* Same notch mechanism as the grid card, row's own radius; --showcase-row-w caps its width. */
.showcase-row-notched {
  --showcase-notch-radius: 13.5px;
}

.showcase-card-list-item {
  flex-basis: 100%;
  max-width: 100%;
}

.showcase-card-list-item,
.showcase-card-list-item .card {
  width: min(var(--showcase-row-w), 100%);
}

/* --showcase-row-h is a MIN-height (unlike the grid card's fixed one), so a longer title grows its
   own row instead of every row padding out to the tallest possible title. */
.showcase-card-host {
  --showcase-row-h: 6.0625rem;
}

@media (max-width: 575.98px) {
  .showcase-card-host {
    --showcase-row-h: 4.5rem;
  }
}

/* Below sm the nowrap pill squeezes the min-width:0 title to a sliver, so it drops under the
   title instead — same fix as .card-list-body--stack-sm. */
.showcase-row-body--stack-sm {
  column-gap: 0.75rem;
}

@media (max-width: 575.98px) {
  .showcase-row-body--stack-sm {
    flex-wrap: wrap;
    row-gap: 0.25rem;
  }

  .showcase-row-body--stack-sm > .showcase-row-main {
    flex: 1 0 100%;
  }
}

/* Kebab reveals on hover/focus/open so a standing dot doesn't compete with the notch;
   :focus-within/:has(.show) hold it while the menu is open. Scoped to .showcase-card-host —
   notched cards don't carry .card-host. */
.showcase-card-host .showcase-card-kebab {
  opacity: 0;
  transition: opacity 0.15s ease-in-out;
}

.showcase-card-host:hover .showcase-card-kebab,
.showcase-card-host:focus-within .showcase-card-kebab,
.showcase-card-host:has(.dropdown-menu.show) .showcase-card-kebab {
  opacity: 1;
}

/* An open menu must outrank the cards after it in the DOM, or the next row paints over it.
   Mirrors .card-host:has(.dropdown-menu.show), scoped to the notched cards' host. */
.showcase-card-host:has(.dropdown-menu.show) {
  position: relative;
  z-index: 5;
}



/* ── Standard dropdown ────────────────────────────────────────────────────────────────────────
   Pick ONE value from a short, fixed list — status, sort order, category, priority.

   It is a Bootstrap dropdown rather than a native <select>, and the open panel is the whole
   reason: the browser draws a native option list itself, so the hovered row, the teal selected
   row and its check glyph cannot be styled onto one, and the closed box cannot flip its caret.
   A surface that only ever needs the closed box can stay a real <select> — .form-select is
   given the same closed-box colours further down, and posts with no script at all.

   Put .select-dropdown on the .dropdown wrapper, and keep .btn on the trigger: the toolbar's
   own `.list-toolbar > .dropdown > .btn` height rule is what makes this control 35px in a list
   header, and it selects on .btn. Do NOT also add .dropdown-menu-dark — the panel below is
   already dark, and that class only re-states colours this component sets itself. */
/* Keyed on .dropdown-toggle, not on .btn: a customizable dropdown's trigger is sometimes an
   icon-only filter button in a table header, which opens the same panel but must keep its own
   square shape. Those buttons carry data-bs-toggle="dropdown" without the .dropdown-toggle
   class (which only exists to draw Bootstrap's caret), so that class is exactly the line
   between "this trigger is a field" and "this trigger is an icon".

   The WRAPPER owns the width and the trigger fills it: a .dropdown div in a grid column is
   already full width, and in a flex toolbar it shrink-wraps to its label. The wrapper gets NO
   min-width:0 — it looks like the way to let a tight toolbar squeeze the control, but a flex
   item defaults to flex-shrink:1, so dropping its min-content floor let any growing sibling
   collapse it and "Sort: Newest" ellipsed to "Sort: …" with the row half empty. That floor is
   what makes a toolbar trigger show its whole label; truncation belongs to the value span
   alone, where the box really is fixed. */
.select-dropdown > .btn.dropdown-toggle {
  --bs-btn-bg: var(--bs-body-bg);
  --bs-btn-border-color: var(--bs-secondary-bg);
  --bs-btn-color: var(--bs-body-color);
  --bs-btn-hover-bg: var(--bs-body-bg);
  --bs-btn-hover-border-color: var(--bs-primary);
  --bs-btn-hover-color: var(--bs-emphasis-color);
  --bs-btn-active-bg: var(--bs-body-bg);
  --bs-btn-active-border-color: var(--bs-primary);
  --bs-btn-active-color: var(--bs-emphasis-color);
  --bs-btn-active-shadow: none;
  --bs-btn-disabled-bg: var(--bs-body-bg);
  --bs-btn-disabled-border-color: var(--bs-secondary-bg);
  --bs-btn-disabled-color: var(--bs-tertiary-color);
  --bs-btn-border-radius: 6px;
  --bs-btn-padding-x: 0.75rem;
  --bs-btn-padding-y: 0.375rem;
  --bs-btn-focus-box-shadow: 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);

  display: inline-flex;
  align-items: center;
  width: 100%;
  column-gap: 0.5rem;
  /* The trigger shows a VALUE, not an action, so it takes body type and sentence case like any
     other field. The CTA step declares a literal Rajdhani and `color: #FFF` on every .btn, and
     the solid variants uppercase, so all four have to be restated here — otherwise the value
     would match neither the options under it nor a real <select> beside it. */
  color: var(--bs-body-color);
  font-family: "Inter", sans-serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  text-align: left;
  text-transform: none;
}

/* Hover and open are not declared here: Bootstrap's own .btn:hover and .btn.show read
   --bs-btn-hover-border-color and --bs-btn-active-border-color, both set to the accent above.
   Focus is the exception — Bootstrap gives .btn:focus-visible a shadow but no border colour. */
.select-dropdown > .btn.dropdown-toggle:focus-visible {
  border-color: var(--bs-primary);
}

/* A value can be longer than the box — an org or capability name especially — so it truncates
   rather than pushing the caret out of the control. min-width:0 is required: a flex item floors
   at min-content, which for text is the longest word, and text-overflow never engages while the
   item is allowed to be that wide. */
.select-dropdown-value {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Bootstrap's caret is a static triangle drawn with a border trick, so it cannot rotate. The
   markup carries a real bi-chevron-down instead and this hides the built-in one. */
.select-dropdown > .btn.dropdown-toggle::after {
  display: none;
}

/* margin-left:auto, not justify-content:space-between — the toolbar height block sets
   justify-content:center on `.list-toolbar > .dropdown > .btn`, and an auto margin absorbs the
   free space BEFORE justify-content is applied, so the caret sits hard right in a toolbar and
   in a form without either rule having to out-specify the other.

   The gap to the value is the button's own column-gap, NOT padding here: rotate(180deg) turns
   this element's box over, so a padding-left becomes a padding-right and the open state loses
   the gap it had while closed. line-height:1 makes the box hug the glyph so align-items keeps
   the chevron centred in both rotations.

   The colour is fixed across every state, and the explicit declaration is what fixes it: the
   trigger's own label brightens on hover and open, so without a colour of its own the caret
   would come along with it. Only the ROTATION marks the open state — the chevron says "this
   opens", which is equally true whichever state the control is in, so lighting it up spends the
   accent colour on something that never changes. The border carries the state instead. */
.select-dropdown-caret {
  flex: 0 0 auto;
  margin-left: auto;
  line-height: 1;
  color: var(--bs-secondary-color);
  transition: transform 0.15s ease-in-out;
}

/* Bootstrap writes aria-expanded on the trigger it opens, so the flip needs no script. */
.select-dropdown > .btn.dropdown-toggle[aria-expanded="true"] .select-dropdown-caret {
  transform: rotate(180deg);
}

/* The panel is AT LEAST the trigger's width and grows past it when a row needs more —
   min-width, not width. Locking it to the trigger looks right while the trigger shows the
   selected option (its label is drawn from the same vocabulary as the rows), but a filter's
   trigger says "Status" while its rows say "Not Started", and a fixed width clipped them
   straight through the panel's edge. Bootstrap's own 10rem floor still has to go, or a control
   narrower than that opens a panel wider than itself. */
.select-dropdown > .dropdown-menu {
  --bs-dropdown-min-width: 0;
  --bs-dropdown-padding-y: 4px;
  --bs-dropdown-bg: var(--bs-body-bg);
  --bs-dropdown-border-color: var(--bs-secondary-bg);
  --bs-dropdown-border-radius: 6px;
  --bs-dropdown-spacer: 4px;

  width: auto;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);

  /* The panel is body text and immune to its container, the same reasoning that makes
     .form-control immune to whatever it sits in. Both halves are load-bearing: the two
     table-header filter panels live inside a .table-header-cell, which is Rajdhani AND
     text-transform: uppercase, and text-transform inherits — so their rows rendered as
     condensed all-caps ("NOT STARTED") while the identical rows in a toolbar read correctly.
     Declared on the panel rather than the row so anything else placed inside is covered too.
     Size is NOT set here — an option has its own, on the row rule below. */
  font-family: "Inter", sans-serif;
  font-weight: 400;
  text-transform: none;
}

/* The trigger-width floor applies only to a panel the browser positions against its trigger.
   A searchable dropdown's panel is `position: fixed` and placed by script (§22), and for a
   fixed element a percentage resolves against the VIEWPORT — `min-width: 100%` there would open
   a panel as wide as the window. Script that positions a panel sets its width too, so the floor
   is neither needed nor meaningful in that case. */
.select-dropdown > .dropdown-menu:not(.position-fixed) {
  min-width: 100%;
}

/* Set on the ITEM, not inherited from the panel: the global .dropdown-item rule near the top of
   this file declares these same three variables on the item itself, and a custom property
   resolves from its own element before any ancestor — so a value set on .dropdown-menu would
   lose to it. Two classes here beats that rule's one. */
.select-dropdown .dropdown-item {
  --bs-dropdown-link-color: var(--bs-secondary-color);
  --bs-dropdown-link-hover-color: var(--bs-primary);
  --bs-dropdown-link-hover-bg: var(--bs-secondary-bg-subtle);
  --bs-dropdown-link-active-color: var(--bs-primary);
  --bs-dropdown-link-active-bg: var(--bs-secondary-bg-subtle);

  display: flex;
  align-items: center;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
}

/* Selected reads off :checked, because the options are radio + <label class="dropdown-item">
   — a radio group so the panel stays keyboard-navigable — and nothing server-side marks the
   row. .active is
   accepted alongside it for a panel whose rows are plain buttons rather than a radio group.
   The fill stays the panel's own — selection is carried by the teal label and the check, so a
   selected row and a hovered row do not compete for the same cue. */
.select-dropdown .btn-check:checked + .dropdown-item,
.select-dropdown .dropdown-item.active {
  color: var(--bs-primary);
  background-color: transparent;
}

.select-dropdown .btn-check:checked + .dropdown-item:hover,
.select-dropdown .dropdown-item.active:hover {
  background-color: var(--bs-secondary-bg-subtle);
}

/* visibility, not display: the glyph keeps its box on every row, so the labels do not shift
   sideways as the selection moves down the list. */
.select-dropdown-check {
  margin-left: auto;
  padding-left: 0.5rem;
  visibility: hidden;
  color: var(--bs-primary);
}

.select-dropdown .btn-check:checked + .dropdown-item .select-dropdown-check,
.select-dropdown .dropdown-item.active .select-dropdown-check {
  visibility: visible;
}

/* A real <select> keeps the same closed box as the component above, so the two never read as
   two different controls on one page. Bootstrap already resolves this box to the right fill
   (--bs-body-bg) and radius (6px); only the border and the two lit states were off.

   Scoped off [multiple], whose box is a scrolling list rather than a closed trigger — the
   dual-list pickers use one and take their own border-0 there. Nothing here tries to reach the
   open option list; the browser owns it, which is what .select-dropdown exists for. */
.form-select:not([multiple]) {
  border-color: var(--bs-secondary-bg);
}

.form-select:not([multiple]):hover:not(:disabled) {
  border-color: var(--bs-primary);
}

/* Bootstrap's compiled focus ring for form controls is its default blue. The teal ring is
   already declared as --bs-focus-ring-color and already used by .form-check-input:focus, so
   this puts selects on the token the rest of the theme uses rather than inventing a colour. */
.form-select:not([multiple]):focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);
}

/* ── Customizable dropdown ────────────────────────────────────────────────────────────────────
   The multi-select sibling of the standard dropdown: pick one OR MORE values and manage the
   selection without the panel closing. Advanced filtering is the case it exists for.

   It IS the standard dropdown — same closed box, same panel, same tokens — so it is a modifier
   rather than a second control: add .select-dropdown--multi alongside .select-dropdown. Three
   things differ, and only the first two are CSS:

     * a row carries a CHECKBOX instead of a trailing check;
     * the trigger reports how many values are selected as a corner count;
     * the panel must stay open on a row click, which is `data-bs-auto-close="outside"` on the
       trigger — markup, not style, and every adopter needs it or the panel shuts after one tick.

   A row is a <label class="dropdown-item"> wrapping its own checkbox, so the whole row is the
   click target and the padding, hover fill and muted label all come from the standard dropdown's
   option rule above rather than being restated. */
.select-dropdown--multi .dropdown-item {
  column-gap: 0.5rem;
}

/* :has(), not the `:checked + .dropdown-item` sibling form the radio rows use — a checkbox has to
   sit INSIDE the label for the row to be its click target, so there is no sibling to match. */
.select-dropdown--multi .dropdown-item:has(input:checked) {
  color: var(--bs-primary);
}

/* The box keeps its size whatever the row's font does, and is never what shrinks. Its checked
   fill is already the accent, from the .form-check-input:checked rule further up. */
.select-dropdown--multi .dropdown-item .form-check-input {
  flex: 0 0 auto;
  margin: 0;
}

/* The count is overlaid on the trigger's corner rather than set inline, so the button never
   resizes as the count appears, disappears or gains a digit — it needs position-relative on the
   trigger to anchor to. Teal like every other count in the app: the four filter triggers that
   predate this had each hand-assembled the same utility stack, and three of them had drifted to
   bg-warning against their own comment saying primary. */
/* The trigger is lit whenever the filter is doing something, derived from the presence of the
   count rather than a second `active` class: the count IS the state, so one mechanism cannot
   drift from the other, the server's first paint and the client's recompute agree for free, and
   the JS that maintains the count no longer has to maintain a class beside it. */
.select-dropdown--multi > .btn.dropdown-toggle:has(.select-dropdown-count) {
  border-color: var(--bs-primary);
}

/* Anchors the corner count. On the modifier rather than at the call site: every multi trigger
   needs it, and a forgotten position-relative sends the count to the page's corner. */
.select-dropdown--multi > .btn.dropdown-toggle {
  position: relative;
}

.select-dropdown-count {
  position: absolute;
  top: 0;
  left: 100%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 50rem;
  background-color: var(--bs-primary);
  color: #fff;
  font-family: "Inter", sans-serif;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
}

/* ── Searchable dropdown ──────────────────────────────────────────────────────────────────────
   The standard dropdown (single-select, check-marked row) with a search field in place of the
   value: for a list long enough that scanning it is slower than typing at it. Its panel and rows
   are the shared ones — the wrapper carries .select-dropdown and the panel is a real
   .dropdown-menu — so only what is genuinely different lives here.

   Its one adopter is the inline assignee picker, and two things about that surface are worth
   knowing before copying it. Its panel is `position: fixed` and placed by script rather than by
   the browser, because an absolutely-positioned descendant's min-width still inflates its
   containing block's shrink-to-fit width — which widened the whole task table. And its field is
   deliberately compact, because the picker replaces a value inline in a dense table row. Neither
   is a property of searchable dropdowns in general; a panel with room should use the ordinary
   browser-positioned .dropdown-menu and a full-height field. */
/* The box, the colours and the teal focus all come from .select-dropdown-search. Only the
   height is this surface's own: the picker replaces a value inline in a dense table row or
   kanban card, where a full-height field would push the row taller. */
.assignee-picker-search {
  height: 20px;
  min-height: 20px;
  padding-top: 0;
  padding-bottom: 0;
  line-height: 20px;
}

.assignee-picker-list {
  /* Above the surfaces this floats over; set through Bootstrap's own token rather than a bare
     z-index, which would tie with .dropdown-menu's and be decided by source order. */
  --bs-dropdown-zindex: 1050;

  /* visibility, NOT display: the script measures the form and places the panel before revealing
     it, so the panel has to be laid out but unpainted until then. display:none would leave it
     with no rect to position against, and no hiding at all flashes it at (0,0). */
  visibility: hidden;
  max-height: 220px;
  overflow-y: auto;
}

/* The panel's width is the field's, so a long name wraps rather than running out of the panel —
   .dropdown-item is nowrap by default, which suits a fixed vocabulary and not a roster. */
.assignee-picker-list .dropdown-item {
  white-space: normal;
}

.assignee-picker-empty {
  padding: 0.5rem 0.75rem;
  font-size: 14px;
}

/* ── Searchable dropdown: the field ───────────────────────────────────────────────────────────
   A searchable dropdown's field is the trigger, so it takes the same closed box — but editable,
   and set at the option rows' 14px rather than a trigger's 16px: the field and the list under it
   are one filter-and-pick unit, and the field's text is a query against those rows, not a value.
   The assignee picker overrides only the height, being inline in a dense table row (§22.1). */
.select-dropdown-search {
  width: 100%;
  padding: 0.375rem 0.75rem;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-secondary-bg);
  border-radius: 6px;
  color: var(--bs-body-color);
  font-family: "Inter", sans-serif;
  font-size: 14px;
}

.select-dropdown-search:focus {
  outline: none;
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);
}

.select-dropdown-search::placeholder {
  color: var(--bs-form-control-placeholder-color);
}

/* For a field in a narrow popover, where the rows under it are the content and every pixel it
   takes is one they do not get. The nav bar's Filter picker is the case; the results sidebar's
   own field sits in a wider column and keeps the full size. */
.select-dropdown-search--compact {
  padding: 0.2rem 0.5rem;
  font-size: 13px;
}

/* An asynchronously-filled panel sits in the DOM before it holds any rows, and without this it
   renders as a bordered sliver under the field. `:has(*)` and not `:empty` — a Django loop over
   nothing still leaves whitespace text nodes, which :empty does not count as empty. */
.select-dropdown > .dropdown-menu.show:not(:has(*)) {
  display: none;
}

/* A keyboard-highlighted row reads exactly like a hovered one. Distinct from `.active`: these
   panels have no selected row, because choosing a value moves it out of the list and into a tag
   above the field. */
.select-dropdown .dropdown-item.is-active {
  color: var(--bs-primary);
  background-color: var(--bs-secondary-bg-subtle);
}

/* ── Customizable dropdown: the tag display ───────────────────────────────────────────────────
   The other way a multi-select reports itself (§21.2 is the corner count): every chosen value
   kept visible above the field and individually removable, with a single "+N" once past the cap.
   Use it when the viewer needs to see and manage which values are on; use the count when the
   values do not matter until the panel is opened.

   The cap is the caller's, not a constant here — how many tags fit is a property of the surface,
   so the view slices the list and passes the remainder as a count. */
.select-dropdown-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-bottom: 0.5rem;
}

/* Accent-bordered and accent-lettered, matching the frame — a tag is a SELECTED value, so it
   carries the same colour a selected row does. It read neutral grey at first, which made the
   tags look like static labels rather than the live selection they are. */
.select-dropdown-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  max-width: 100%;
  padding: 0.2rem 0.45rem;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-primary);
  border-radius: 6px;
  color: var(--bs-primary);
  font-size: 14px;
  line-height: 1.3;
}

.select-dropdown-tag > i {
  flex: 0 0 auto;
}

/* A long org or person name truncates rather than forcing the panel it sits in wider. */
.select-dropdown-tag-label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Inherits the tag's accent; only the hover brightens, so the × is never the loudest thing in
   a row of tags. */
.select-dropdown-tag-remove {
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.select-dropdown-tag-remove:hover {
  color: var(--bs-emphasis-color);
}

/* ── Comment composer ─────────────────────────────────────────────────────────────────────────
   The rounded, pill-bordered comment box that sits at the bottom of a review modal's left
   panel: toolbar row on top, editor plus a send arrow below, so it reads like a chat composer
   under the content it comments on. Shared by the project task and event showcase review
   modals — it lived in the task template's own <style> block until the showcase needed it too.

   Quill draws its own toolbar and container boxes with square corners and their own background
   (quill-snow-bootstrap-dark.css paints --bs-body-bg), so those are flattened here rather than
   on the shared .message-wrapper class — the editor has to blend into the single rounded pill
   instead of nesting another box inside it. */
.comment-composer {
  position: relative;
}

.comment-composer .ql-toolbar.ql-snow,
.comment-composer .ql-container.ql-snow {
  background-color: var(--bs-secondary-bg);
  border: 0;
}

/* Clears the send button, which is absolutely positioned over the editor's bottom-right. */
.comment-composer .ql-container.ql-snow {
  padding-right: 3rem;
}

.comment-composer-send {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  width: 32px;
  height: 32px;
  border-radius: 0.5rem;
}

/* bi-send-fill is drawn as a paper plane angled up-right; rotate it back to horizontal so it
   points straight right, matching the composer's left-to-right reading direction. */
.comment-composer-send-icon {
  transform: rotate(45deg);
}

/* Showcase assessments. Unread counts use the shared .notification-count-badge; a section
   total is a plain accent number, not a badge. */
.assessment-panel-trigger {
  font-family: "Inter", sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-decoration: none;
}

.assessment-panel-trigger:hover,
.assessment-panel-trigger:focus-visible {
  text-decoration: underline;
}

/* List rows that are <button>s for semantics, so they hover like .search-result-row and
   .pending-action-row (a tint), not like a button: btn-outline-secondary's solid #6c757d
   fill is the same colour as the .text-secondary lines inside, which hid them. */
.assessment-row {
  --bs-btn-hover-bg: rgba(255, 255, 255, 0.05);
  --bs-btn-hover-border-color: var(--bs-secondary);
  --bs-btn-active-bg: rgba(255, 255, 255, 0.08);
  --bs-btn-active-border-color: var(--bs-secondary);
}

/* The tint lightens towards #6c757d, so the muted lines need lifting too or hover reads
   worse than rest. !important because Bootstrap's .text-secondary utility carries one. */
.assessment-row:hover .text-secondary,
.assessment-row:focus-visible .text-secondary {
  color: var(--bs-gray-500, #adb5bd) !important;
}

/* Attachment chips. The stem truncates, the extension does not — CSS has no middle
   ellipsis, so the name is two spans and only one of them may shrink. */
.assessment-attachment {
  display: inline-flex;
  align-items: center;
  max-width: 15rem;
  min-width: 0;
}

.assessment-attachment-group {
  max-width: 17rem;
  min-width: 0;
}

.assessment-attachment-name {
  display: inline-flex;
  min-width: 0;
}

.assessment-attachment-stem {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Quill wraps content in <p>, which the global "P step" sizes at 18px — that outweighs the
   0.82rem on the wrapper. Scoped here, not to .ql-view: the review modal shares that. */
.assessment-message-body p,
.assessment-message-body li {
  font-size: 0.82rem;
  line-height: 1.45;
}

.assessment-message-body p:not(:last-child) {
  margin-bottom: 0.4rem;
}

/* Right-aligned action button on the project tab row */
.project-tabs-action-btn {
  min-width: 130px;
  height: 38px;
  border-radius: var(--Corners-Rounded-Corners, 6px);
  border: var(--Border-Border-Size, 1px) solid var(--Color-gray-00, #fff);
  background: var(--Surface-Surface-Secondary, #222832);
  color: var(--Color-gray-10, #b0b0b0);
  font-family: "Rajdhani", sans-serif;
  font-style: normal;
  font-weight: 600;
  line-height: normal;
}

.project-tabs-action-btn:hover,
.project-tabs-action-btn:focus-visible {
  background: #2c333f;
  color: var(--Color-gray-00, #fff);
}

/* ── Two-column modals: content beside an action column ───────────────────────────────────────
   Six templates, one shape: a content column beside an action/meta column, inside a body that
   never scrolls itself. The task review shell (components/history_review_modal_shell.html, which
   four project task modals extend), the event showcase review modal, the generic task detail
   modal, and the three invite review modals (company / stakeholder / event).

   .split-modal-* is the shared skeleton. A modal adds a variant class only where it genuinely
   differs — body height, action-column width, or a content column split into a capped block
   above a feed rather than one scroll region. The three invite modals need no variant at all.

   Layout lives here rather than inline on the elements because the narrow-width rules have to
   override it, and an inline style can only be beaten with !important.

   Each dialog sets its own width: Bootstrap collapses .modal-xl to the base 500px .modal-dialog
   everywhere between 576px and 1199.98px, and 500px is narrower than the action column plus
   anything usable beside it — so these were already broken at 1199px, long before phone widths.
   style.css loads after bootstrap_css, so equal specificity wins. */
.split-modal-dialog {
  width: 1140px;
  max-width: calc(100% - 1rem);
}

.split-modal-body {
  overflow: hidden;
}

.split-modal-split {
  display: flex;
  height: 85vh;
  min-height: 0;
  min-width: 0;
}

.split-modal-main {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  width: 0;
  min-width: 0;
  overflow: hidden;
}

/* A region that fills its column and scrolls — the whole content column where that column is one
   scroll region (showcase, invite, task detail), or a pane within one where it is not. */
.split-modal-scroll {
  flex-grow: 1;
  min-height: 0;
  min-width: 0;
  overflow-x: hidden;
  overflow-y: auto;
}

/* Border colour comes from .border-secondary on the element; only width/style are set here, so
   the narrow-width rule can move the border to the top without fighting the utility's
   !important. */
.split-modal-aside {
  flex: 0 1 380px;
  min-width: 320px;
  padding: 1.25rem;
  overflow: auto;
  border-left-width: var(--bs-border-width);
  border-left-style: solid;
}

/* ── Variant: task review shell ───────────────────────────────────────────────────────────────
   The one content column that is not a single scroll region — a capped block above an
   independently scrolling activity feed, so scrolling one never drags the other. */
.review-modal-primary {
  flex-shrink: 0;
  max-height: 50%;
  overflow-x: hidden;
  overflow-y: auto;
}

/* The feed's inline visibility:hidden stays in the markup — the shell's script owns it.
   padding-bottom clears the absolutely-positioned Jump to Bottom pill. */
.review-modal-activity {
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  padding-bottom: 3.5rem;
  -webkit-overflow-scrolling: touch;
}

/* ── Variant: generic task detail modal ───────────────────────────────────────────────────────
   Shorter body, narrower action column, and no .border-secondary on that column to take a
   border colour from. */
.task-detail .split-modal-split {
  height: 75vh;
}

.task-detail .split-modal-aside {
  flex-basis: 340px;
  border-color: var(--bs-border-color);
}

/* ── Variant: showcase review modal ───────────────────────────────────────────────────────────
   Wider dialog, and an action column that does not shrink — the extra width from a wide viewport
   goes to the showcase preview, not the action column. max(70vw, 1140px) keeps the wide-viewport
   70vw this modal has always used while giving it a floor the clamp can fit to the space
   available. Its column holds two stacked regions, so padding sits on the upper block rather than
   on the column, letting the activity pane keep its own px-4 gutter. */
.showcase-review {
  width: max(70vw, 1140px);
}

.showcase-review-aside {
  display: flex;
  flex-direction: column;
  flex: 0 0 450px;
  min-width: 360px;
  min-height: 0;
  overflow: hidden;
  border-left-width: var(--bs-border-width);
  border-left-style: solid;
}

.showcase-review-aside-top {
  flex-shrink: 0;
  padding: 1.25rem;
  overflow: auto;
}

/* flex 1 1 auto + min-height 0 is what bounds the pane so it scrolls internally instead of
   pushing the column. */
.showcase-review-activity-pane {
  flex: 1 1 auto;
  min-height: 0;
}

/* ── Narrow widths: stack the columns ─────────────────────────────────────────────────────────
   The modal body becomes a single scroll container and every nested scroll region releases its
   cap, so the modal flows as one document instead of becoming several thumbnail-sized scrollers.

   Two breakpoints, because the modals run out of room at different widths. 767.98px for the
   shared skeleton, whose action column shrinks to a 320px floor. 991.98px for the showcase,
   whose column is a fixed 450px beside a media preview (banners, carousels) that needs real
   estate of its own — at 768px that would leave the preview ~300px. */
@media (max-width: 767.98px) {
  .split-modal-body {
    max-height: calc(85vh - 4rem);
    overflow-x: hidden;
    overflow-y: auto;
  }

  .task-detail .split-modal-body {
    max-height: calc(75vh - 4rem);
  }

  .split-modal-split {
    flex-direction: column;
    height: auto;
  }

  .split-modal-main,
  .split-modal-scroll,
  .split-modal-aside,
  .review-modal-primary,
  .review-modal-activity {
    flex: 0 0 auto;
    width: auto;
    min-width: 0;
    max-height: none;
    overflow: visible;
  }

  /* The Jump to Bottom pill goes dead here — nothing scrolls, so the script's near-bottom check
     is permanently true — hence no clearance to reserve. */
  .review-modal-activity {
    padding-bottom: 0;
  }

  .split-modal-aside {
    border-left-width: 0;
    border-top-width: var(--bs-border-width);
    border-top-style: solid;
  }
}

@media (max-width: 991.98px) {
  .showcase-review .split-modal-body {
    max-height: calc(85vh - 4rem);
    overflow-x: hidden;
    overflow-y: auto;
  }

  .showcase-review .split-modal-split {
    flex-direction: column;
    height: auto;
  }

  .showcase-review .split-modal-main,
  .showcase-review .split-modal-scroll,
  .showcase-review-aside,
  .showcase-review-aside-top,
  .showcase-review-activity-pane {
    flex: 0 0 auto;
    width: auto;
    min-width: 0;
    max-height: none;
    overflow: visible;
  }

  .showcase-review-aside {
    border-left-width: 0;
    border-top-width: var(--bs-border-width);
    border-top-style: solid;
  }
}
