/* ============================================
   AOHeader — consolidated portal header (spec §7)
   Every rule scoped under .acec-portal (Global Constraints §3.1).
   ============================================ */

/* ── Inheritance barrier (2026-09-16) ─────────────────────────────────────
   The same header rendered differently on three live netFORUM pages. Our own
   rules already beat the site theme wherever we DECLARE a property — theirs are
   element selectors (0,0,1), ours are `.acec-portal X` (0,2,0). The variance
   lived entirely in the properties we DO NOT declare: the theme's
   `div, p, a, li { font-family: Raleway; letter-spacing: .08em }` and
   `a { font-weight: 600; color: #e09a10 }` reached our elements directly, and
   other inherited values reached them through the host's <body> — and which
   stylesheets a page loads differs page by page.

   Two rules close it, and BOTH MUST STAY AT THE TOP OF THIS FILE: they carry
   the same specificity as most rules below, so it is source order that lets the
   real rules override them.

   1. A baseline on the header root. Every inherited property is pinned, so
      nothing flows in from the page above us.
   2. A descendant rule for the element types the theme targets. It uses
      `inherit`, NOT fixed values — the point is that these elements take OUR
      parent's computed value instead of the theme's element rule, which keeps
      the header's own internal inheritance intact (the 0.45rem nav button still
      passes its size down to its label span; a fixed value there would have
      reset every label to 1rem).

   `:where()` is what makes rule 2 safe: it contributes NO specificity, so the
   selector scores 0,2,0 rather than 0,2,1 and every later rule of ours still
   wins. Replacing it with a plain selector list would silently outrank the rest
   of this stylesheet. */

.acec-portal .aoheader {
  /* --font-main is declared on :root in shared.css, which makes it GLOBAL: any
     portal's stylesheet can redefine it and the last one loaded wins. That is
     precisely what differed between pages — committee.css and the events portal
     set it to Inter, so the same var() resolved to Inter there and to Raleway
     elsewhere, and the header drew in two different typefaces without a single
     rule of ours changing. Redefining it HERE puts our value closer in the tree
     than any :root can be, so every var(--font-main) below resolves to this one
     whatever the page declares. Inter matches committee.css and the events
     portal (user's call, 2026-09-16). */
  --font-main: 'Inter', Arial, sans-serif;

  /* Root-font-size barrier (2026-10-10). Some host pages set their own root
     size — AODirectory's style.css has `html, body { font-size: 20px }` — and
     every rem in this header then grew by 25%. All lengths in this file are
     now px (the old rem value x 16), and the shared tokens it reads are pinned
     here to the same px values so a host :root cannot rescale them either. */
  --sp-xs: 4px;
  --sp-sm: 8px;
  --sp-md: 16px;
  --sp-lg: 24px;
  --sp-xl: 32px;
  --sp-xxl: 48px;
  --radius-xs: 2.4px;
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;

  font-family: var(--font-main);
  font-size: 16px;
  font-weight: 400;
  font-style: normal;
  line-height: 1.5;
  letter-spacing: normal;
  word-spacing: normal;
  text-transform: none;
  text-align: left;
  text-indent: 0;
  color: var(--acec-navy);
  white-space: normal;
}

.acec-portal .aoheader :where(a, div, p, li, span, button, nav, header, img, svg, i) {
  font: inherit;
  letter-spacing: inherit;
  word-spacing: inherit;
  text-transform: inherit;
  text-align: inherit;
  color: inherit;
  text-decoration: none;
}

/* ── Shell (§7.1) ── */

.acec-portal .aoheader {
  background: var(--acec-bg-soft);
  border-bottom: 1px solid var(--acec-border);
  /* 0.6rem x 1.2 — see the 20% pass, 2026-09-17. */
  padding: 11.52px 0;
}

.acec-portal .acec-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  /* Fix round 1: row-gap then gap is a real shorthand collision (gap resets */
  /* row-gap) but it is intentional — it reproduces ConsolidatedHeader.tsx's */
  /* own style object (rowGap then gap, lines 1014-1015), so the prototype's */
  /* effective row-gap is --sp-lg too. Do not "fix" this; see OPEN-ITEMS.md. */
  row-gap: var(--sp-sm);
  gap: var(--sp-lg);
  /* Full-bleed (user request, 2026-09-17). Declared here AND on the compound
     selector below because both classes sit on this one element and the two
     rules have equal specificity — whichever file loads last would otherwise
     decide the width. */
  max-width: none;
}

.acec-portal .aoheader > .aoheader-container {
  max-width: none;
}

/* Side padding pinned in px (2026-10-10). shared.css sets it in rem
   (1rem / 1.5rem / 2rem), so on a page with its own root size — AODirectory's
   html { font-size: 20px } — the logo and the profile pill moved 8px inward.
   Same breakpoints and the same values under a 16px root. */
.acec-portal .aoheader > .aoheader-container {
  padding: 0 16px;
}

@media (min-width: 768px) {
  .acec-portal .aoheader > .aoheader-container {
    padding: 0 24px;
  }
}

@media (min-width: 1200px) {
  .acec-portal .aoheader > .aoheader-container {
    padding: 0 32px;
  }
}

/* ── Brand block (§7.2) ── */

.acec-portal .acec-brand-link {
  display: inline-flex;
  align-items: center;
  gap: 1.6px;
  flex-shrink: 0;
  color: var(--acec-navy);
  text-decoration: none;
}

.acec-portal .acec-brand-link img {
  height: 36px;
  object-fit: contain;
}

.acec-portal .acec-brand-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22.4px;
  height: 22.4px;
  margin-left: -2.4px;
}

.acec-portal .acec-brand-arrow svg {
  width: 18px;
  height: 18px;
  /* The resting colour is named here rather than inherited so the hover has a
     defined value to animate FROM — a transition between an inherited and a
     declared colour does not always run. */
  color: var(--acec-navy);
  transition: transform 0.2s ease, color 0.2s ease;
}

.acec-portal .acec-brand-link:hover svg,
.acec-portal .acec-brand-link:focus-visible svg {
  transform: translate(2px, -2px);
}

/* Gold on hover (user request, 2026-09-16). §7.2 specifies only the transform,
   so the colour change is an addition — recorded in rulings.md R43.
   --acec-gold-dark, not --acec-gold: the arrow is an 18px stroked glyph on the
   header's light surface, where the brighter fill gold reads as washed out.
   One token name to swap if the brighter one is wanted. */
.acec-portal .acec-brand-link:hover .acec-brand-arrow svg,
.acec-portal .acec-brand-link:focus-visible .acec-brand-arrow svg {
  color: var(--acec-gold-dark);
}

/* ── Desktop navigation (§7.3) ── */

.acec-portal .acec-header-nav {
  display: flex;
  flex: 1;
  justify-content: flex-end;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--sp-xs);
}

.acec-portal .acec-header-nav-button {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 3.2px;
  /* §7.3.2's 4.75rem, narrowed on 2026-09-17 after seeing the row at full
     width. The label still wraps rather than overflowing (word-break below),
     and the side padding came in with it so the inner room lost is 0.25rem. */
  width: 70.4px;
  /* 3.1rem x 1.2 — the ONLY nav-button dimension the 20% pass of 2026-09-17
     changed (user, same day). The label size, gap and icon are §7.3.2's. */
  min-height: 59.52px;
  /* Top padding carries most of the height the 20% pass added, so the icon and
     label sit lower in the box instead of hugging its top edge (user, same
     day). Content stays TOP-aligned rather than centred: with justify-content
     centre, a one-line label would float its icon below a two-line label's and
     the row of icons would no longer read as a row. */
  padding: 9.6px 3.2px 4.8px;
  border: 1px solid var(--acec-border);
  border-radius: var(--radius-md);
  background: var(--portal-white);
  color: var(--acec-navy);
  font-family: var(--font-main);
  /* Deliberately tiny — locked to the approved 2026-09-15 screenshot. Do not */
  /* "correct" this after the Raleway font swap; see OPEN-ITEMS.md item 8. */
  font-size: 7.2px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.15;
  text-align: center;
  white-space: normal;
  word-break: break-word;
  text-decoration: none;
}

.acec-portal .acec-header-nav-button svg {
  width: 15px;
  height: 15px;
}

/* ── Nav item states (§7.3.3) ── */

.acec-portal .acec-header-nav-button:hover,
.acec-portal .acec-header-nav-button:focus-visible {
  background: var(--acec-gold);
  color: var(--acec-navy);
}

.acec-portal .acec-header-nav-button:focus-visible {
  outline: 2px solid var(--acec-gold-dark);
  outline-offset: 2px;
}

.acec-portal .acec-header-nav-button.is-active {
  background: var(--acec-gold);
  border-color: var(--acec-gold);
}

/* Action-required rows are filtered out of this nav row (§7.3.1) and only ever */
/* render in the profile dropdown (Task 5), which reuses this same builder. */
.acec-portal .acec-header-nav-button--action:not(.is-active) {
  border: 1.5px solid var(--acec-gold);
}

.acec-portal .acec-header-nav-button--action:not(.is-active) svg {
  color: var(--acec-gold-dark);
}

/* ── Profile card (§7.4) ── */

.acec-portal .acec-profile-card {
  /* Anchors the absolutely-positioned dropdown (§7.6). */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  background: var(--portal-white);
  border: 1px solid var(--acec-border);
  border-radius: 9999px;
  padding: 4.8px 9.6px 4.8px 5.6px;
}

/* Reset: a <button> masquerading as the avatar + identity trigger (§3.4). */
.acec-portal .acec-profile-trigger {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  min-width: 0;
  background: none;
  border: none;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* ── Avatar (§7.4.1) ── */

.acec-portal .acec-avatar-wrapper {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
}

.acec-portal .acec-avatar {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  background: var(--acec-gold);
  border: 1px solid var(--acec-border);
}

.acec-portal .acec-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.acec-portal .acec-avatar-initials {
  font-family: var(--font-title);
  font-weight: 700;
  font-size: 15.2px;
  letter-spacing: 0.02em;
  color: var(--acec-navy);
}

/* ── Identity text (§7.4.2) ── */

.acec-portal .acec-profile-identity {
  display: flex;
  flex-direction: column;
  line-height: 1.45;
  /* Shrinkable by default: below 992px the pill has to give way, and a flex
     child will not shrink past its content without this. */
  min-width: 0;
}

/* Desktop only, and a MIN rather than a fixed width (user, 2026-09-17): the
   name and organization are both nowrap, so the block was exactly as wide as
   whatever text it held and a short name left the pill looking pinched against
   the divider. A longer name still widens past this. Confined to >=992px
   because the min-width above is what lets the pill survive a phone. */
@media (min-width: 992px) {
  .acec-portal .acec-profile-identity {
    min-width: 136px;
  }
}

.acec-portal .acec-profile-name {
  font-family: var(--font-title);
  font-size: 14.4px;
  font-weight: 700;
  color: var(--acec-navy);
  white-space: nowrap;
}

/* Grey, not gold — 2026-09-11 readability decision. Do not reintroduce */
/* --acec-gold-dark here (§7.4.2). */
.acec-portal .acec-profile-org {
  display: inline-flex;
  align-items: center;
  gap: 3.2px;
  font-family: var(--font-main);
  font-size: 10.4px;
  font-weight: 600;
  color: var(--acec-nav-link);
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.acec-portal .acec-profile-org svg,
.acec-portal .acec-profile-org [data-lucide] {
  width: 12px;
  height: 12px;
}

/* ── Divider (§7.4.4) ── */

.acec-portal .acec-profile-divider {
  width: 1px;
  align-self: stretch;
  margin: 2.4px 4px;
  background: var(--acec-border);
}

/* ── Icon buttons / gear (§7.4.3) ── */

.acec-portal .acec-header-icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--acec-border);
  background: var(--portal-white);
  color: var(--acec-navy);
  text-decoration: none;
}

.acec-portal .acec-header-icon-button svg,
.acec-portal .acec-header-icon-button [data-lucide] {
  width: 13px;
  height: 13px;
}

.acec-portal .acec-header-icon-button:hover,
.acec-portal .acec-header-icon-button:focus-visible {
  background: var(--acec-gold);
  color: var(--acec-navy);
}

.acec-portal .acec-header-icon-button:focus-visible {
  outline: 2px solid var(--acec-gold-dark);
  outline-offset: 2px;
}

.acec-portal .acec-header-icon-button.is-active {
  background: var(--acec-gold);
  border-color: var(--acec-gold);
}

/* The `.acec-header-login` icon button that used to sit here was removed on
   2026-09-17 (rulings.md R65). Its successor, `.acec-header-signin`, is a
   labelled button and lives in section 7 at the end of this file. */

/* ── Avatar indicators (§7.5) ── */
/* Three explicit modifiers instead of the prototype's inline size/offset */
/* overrides: --nav (header nav row, Task 3's action-required items), */
/* --avatar (here), --menu (Task 5's dropdown certification row, static). */

.acec-portal .acec-invoice-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17.6px;
  height: 17.6px;
  padding: 0 3.2px;
  border-radius: 9999px;
  background: var(--acec-gold);
  color: var(--acec-navy);
  border: 1px solid var(--portal-white);
  font-family: var(--font-main);
  font-size: 9.6px;
  font-weight: 700;
  line-height: 1;
  pointer-events: none;
  z-index: 1;
}

.acec-portal .acec-nav-action-dot {
  position: absolute;
  border-radius: 9999px;
  background: var(--acec-gold);
  border: 1px solid var(--acec-gold-dark);
  /* acecActionPulse is defined once, globally, in shared.css. */
  animation: acecActionPulse 1.8s ease-in-out infinite;
  pointer-events: none;
  z-index: 1;
}

.acec-portal .acec-nav-action-dot--nav {
  top: -4px;
  right: -4px;
  width: 8.8px;
  height: 8.8px;
}

.acec-portal .acec-nav-action-dot--avatar {
  top: -1.6px;
  right: -1.6px;
  width: 9.6px;
  height: 9.6px;
}

.acec-portal .acec-nav-action-dot--menu {
  position: static;
  width: 8.8px;
  height: 8.8px;
  /* Trailing, not adjacent to the label — pushes to the row's right edge
     regardless of label length (§7.6.2 "trailing static action dot"). */
  margin-left: auto;
}

@media (prefers-reduced-motion: reduce) {
  .acec-portal .acec-nav-action-dot { animation: none; }
}

/* Menu-row placement override: the badge is inline content in a flex row here,
   not anchored to an avatar corner, so it drops the absolute positioning and
   is pushed to the row's trailing edge instead (§7.6.2 "trailing badge"). */
.acec-portal .acec-invoice-badge--menu {
  position: static;
  margin-left: auto;
}

/* The pill's badge sits on the gear (desktop) or the menu toggle that takes
   the gear's slot below 992px — user, 2026-09-24; it used to be on the avatar.
   Both hosts need to be the containing block for the absolute offsets above. */
.acec-portal .acec-gear,
.acec-portal .acec-menu-toggle {
  position: relative;
}

/* Pushed further out than the avatar's old -0.25rem: on a 1.75rem button the
   badge covered half the gear / burger glyph (user, 2026-09-24). At -0.6rem
   it only overlaps the button's rim and clears the 13px icon. */
.acec-portal .acec-gear .acec-invoice-badge,
.acec-portal .acec-menu-toggle .acec-invoice-badge {
  top: -9.6px;
  right: -9.6px;
}

/* Once the drawer is open the toggle is an X, a close control, and the
   drawer's Account row is showing the same count right below it. */
.acec-portal .acec-menu-toggle.is-active .acec-invoice-badge {
  opacity: 0;
  transform: scale(0.6);
}

.acec-portal .acec-menu-toggle .acec-invoice-badge {
  transition: opacity 0.16s ease, transform 0.16s ease;
}

@media (prefers-reduced-motion: reduce) {
  .acec-portal .acec-menu-toggle .acec-invoice-badge { transition: none; }
}

/* ── Profile dropdown menu (§7.6) ── */

.acec-portal .acec-profile-dropdown {
  position: absolute;
  top: calc(100% + var(--sp-sm));
  right: 0;
  width: 320px;
  padding: 8px;
  gap: 8px;
  flex-direction: column;
  background: var(--acec-bg-soft);
  border: 1px solid var(--acec-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--acec-shadow-hover);
  z-index: 80;
  animation: acecProfileMenuIn 0.2s ease both;
}

/* The [hidden] attribute (open/close state, §7.6.3) must keep winning over
   this component's own display value, so `display` is set only when the
   dropdown is NOT hidden rather than unconditionally on the rule above —
   an unconditional `display: flex` here would outrank the UA `[hidden]`
   stylesheet rule by specificity and defeat menu.hidden entirely. */
.acec-portal .acec-profile-dropdown:not([hidden]) {
  display: flex;
}

@media (max-width: 991.98px) {
  .acec-portal .acec-profile-dropdown {
    width: min(352px, calc(100vw - 32px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .acec-portal .acec-profile-dropdown { animation: none; }
}

/* ── Dropdown blocks (§7.6.1) ── */

.acec-portal .acec-profile-menu-block {
  background: var(--portal-white);
  border: 1px solid var(--acec-border);
  border-radius: var(--radius-md);
  padding: 5.6px;
  display: flex;
  flex-direction: column;
  gap: 2.4px;
}

.acec-portal .acec-profile-menu-identity {
  display: flex;
  align-items: center;
  gap: 10.4px;
  padding: 6.4px 5.6px;
}

/* ── Dropdown row (§7.6.2) ── */

.acec-portal .acec-profile-menu-item {
  display: flex;
  align-items: center;
  gap: 10.4px;
  width: 100%;
  min-height: 36px;
  padding: 8px 12px;
  border-radius: var(--radius-md);
  font-family: var(--font-main);
  font-size: 14px;
  font-weight: 600;
  color: var(--acec-dark);
  text-decoration: none;
}

.acec-portal .acec-profile-menu-label {
  flex: 1;
  min-width: 0;
}

.acec-portal .acec-profile-menu-item svg,
.acec-portal .acec-profile-menu-item [data-lucide] {
  width: 18px;
  height: 18px;
  color: var(--acec-nav-link);
  flex-shrink: 0;
}

.acec-portal .acec-profile-menu-item:hover,
.acec-portal .acec-profile-menu-item:focus-visible {
  background: rgba(247, 201, 93, 0.12);
}

/* Active row: gold fill, and its hover/focus stays gold rather than falling */
/* back to the lighter tint above (§7.6.2 "its hover stays gold"). */
.acec-portal .acec-profile-menu-item.is-active {
  background: var(--acec-gold);
  font-weight: 700;
}

.acec-portal .acec-profile-menu-item.is-active svg,
.acec-portal .acec-profile-menu-item.is-active [data-lucide] {
  color: var(--acec-navy);
}

.acec-portal .acec-profile-menu-item.is-active:hover,
.acec-portal .acec-profile-menu-item.is-active:focus-visible {
  background: var(--acec-gold);
}

/* Action-required, not active: gold outline + tint, trailing static dot */
/* (the dot itself is .acec-nav-action-dot--menu, added above). */
.acec-portal .acec-profile-menu-item--action {
  border: 1px solid var(--acec-gold);
  background: rgba(247, 201, 93, 0.12);
}

.acec-portal .acec-profile-menu-item--action svg,
.acec-portal .acec-profile-menu-item--action [data-lucide] {
  color: var(--acec-gold-dark);
}

@media (max-width: 991.98px) {
  .acec-portal .acec-profile-menu-item {
    min-height: 44px;
  }
}

/* ── Mobile navigation (§7.7) ── */

/* Hidden by default (>=992px): "the toggle is display:none and the mobile
   nav is always hidden" — this holds even if the [hidden] attribute were
   ever missing, because nothing below overrides it outside the <992px
   media query. */
.acec-portal .acec-header-mobile-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  border: 1px solid var(--acec-border);
  background: var(--portal-white);
  color: var(--acec-navy);
  flex-shrink: 0;
}

.acec-portal .acec-header-mobile-toggle svg,
.acec-portal .acec-header-mobile-toggle [data-lucide] {
  width: 18px;
  height: 18px;
}

.acec-portal .acec-header-mobile-toggle:focus-visible {
  outline: 2px solid var(--acec-gold-dark);
  outline-offset: 2px;
}

/* The stacked `.acec-header-mobile-nav` list and its `.acec-header-mobile-link`
   rows were removed on 2026-09-23: signed out, the burger now opens the same
   drawer as signed in (section 7 at the end of this file). */

@media (max-width: 991.98px) {
  /* Desktop nav row is not rendered below 992px; the hamburger takes its
     place — always present, including on the home page (§7.7). */
  .acec-portal .acec-header-nav {
    display: none;
  }

  .acec-portal .acec-header-mobile-toggle {
    display: inline-flex;
  }
}

/* ── Motion pass (§3.1) ──
   Every animation/transition this module introduced, disabled under
   `prefers-reduced-motion: reduce`. The action-dot pulse and the dropdown
   entrance already have their own guards next to their rules (added in
   Tasks 4 and 5, immediately above `.acec-nav-action-dot` and
   `.acec-profile-dropdown` respectively) — kept as separate blocks rather
   than merged into this one because their exact selectors are pinned by
   frozen assertions in domtest/task4.js and domtest/task5.js (each regex
   requires its selector to be the first rule immediately inside its own
   `@media (prefers-reduced-motion: reduce) { ... }` block; merging would
   break one or the other). All three blocks are the same media feature and
   have the identical combined effect as one block would. This block adds
   the one motion this module was still missing: the brand-arrow hover
   translate (§7.2). */
@media (prefers-reduced-motion: reduce) {
  .acec-portal .acec-brand-link:hover svg,
  .acec-portal .acec-brand-link:focus-visible svg {
    transform: none;
  }

  /* The gold still appears on hover — only the fade is dropped. A colour change
     is not motion, but this file's convention is that every transition it
     introduces is disabled here, so the transition goes and the state stays. */
  .acec-portal .acec-brand-arrow svg {
    transition: none;
  }
}

/* ── Header identity loading state (spec §9: "Give every region a real
   loading state ... rather than an empty box that looks like missing data").
   Added in the final fix wave (2026-09-16) — the header had none. Rendered by
   AOHeader.js's buildAvatarSkeleton()/buildIdentitySkeleton() while the
   `member` branch has not been delivered; before this, a header painted ahead
   of its content detail showed an empty gold circle and two blank text lines,
   which is indistinguishable from a member record with no name on file.

   rulings-digest.md R23 applies: the requirement is the spec's, the visual
   form is ours, and it deliberately mirrors the Profile panel's skeleton
   treatment (same fill token, same rounded bars) so "loading" looks the same
   everywhere in the portal. The header sits on white, so it uses the
   on-light fill — see shared.css's :root for the pair.

   Appended at the end of this file on purpose: domtest/task4.js and
   domtest/task5.js pin several selectors by their SOURCE ADJACENCY (see the
   motion-pass comment above, and OPEN-ITEMS.md), so new rules go after
   everything rather than in among them. ── */

.acec-portal .acec-header-skeleton {
  background: var(--portal-skeleton-on-light);
  border-radius: var(--radius-sm);
  display: block;
}

.acec-portal .acec-header-skeleton-avatar {
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

.acec-portal .acec-header-skeleton-line--name {
  width: 112px;
  height: 14.4px;
  margin-bottom: 4.8px;
}

.acec-portal .acec-header-skeleton-line--org {
  width: 72px;
  height: 10.4px;
}

/* ══ Mobile pass (user, 2026-09-21) ═══════════════════════════════════════
   Four changes, all below 992px, all appended here rather than edited into
   the rules above for the reason the skeleton block gives: several selectors
   in this file are pinned by source adjacency, so new rules go after
   everything.

     1. The brand halves.
     2. The gear becomes a hamburger that opens the member menu.
     3. That hamburger morphs into the X that closes it.
     4. The member menu becomes a full-screen drawer that comes in from the
        left — AOHero's motion, this header's colours, rows and badges.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. Brand (§7.2), halved below 992px ──
   At 2.25rem the wordmark took most of a phone's header row and pushed the
   profile pill onto a line of its own. The arrow goes with it: left at 18px it
   would have been taller than the logo it sits beside. */

@media (max-width: 991.98px) {
  /* Fluid, not stepped (user, 2026-09-21).
  
     This went 1.125rem -> 1.6rem -> a second 1.2rem step under 576px before
     it became clear that what the wordmark actually has to track is the width
     of the phone, continuously: a breakpoint gives the 393px handset and the
     430px one the same logo, and the wrong one for at least one of them.
  
     The floor is the size approved on a 393px iPhone; the ceiling is the
     desktop 2.25rem, so nothing jumps as the bar crosses 992px into the
     unscoped rule above. The 2.6vw slope is what puts the floor's end exactly
     at ~390px:
  
         360px -> 1.2rem (floor)   576px -> ~1.49rem
         390px -> 1.2rem (floor)   768px -> ~1.80rem
         430px -> ~1.25rem         991px -> ~2.16rem  */
  .acec-portal .acec-brand-link img {
    height: clamp(19.2px, 8.8px + 2.6vw, 36px);
  }

  /* The arrow rides the same curve — it is part of the mark, and a fixed one
     beside a shrinking wordmark is what made the first pass look wrong. */
  .acec-portal .acec-brand-arrow {
    width: clamp(12.8px, 6.4px + 1.9vw, 22.4px);
    height: clamp(12.8px, 6.4px + 1.9vw, 22.4px);
    margin-left: -1.6px;
  }

  .acec-portal .acec-brand-arrow svg,
  .acec-portal .acec-brand-arrow [data-lucide] {
    width: clamp(11px, 5.12px + 1.55vw, 18px);
    height: clamp(11px, 5.12px + 1.55vw, 18px);
  }
}

/* ── 2. Hamburger glyph ──
   Three spans, not a lucide icon: the open and closed states are the same
   three elements in different places, which is what lets the change be an
   animation instead of a swap. Geometry comes from three custom properties so
   the two buttons that use it (the 1.75rem one in the pill, the 2.5rem
   standalone one) can size it without redeclaring the bar positions. */

.acec-portal .acec-burger {
  --burger-width: 14px;
  --burger-bar: 2px;
  --burger-gap: 4px;
  /* The distance each outer bar travels to meet the middle — the one value
     the top/bottom offsets and the X transform both derive from. */
  --burger-step: calc(var(--burger-bar) + var(--burger-gap));

  position: relative;
  display: block;
  flex-shrink: 0;
  width: var(--burger-width);
  height: calc(var(--burger-bar) * 3 + var(--burger-gap) * 2);
  color: inherit;
}

.acec-portal .acec-burger > span {
  position: absolute;
  left: 0;
  width: 100%;
  height: var(--burger-bar);
  border-radius: var(--burger-bar);
  background: currentColor;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.16s ease;
}

.acec-portal .acec-burger > span:nth-child(1) { top: 0; }
.acec-portal .acec-burger > span:nth-child(2) { top: var(--burger-step); }
.acec-portal .acec-burger > span:nth-child(3) { top: calc(var(--burger-step) * 2); }

/* Open: the middle bar goes, the outer two travel to where it was and cross.
   Both buttons carry `is-active` from the same JS state, so the icon can
   never show an X over a closed menu. */
.acec-portal .acec-menu-toggle.is-active .acec-burger > span:nth-child(1),
.acec-portal .acec-header-mobile-toggle.is-active .acec-burger > span:nth-child(1) {
  transform: translateY(var(--burger-step)) rotate(45deg);
}

.acec-portal .acec-menu-toggle.is-active .acec-burger > span:nth-child(2),
.acec-portal .acec-header-mobile-toggle.is-active .acec-burger > span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0.2);
}

.acec-portal .acec-menu-toggle.is-active .acec-burger > span:nth-child(3),
.acec-portal .acec-header-mobile-toggle.is-active .acec-burger > span:nth-child(3) {
  transform: translateY(calc(var(--burger-step) * -1)) rotate(-45deg);
}

/* The standalone toggle is a 2.5rem square against the pill's 1.75rem circle,
   so its glyph is scaled to match the 18px icon it replaced. */
.acec-portal .acec-header-mobile-toggle .acec-burger {
  --burger-width: 18px;
  --burger-gap: 5px;
}

/* ── 3. The gear's mobile replacement (§7.4.3) ──
   Exactly one of the two is in the pill at any width: the gear navigates to
   Account, the burger opens the menu that contains Account. */

.acec-portal .acec-menu-toggle {
  display: none;
  padding: 0;
  cursor: pointer;
}

@media (max-width: 991.98px) {
  .acec-portal .acec-gear {
    display: none;
  }

  .acec-portal .acec-menu-toggle {
    display: inline-flex;
  }
}

/* ── 4. The member menu as a drawer (§7.6, §7.7) ──

   Same element, same blocks, same rows and the same badges as the desktop
   dropdown — only the frame changes. It is `position: fixed` and starts at
   `--acec-drawer-top`, which AOHeader.js sets to the header bar's bottom edge
   at open: the burger the drawer came out of has to stay visible above it,
   because that X is the only way to close it.

   The destinations block is the one piece of content that is mobile-only. The
   desktop nav row is not rendered below 992px, so without it the menu would
   reach no section of the portal at all. */

@media (min-width: 992px) {
  .acec-portal .acec-profile-menu-block--nav {
    display: none;
  }
}

@media (max-width: 991.98px) {
  .acec-portal .acec-profile-dropdown {
    position: fixed;
    top: var(--acec-drawer-top, 0px);
    right: 0;
    bottom: 0;
    left: 0;
    /* Overrides the `width: min(22rem, ...)` this same breakpoint sets higher
       up, which sized the panel when it was still a dropdown. */
    width: auto;
    height: auto;
    padding: var(--sp-md) var(--sp-md) var(--sp-xl);
    gap: var(--sp-sm);
    border: 0;
    border-radius: 0;
    box-shadow: none;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* Above the page, below nothing else of ours — the header bar it hangs
       from is never covered, so the two do not compete. */
    z-index: 9000;
    /* The desktop entrance is replaced rather than layered on: a panel that
       fades down from its trigger is the wrong motion for one that arrives
       from the left edge of the screen. */
    animation: none;
    transform: translateX(-100%);
    opacity: 0;
    transition: transform 0.32s cubic-bezier(0.25, 0.46, 0.45, 0.94),
                opacity 0.32s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    will-change: transform;
  }

  /* `is-open`, not `:not([hidden])`: the drawer keeps `hidden` off for the
     length of its slide-out, so the attribute cannot carry the open state on
     its own (AOHeader.js, isOpen()). */
  .acec-portal .acec-profile-dropdown.is-open {
    transform: translateX(0);
    opacity: 1;
  }

  /* The identity block is the one piece of the dropdown the drawer drops: the
     pill it hangs from is still on screen directly above it, showing the same
     avatar, name and organization (user, 2026-09-21). */
  .acec-portal .acec-profile-menu-block--identity {
    display: none;
  }
}

/* Locks the page behind the drawer. Unscoped on purpose — <body> is the host
   page's element, never ours. `overflow` only: AOHero's companion
   `position: fixed` also scrolls the page to the top behind the drawer, which
   the member then finds when they close it. */
body.acec-menu-open {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .acec-portal .acec-burger > span {
    transition: none;
  }

  .acec-portal .acec-profile-dropdown {
    transition: none;
  }
}

/* ── 5. Narrow phones: the header bar stays ONE row (user, 2026-09-21) ──

   At 360px the bar broke in two — wordmark on the first line, profile pill on
   the second — and the cause was not only the logo's width. `.acec-header-inner`
   is `flex-wrap: wrap`, and the pill could not shrink to make room because
   BOTH identity lines are `white-space: nowrap` with no overflow: a nowrap run
   of text has a min-content width equal to its full length, so the pill's
   minimum was the whole of "Angie Cooper" plus the whole of "ACEC Arkansas"
   however narrow the screen got. `min-width: 0` on the identity column does
   not help while its children can still refuse to shrink.

   So: no wrapping below 992px, a tighter gap, and a pill whose text truncates
   instead of pushing. The wordmark shrinks again only on the narrowest class
   of phone, where truncating alone is not enough. */

@media (max-width: 991.98px) {
  .acec-portal .acec-header-inner {
    flex-wrap: nowrap;
    gap: var(--sp-sm);
  }

  /* Shrinkable, so the brand keeps its intrinsic width and the pill gives. */
  .acec-portal .acec-profile-card {
    min-width: 0;
  }

  /* NOT `overflow: hidden` on the trigger itself, tempting as it is: the
     action dot is absolutely positioned OUTSIDE the avatar's box, and the
     invoice badge outside the menu toggle's (top/right: -0.25rem) — clipping
     the trigger or the card would cut them off. The
     truncation belongs on the two text lines, which are the only things that
     refuse to shrink. */

  /* The name ellipses; the organization line simply clips, because it is an
     inline-flex row (text + the Network icon) and `text-overflow` has nothing
     to act on there. Both keep `white-space: nowrap` — a second line inside
     the pill is the thing being avoided. */
  .acec-portal .acec-profile-name {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .acec-portal .acec-profile-org {
    overflow: hidden;
  }
}

/* The brand is NOT resized here any more — the clamp above already covers
   every width below 992px in one curve. What is left are the things that do
   not scale linearly: the gaps and the pill's own type, which have a legible
   floor of their own. */
@media (max-width: 575.98px) {
  .acec-portal .acec-header-inner {
    gap: var(--sp-xs);
  }

  .acec-portal .acec-profile-card {
    gap: var(--sp-xs);
    padding: 4px 6.4px 4px 4.8px;
  }

  .acec-portal .acec-profile-name {
    font-size: 13.12px;
  }

  .acec-portal .acec-profile-org {
    font-size: 9.6px;
    letter-spacing: 0.03em;
  }
}

/* ══ Long names and organizations (user, 2026-09-23) ═════════════════════
   A long display name or organization ("U.S. Army Corps of Engineers,
   Savannah District") widened the pill to whatever the text measured, and
   the dropdown's identity block ran out past its own 20rem frame — on a
   desktop that frame sits at the right edge of the page, so the overflow
   became a horizontal scrollbar.

   The pill now has a width cap and both of its lines end in an ellipsis; the
   full text is on the identity block's `title` (AOHeader.js, buildIdentity).
   The dropdown, which has room to spare vertically, wraps instead — up to two
   lines per field — so opening the menu is how a member sees the whole name.
   ═══════════════════════════════════════════════════════════════════════ */

/* The pill can never be wider than the row it sits in, whatever the host
   page's own container rules do. */
.acec-portal .acec-profile-card {
  max-width: 100%;
}

.acec-portal .acec-profile-name {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The row itself only clips; the ellipsis belongs to the text span inside it,
   because `text-overflow` does nothing to a flex container. */
.acec-portal .acec-profile-org {
  max-width: 100%;
  overflow: hidden;
}

.acec-portal .acec-profile-org-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.acec-portal .acec-profile-org svg,
.acec-portal .acec-profile-org [data-lucide] {
  flex-shrink: 0;
}

/* Desktop cap. Wide enough for a typical "Ms. First Last" and a two-word
   firm without truncating; the >=992px min-width above still keeps a short
   name from looking pinched. */
@media (min-width: 992px) {
  .acec-portal .acec-profile-trigger .acec-profile-identity {
    max-width: 256px;
  }
}

/* ── The dropdown's identity block wraps rather than truncates ── */

.acec-portal .acec-profile-menu-identity .acec-profile-identity {
  flex: 1;
  min-width: 0;
  line-height: 1.3;
  gap: 2.4px;
}

.acec-portal .acec-profile-menu-identity .acec-profile-name,
.acec-portal .acec-profile-menu-identity .acec-profile-org-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  white-space: normal;
  overflow-wrap: anywhere;
}

.acec-portal .acec-profile-menu-identity .acec-profile-org {
  display: flex;
  align-items: flex-start;
}

.acec-portal .acec-profile-menu-identity .acec-profile-org svg,
.acec-portal .acec-profile-menu-identity .acec-profile-org [data-lucide] {
  margin-top: 0.1em;
}

/* ── Mobile: the pill is avatar + burger only (user, 2026-09-23) ──
   Truncating was not enough on a real phone — the name and organization still
   crowded the row. Below 992px the pill drops its text entirely, and the
   drawer's identity block comes back (it was hidden in section 4 only because
   the pill used to show the same name), so the member's full name and
   organization are still one tap away. */

@media (max-width: 991.98px) {
  .acec-portal .acec-profile-trigger .acec-profile-identity {
    display: none;
  }

  .acec-portal .acec-profile-card {
    flex-shrink: 0;
  }

  .acec-portal .acec-profile-menu-block--identity {
    display: flex;
  }
}

/* ══ 7. Signed out: Sign in + the member drawer (user, 2026-09-23) ════════
   The slot the profile card fills for a member holds a Sign in control drawn
   as that same card: white pill, a 2.5rem gold circle where the avatar sits
   (the log-in icon instead of initials), then "Sign in" in the name's type,
   centred on the circle. Sized to its content, not to the member pill.
   Below 992px the pill gains the
   member pill's divider and 1.75rem burger, which opens the same drawer as
   signed in (AOHeader.js, buildGuestActions). ══════════════════════════ */

.acec-portal .acec-guest-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  flex-shrink: 0;
  background: var(--portal-white);
  border: 1px solid var(--acec-border);
  border-radius: 9999px;
  padding: 4.8px 5.6px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.acec-portal .acec-header-signin {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding-right: 12px;
  border-radius: 9999px;
  color: var(--acec-navy);
  text-decoration: none;
}

.acec-portal .acec-signin-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--acec-gold);
  border: 1px solid var(--acec-border);
  color: var(--acec-navy);
  transition: background 0.2s ease;
}

.acec-portal .acec-signin-badge svg,
.acec-portal .acec-signin-badge [data-lucide] {
  width: 18px;
  height: 18px;
}

.acec-portal .acec-signin-title {
  font-family: var(--font-title);
  font-size: 14.4px;
  font-weight: 700;
  color: var(--acec-navy);
  line-height: 1;
  white-space: nowrap;
}

/* The whole pill answers the pointer; the circle darkens like a pressed nav
   item. */
.acec-portal .acec-guest-actions:hover {
  border-color: var(--acec-gold);
  box-shadow: var(--acec-shadow);
}

.acec-portal .acec-header-signin:hover .acec-signin-badge,
.acec-portal .acec-header-signin:focus-visible .acec-signin-badge {
  background: var(--acec-gold-dark);
}

.acec-portal .acec-header-signin:focus-visible {
  outline: 2px solid var(--acec-gold-dark);
  outline-offset: 2px;
}

/* Desktop: no burger, so no divider either — the pill ends after the text. */
.acec-portal .acec-guest-divider {
  display: none;
}

@media (max-width: 991.98px) {
  .acec-portal .acec-guest-actions {
    gap: var(--sp-xs);
    padding: 4px 6.4px 4px 4.8px;
  }

  .acec-portal .acec-guest-divider {
    display: block;
  }

  /* "Sign in" stays even though the member pill drops its text: a bare icon
     would not say what it does. */
  .acec-portal .acec-header-signin {
    padding-right: 5.6px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .acec-portal .acec-guest-actions,
  .acec-portal .acec-signin-badge {
    transition: none;
  }
}

/* ══ Avatar size vs. host root font size (2026-10-10) ════════════════════
   AOHeader.js sets the avatar wrapper's size inline in rem (2.5rem in the
   pill, 3rem in the dropdown), which a host page's `html { font-size }`
   rescales. Pinned here to the 16px-root values; !important is needed only
   to beat the inline style. */

.acec-portal .acec-profile-trigger .acec-avatar-wrapper {
  width: 40px !important;
  height: 40px !important;
}

.acec-portal .acec-profile-menu-identity .acec-avatar-wrapper {
  width: 48px !important;
  height: 48px !important;
}
/* ══ Footer pinned to the bottom on short pages (2026-10-10) ═════════════
   netFORUM lays the page out as one table, #PageFrameWorkTable, with three
   rows: top (this header), middle (content panes) and bottom (#footer in
   #BottomPane). On a page with little content the table ended mid-viewport
   and left blank space under the footer.

   The table is given the viewport's height (a table's `height` acts as a
   minimum — it still grows with its content), and the top and bottom rows a
   nominal 1px so they stay at their content height and the spare height goes
   to the middle row. The content cells are valign="top", so their content
   stays where it was.

   Outside .acec-portal on purpose — these are the host's elements — and
   limited by :has() to pages that carry this header. */

body:has(#aoheader-mount) #PageFrameWorkTable {
  height: 100vh;
}

body:has(#aoheader-mount) #eWebFrameWorkTopRow,
body:has(#aoheader-mount) #eWebFrameWorkBottomRow {
  height: 1px;
}

/* ══ Scrollbar compensation (2026-10-10) ══════════════════════════════════
   A page tall enough to scroll gets a vertical scrollbar that narrows the
   viewport, so everything right-aligned or proportional in the header and
   the footer — the nav row, the profile pill, the footer's columns — sat a
   few pixels further left than on a short page.

   `scrollbar-gutter: stable` fixed the shift but left an empty white strip
   down the right edge of every short page. Instead, the bands keep their
   full-width backgrounds and only their RIGHT padding gives up the
   scrollbar's width: `100vw - 100%` is that width (0 when there is none), so
   the content box is the same 100vw - 64px whether the page scrolls or not.
   max(0px, …) keeps the 16px mobile padding valid next to a classic 17px
   scrollbar. */

.acec-portal .aoheader > .aoheader-container {
  padding-right: max(0px, calc(16px - (100vw - 100%)));
}

@media (min-width: 768px) {
  .acec-portal .aoheader > .aoheader-container {
    padding-right: max(0px, calc(24px - (100vw - 100%)));
  }
}

@media (min-width: 1200px) {
  .acec-portal .aoheader > .aoheader-container {
    padding-right: max(0px, calc(32px - (100vw - 100%)));
  }
}

/* The theme's footer (acec.css: `footer .acec-container`, 32px sides). Host
   element, so outside .acec-portal and limited to pages with this header. */
body:has(#aoheader-mount) #footer .acec-container {
  padding-right: max(0px, calc(32px - (100vw - 100%)));
}
