/* ==========================================================================
   In-page record header (round 7) — back · identity · stage chip · actions,
   with the tab bar built into its bottom edge. The main app header carries
   none of it: the record identity sits at the top of the content surface with
   the tab strip beneath it, left justified. The global dark global-app-header is left empty for this page;
   everything below sits on the light content surface. The bar, back link,
   heading, actions cluster, and tab strip now come from the canonical
   shared/components/record_header.html (styled in components/page-header.css)
   — this file keeps only opportunity's own inner-element styling: the stage
   chip, the actions cluster's buttons/divider, and the description slot.
   ========================================================================== */
/* The whole record reads as ONE card. The shell (.opportunities-tabs-shell) draws the only
   border, radius, and background; the header, the tab strip, and the active pane
   are borderless sections inside it, so there is a single continuous surface with
   no stacked-box seam. overflow:hidden clips those sections to the rounded corners. */
.opportunities-tabs-shell {
    background: var(--color-card-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    margin-bottom: var(--spacing-mdlg);
}

/* The canonical record header draws its own card frame — dropped here so it sits
   flush on the shell above, the same "first-level loses its own chrome" treatment
   as the pane cards below (.opportunities-tab-pane > .card). */
.opportunities-tabs-shell > .global-record-header {
    background: transparent;
    border: none;
    border-radius: 0;
    margin-bottom: 0;
}

/* Actions cluster — Invite (solid) · Add role (outline) · divider · edit/gear/trash. */
.opportunities-record-header__actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-xsm);
    flex-shrink: 0;
    flex-wrap: wrap;
}

.opportunities-record-header__cta {
    white-space: nowrap;
}

.opportunities-record-header__divider {
    width: 1px;
    align-self: stretch;
    min-height: 24px;
    margin: 0 var(--spacing-xs);
    background: var(--color-grey-200);
}

/* Edit / gear / trash — light-surface icon buttons (the on-dark header twins are gone). */
.opportunities-record-header__icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    border-radius: var(--radius-button);
    background: transparent;
    color: var(--color-text-muted);
    font-size: var(--font-size-md);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.opportunities-record-header__icon-btn:hover,
.opportunities-record-header__icon-btn:focus-visible {
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
}

.opportunities-record-header__icon-btn--danger:hover,
.opportunities-record-header__icon-btn--danger:focus-visible {
    background: color-mix(in srgb, var(--color-error) 10%, transparent);
    color: var(--color-error);
}

.opportunities-record-header__icon-btn i {
    display: flex;
    line-height: 1;
}

/* Optional description, sitting under the identity bar and above the tabs. Muted
   and indented to line up with the identity, so it reads as record context. */
.opportunities-record-header__desc {
    padding: 0 var(--spacing-md) var(--spacing-smd);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.opportunities-record-header__desc :last-child {
    margin-bottom: 0;
}

/* Opportunity identity — service · detail · recipient. Shared by the in-page record
   header and the gear drawer's out-of-band refresh. All three items share the
   service-type heading font so the line reads as one title; a dimmed · separates them. */
.opportunities-detail__identity {
    display: inline-flex;
    align-items: baseline;
    gap: var(--spacing-sm);
    min-width: 0;
    overflow: hidden;
    vertical-align: middle;
    color: var(--sidebar-text);
}

/* On the light content surface (round 7) the identity uses the normal text color and
   is free to use the full width the record header gives it. */
.opportunities-detail__identity--inpage {
    color: var(--color-text);
    max-width: none;
}

/* One uniform treatment for all three items — same heading font as the service type
   (Nick, 2026-06: "use the service type font for everything"). */
.opportunities-detail__identity-service,
.opportunities-detail__identity-detail,
.opportunities-detail__identity-recipient {
    font-family: var(--font-family-heading);
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-size: clamp(0.9rem, 2vw, 1.1rem);
    line-height: 1.1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* The service type never truncates; the detail and recipient yield first when narrow. */
.opportunities-detail__identity-service {
    flex-shrink: 0;
}

/* Dimmed · before every item after the service type (always first), so any present
   subset of {detail, recipient} stays correctly punctuated with no leading dot. */
.opportunities-detail__identity > span:not(:first-child)::before {
    content: "·";
    margin-right: var(--spacing-sm);
    opacity: 0.5;
}

/* The two sentence-case title styles (title_style client_over_service and
   service_over_client): a big line, then a small muted line under it, no dot.
   Keyed on the big line being there, so the wrapper keeps the one class it has
   in the header and in the gear's out-of-band refresh. Each selector is two
   classes deep, which outranks the phone block's running-text rule for the caps
   line without a phone rule of its own. */
.opportunities-detail__identity:has(> .opportunities-detail__identity-big) {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    max-width: 100%;
}

.opportunities-detail__identity > .opportunities-detail__identity-big,
.opportunities-detail__identity > .opportunities-detail__identity-small {
    display: block;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-transform: none;
    letter-spacing: normal;
}

.opportunities-detail__identity > .opportunities-detail__identity-big {
    font-family: var(--font-family-heading);
    font-weight: var(--font-weight-heading);
    font-size: var(--font-size-h3);
    line-height: var(--line-height-heading);
    color: var(--color-text);
}

.opportunities-detail__identity > .opportunities-detail__identity-small {
    font-family: inherit;
    font-weight: normal;
    font-size: var(--font-size-sm);
    line-height: var(--line-height);
    color: var(--color-text-secondary);
}

.opportunities-detail__identity > .opportunities-detail__identity-small:not(:first-child)::before {
    content: none;
}

/* Role roster — one tidy list: a row per role, split by hairlines, no card around
   each row (Nick's pick for the booking page redesign). */
.opportunities-detail__roles {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0;
    padding: var(--spacing-xs) var(--spacing-md);
}

/* Each role is one row: a face + name on the left, a due/open pill in the middle,
   the pay number and Remove on the right. Any warning wraps to a full-width line
   beneath. Appearance (flex internals, colors, fonts) lives in the per-part rules
   further down; only layout is grouped here. */
.opportunities-detail__role-tile {
    /* Containing block for .opportunities-detail__role-trigger, the overlay that makes the
       whole row open the edit drawer. */
    position: relative;
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    column-gap: var(--spacing-sm);
    row-gap: var(--spacing-xsm);
    padding: var(--spacing-smd) var(--spacing-xs);
    border: 0;
    border-bottom: 1px solid var(--color-grey-100);
    background: var(--color-card-bg);
    transition: background-color 0.15s ease;
}

.opportunities-detail__role-tile:last-child {
    border-bottom: 0;
}

/* ── The row as a control ─────────────────────────────────────────────────
   A coordinator's roster row opens the edit drawer focused on its own role, so
   the row itself has to look and behave like something you can click — the two
   far-right buttons that used to do this are gone (Nick, 07-31).

   The trigger is a real, empty <button> stretched over the tile by inset: 0.
   Absolutely positioned, so it takes no grid cell; transparent, so the row still
   reads as a row. It comes FIRST in the tile's source order, which is what lets
   every positioned thing after it — the name link, the waiting link, the bill
   pill, Remove — paint on top and keep its own click without a z-index anywhere.
   Anything in the row that is NOT positioned falls through to this on purpose. */
.opportunities-role-tile--clickable {
    cursor: pointer;
}

/* The hover has to be legible, not merely present: the row is now the only way
   into a role's details, so a tinted border alone (which is all the base tile
   hover a few rules up gives every row, clickable or not) does not say "this
   one opens". Selector carries the tile class as well so it outranks the
   --filled / --open / --mine background rules further down the file.

   The tint is LAYERED, not swapped: background-image paints above
   background-color, so hovering a row that already carries a state colour —
   mine, available — tints it without costing it that colour. */
.opportunities-detail__role-tile.opportunities-role-tile--clickable:hover {
    background-image: linear-gradient(
        var(--ink-alpha-05),
        var(--ink-alpha-05)
    );
}

.opportunities-detail__role-trigger {
    position: absolute;
    inset: 0;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
}

/* The ring belongs on the tile's outline, not on a transparent rectangle nobody
   can see — inheriting the radius is what makes the two agree. */
.opportunities-detail__role-trigger:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
    border-radius: inherit;
}

/* Above the overlay (paint order, no z-index): each of these goes somewhere the
   edit drawer does not. The static pay pill is deliberately absent — clicking a
   number that nothing else owns should open the row like the rest of it.
   (The tooltip bubble itself is the shared portal, position:fixed on <body> —
   it neither needs nor is affected by these positioning contexts.) */
.opportunities-detail__role-namelink,
.opportunities-detail__role-waiting,
.opportunities-detail__role-pay.global-value-pill--interactive {
    position: relative;
}

.opportunities-detail__role-identity {
    grid-column: 1;
    grid-row: 1;
}

/* The status pill (a filled role's due date, an open role's waiting count)
   sits between the identity and the actions. */
.opportunities-detail__role-due,
.opportunities-detail__role-waiting {
    grid-column: 2;
    grid-row: 1;
}

.opportunities-detail__role-actions {
    grid-column: 3;
    grid-row: 1;
}

/* The coordinator's overflow kebab takes a FOURTH column, which the tile never
   declares. That is deliberate: an implicit track only exists on the rows that put
   something in it, so a role with no kebab — open, worker view, no approved
   assignment — keeps the three-column tile it had and no fourth gap opens under it.
   Declaring a fourth `auto` column up in grid-template-columns instead would spend a
   column-gap on every roster row whether or not it had a menu.

   It needs no `position: relative` of its own to clear the stretched row overlay:
   .global-action-dropdown already sets it, which is also what its menu positions against.
   The two are one dependency, not two — if that ever went static the menu would
   escape its anchor at the same moment the kebab fell behind the overlay. */
.opportunities-detail__role-menu {
    grid-column: 4;
    grid-row: 1;
}

.opportunities-detail__role-warning {
    grid-column: 1 / -1;
}

.opportunities-detail__role-tile.opportunities-role-tile--filled {
    background: var(--color-card-bg);
}

.opportunities-detail__role-tile.opportunities-role-tile--mine {
    background: var(--role-tile-mine-bg, color-mix(in srgb, var(--color-action) 10%, white));
    border-color: color-mix(in srgb, var(--color-action) 30%, var(--color-grey-200));
}

.opportunities-detail__role-tile.opportunities-role-tile--open {
    background: var(--color-card-bg);
}

.opportunities-detail__role-identity {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    min-width: 0;
    flex: 1;
}

.opportunities-detail__role-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.opportunities-detail__role-name {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-md);
    color: var(--color-text);
    line-height: 1.2;
}

.opportunities-detail__role-worker-name {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The name goes to the role's own assignment page. It sits above the tile's
   trigger overlay (see .opportunities-detail__role-trigger) so the two destinations stay
   distinct: the name for this role's page, the rest of the row for the drawer. */
.opportunities-detail__role-namelink {
    display: inline-flex;
    flex-direction: column;
    min-width: 0;
    text-decoration: none;
    color: inherit;
    border-radius: 4px;
}

.opportunities-detail__role-namelink:hover .opportunities-detail__role-name,
.opportunities-detail__role-namelink:focus-visible .opportunities-detail__role-name {
    color: var(--color-action);
    text-decoration: underline;
}

.opportunities-detail__role-namelink:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}

/* ── Roster avatars — a 52px face leads each row ─────────────────────────── */
.opportunities-detail__role-avatar {
    width: 52px;
    height: 52px;
    font-size: var(--font-size-lg);
    flex-shrink: 0;
}

/* No photo on file → ink-tinted initials. */
.opportunities-detail__role-avatar--initials {
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
}

/* A teammate's filled role in the worker view — a neutral check, never a face. */
.opportunities-detail__role-avatar--filled {
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
    font-size: var(--font-size-xl);
}

/* Open role — a dashed empty circle with a plus. */
.opportunities-detail__role-avatar--empty {
    background: transparent;
    border: 2px dashed var(--role-tile-open-border, #d1d5db);
    color: var(--color-grey-500);
    font-size: var(--font-size-xl);
}

/* Filled row: worker name is the primary bold line, role name the muted line beneath. */
.opportunities-detail__role-worker-name--primary {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.opportunities-detail__role-name--primary {
    font-size: var(--font-size-lg);
}

.opportunities-detail__role-name--secondary {
    font-weight: 400;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Due-date pill on a filled row — fully written out (day, month, year, time). */
.opportunities-detail__role-due {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    white-space: nowrap;
}

/* Open role: skill-requirement chips in brand tokens. */
.opportunities-detail__role-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-xs);
    margin-top: var(--spacing-xs);
}

.opportunities-detail__role-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.0625rem var(--spacing-xsm);
    border-radius: 0.625rem;
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
}

/* One quiet count of who's waiting for approval — amber/pending token, no names.
   A plain link to the signup dashboard, where approvals actually happen. */
.opportunities-detail__role-waiting {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: 0.0625rem var(--spacing-xsm);
    border-radius: 0.625rem;
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
    text-decoration: none;
    color: var(--color-warning);
    background: color-mix(in srgb, var(--color-warning) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-warning) 25%, transparent);
}

.opportunities-detail__role-waiting:hover,
.opportunities-detail__role-waiting:focus-visible {
    background: color-mix(in srgb, var(--color-warning) 20%, transparent);
}

.opportunities-detail__role-waiting:focus-visible {
    outline: 2px solid var(--color-warning);
    outline-offset: 2px;
}

/* An unfilled role with nobody waiting: state, not a destination. Same pill shape
   as the waiting count so the middle column keeps one silhouette, in muted tokens
   because there is nothing here to act on — the row itself is the action. */
.opportunities-detail__role-status {
    grid-column: 2;
    grid-row: 1;
    display: inline-flex;
    align-items: center;
    padding: 0.0625rem var(--spacing-xsm);
    border-radius: var(--radius-pill);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
    color: var(--color-text-muted);
    background: var(--ink-alpha-05);
    border: 1px solid var(--color-grey-200);
}

/* Round 7 Option B: a plain footer link from the pure-display roster to the signup
   dashboard, where open roles get filled and pending sign-ups are reviewed. */
.opportunities-detail__roster-manage {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xsm);
    margin: var(--spacing-xs) var(--spacing-smd) var(--spacing-smd);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    text-decoration: none;
    color: var(--color-action);
}

.opportunities-detail__roster-manage:hover,
.opportunities-detail__roster-manage:focus-visible {
    text-decoration: underline;
}

.opportunities-detail__roster-manage:focus-visible {
    outline: 2px solid var(--color-action);
    outline-offset: 2px;
    border-radius: var(--radius-md);
}

/* Availability highlight (worker view, plugin-gated) — soft tint + "Available" tag. */
.opportunities-detail__role-tile.opportunities-role-tile--available {
    background: var(--color-availability-bg);
    border-color: var(--color-availability);
}

.opportunities-role-tile__available-tag {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    margin-top: var(--spacing-2xs);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    color: var(--color-availability);
}

.opportunities-detail__role-warning {
    font-size: var(--font-size-xs);
    color: var(--color-warning);
}

/* Right-side cell: the pay pill, alone. The pill is a static number unless the role
   has a bill, in which case it opens that bill's drawer; setting pay is what clicking
   the row does now, so no "Set pay" button lives here. Shared with the referrer card,
   which still uses the icon buttons.

   Remove came back on 08-07 but not into this cell — it is an item inside the kebab
   in the next column over, which is gated on the coordinator rather than on the
   payments plugin this container is gated on. */
.opportunities-detail__role-actions {
    display: flex;
    gap: var(--spacing-xs);
    align-items: center;
    flex-shrink: 0;
}

/* How often an ongoing seat pays, beside its pay: "$200 Monthly". A quiet word so
   the number stays the thing the eye lands on. */
.opportunities-detail__role-cadence {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    white-space: nowrap;
}

/* Quiet by default, chrome on hover. The row itself is the loud affordance here —
   a kebab wearing the component's default white fill and border would read as the
   tile's primary control and compete with it. Borrowed from the pipeline band's
   kebab, which sits on a busy surface for the same reason. Border stays declared at
   full width in both states so gaining chrome on hover does not shift the row. */
.opportunities-detail__role-menu-toggle {
    background: transparent;
    border-color: transparent;
    color: var(--color-text-light);
    line-height: 1;
}

.opportunities-detail__role-menu-toggle:hover,
.opportunities-detail__role-menu-toggle[aria-expanded="true"] {
    background: var(--color-grey-50);
    border-color: var(--color-border);
    color: var(--color-text-muted);
}

/* The muted second line — "role · pay" (filled) or "pay · skill chips" (open). */
.opportunities-detail__role-subline {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--spacing-xsm);
    min-width: 0;
}

/* The row's extra facts from the gear's Roster rows (time, where, scope of work):
   a quiet third line of small label and value pairs. Empty when the role template
   shows none of them. */
.opportunities-detail__role-facts {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--spacing-smd);
    row-gap: var(--spacing-2xs);
    margin-top: var(--spacing-2xs);
    font-size: var(--font-size-xs);
    color: var(--color-text-secondary);
    min-width: 0;
}

.opportunities-detail__role-facts:empty {
    display: none;
}

.opportunities-detail__role-fact {
    min-width: 0;
    overflow-wrap: anywhere;
}

.opportunities-detail__role-fact-label {
    color: var(--color-text-muted);
}

/* Icon button base. The referrer card is the only consumer left: the roster tile's
   copy/edit buttons went in round 7 Option B, and Remove — its last one — went on
   08-01. Kept because the referrer card still renders two of these. */
.opportunities-detail__role-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    border-radius: var(--radius-button);
    background: transparent;
    color: var(--color-text-muted);
    font-size: var(--font-size-md);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.opportunities-detail__role-icon-btn:hover,
.opportunities-detail__role-icon-btn:focus-visible {
    color: var(--color-action);
    background: var(--color-action-tint-pressed);
}

.opportunities-detail__role-icon-btn:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 1px;
}

.opportunities-props__section-label {
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.8px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    margin-bottom: var(--spacing-smd);
    padding-bottom: var(--spacing-xsm);
    border-bottom: 1px solid #eee;
}

.opportunities-props__field-group {
    margin-bottom: var(--spacing-xs);
}

.opportunities-props__field-label {
    font-size: var(--font-size-xs);
    font-weight: 500;
    color: #888;
    margin-bottom: var(--spacing-2xs);
}

.opportunities-props__field-readonly {
    font-size: var(--font-size-md);
    color: #333;
    padding: var(--spacing-xsm) var(--spacing-sm);
    min-height: 34px;
    display: flex;
    align-items: center;
    cursor: default;
}

/* A read-only value that points somewhere -- an order number that opens the
   order it names. It has to read as a link, and the block above paints every
   read-only value flat grey with a default cursor, so this says otherwise. */
.opportunities-props__field-readonly--link {
    color: var(--color-action);
    cursor: pointer;
    text-decoration: underline;
    gap: var(--spacing-2xs);
}

.opportunities-props__field-readonly--link:hover {
    color: var(--color-action-hover);
}

.opportunities-props__field-readonly--link .bi {
    font-size: var(--font-size-xs);
    text-decoration: none;
}

/* Properties fixed by the member's access (opportunity form). A quiet
   read-only block: lock + heading, then label/value pairs. */
.opportunities-pinned {
    border: 1px solid var(--color-border-subtle);
    background: var(--color-surface-subtle);
    border-radius: var(--radius-md);
    padding: var(--spacing-smd) var(--spacing-smd);
}

.opportunities-pinned__head {
    display: flex;
    align-items: center;
    gap: var(--spacing-xsm);
    font-size: var(--font-size-xs);
    color: var(--color-text-secondary);
    margin-bottom: var(--spacing-sm);
}

.opportunities-pinned__rows {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-sm) var(--spacing-mdlg);
}

.opportunities-pinned__row {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
}

.opportunities-pinned__label {
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.opportunities-pinned__value {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
}

.opportunities-props__field-editable {
    font-size: var(--font-size-md);
    color: #333;
    padding: var(--spacing-2xs) var(--spacing-xs);
    border-radius: 6px;
    border: 1px solid transparent;
    cursor: text;
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
    min-height: 34px;
    display: flex;
    align-items: center;
}

.opportunities-props__field-editable:hover {
    background: #f0f4f8;
    border-color: #e0e4e8;
}

.opportunities-props__field-editable:focus-within {
    background: white;
    border-color: var(--color-action-hover);
    box-shadow: 0 0 0 2px var(--ink-alpha-15);
}

.opportunities-props__field-editable input,
.opportunities-props__field-editable select,
.opportunities-props__field-editable textarea {
    border: none;
    outline: none;
    background: transparent;
    width: 100%;
    font-size: var(--font-size-md);
    color: #333;
    padding: var(--spacing-xs) var(--spacing-xs);
    font-family: inherit;
}

.opportunities-props__field-editable textarea {
    resize: vertical;
    min-height: 60px;
    padding-top: var(--spacing-xsm);
}

/* Save indicator — copied exactly from public-form.css (lines 614-668) */
.opportunities-save-indicator {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xsm);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    transition: color 0.25s ease;
}

.opportunities-save-indicator::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-text-muted);
    flex-shrink: 0;
    transition: background 0.25s ease;
}

.opportunities-save-indicator--saving {
    color: var(--color-text-muted);
}

.opportunities-save-indicator--saving::before {
    background: var(--color-text-muted);
    animation: pulse-save 1.2s ease-in-out infinite;
}

.opportunities-save-indicator--saved {
    color: var(--color-success);
}

.opportunities-save-indicator--saved::before {
    background: var(--color-success);
}

.opportunities-save-indicator--error {
    color: var(--color-error);
}

.opportunities-save-indicator--error::before {
    background: var(--color-error);
}

@keyframes pulse-save {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%       { opacity: 0.4; transform: scale(0.8); }
}

/* ── Pipeline stage band (PR2 detail redesign) ─────────────────────────────
   Slim advance band at the top of the Pipeline card. Markup lives in
   app/opportunities/_pipeline_stage_band.html. */
.opportunities-pipe-band {
    display: flex;
    align-items: center;
    gap: var(--spacing-smd);
    flex-wrap: wrap;
    padding: var(--spacing-sm) var(--spacing-md);
    border-bottom: 1px solid var(--color-grey-100);
}
.opportunities-pipe-band__action {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}
/* Force Advance is demoted into an overflow menu — a quiet kebab, never a primary
   affordance. Resolving the blocker is the loud path (the blocked list below). */
.opportunities-pipe-band__overflow-toggle {
    padding: var(--spacing-xs) var(--spacing-xsm);
    line-height: 1;
}
/* Blocked callout takes the full band width (it wraps under the progress readout)
   and lists the exact steps left, each a link that jumps to it in the checklist. */
.opportunities-pipe-band__blocked {
    flex-basis: 100%;
    font-size: var(--font-size-xs);
    color: var(--color-warning);
    background: color-mix(in srgb, var(--color-warning) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-warning) 25%, transparent);
    border-radius: 6px;
    padding: var(--spacing-sm) var(--spacing-sm);
}
.opportunities-pipe-band__blocked-head {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
}
.opportunities-pipe-band__blocked-head strong {
    color: var(--color-warning);
}
.opportunities-pipe-band__blockers {
    list-style: none;
    margin: var(--spacing-xsm) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
}
.opportunities-pipe-band__blocker {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--spacing-xsm);
}
.opportunities-pipe-band__blocker-link {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    text-decoration: underline;
}
.opportunities-pipe-band__blocker-hint {
    color: var(--color-text-muted);
}
/* The :target highlight for a blocked step lives with the checklist's own
   rules in components/milestone-checklist.css. */

/* ── Gallery export strip ────────────────────────────────────────────────────
   One quiet line at the top of the Pipeline card: where the gallery export
   stands, and a button wearing the export automation's name when a person
   may ask again.
   Markup lives in app/opportunities/_gallery_export_strip.html. */
.opportunities-export-strip {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    flex-wrap: wrap;
    padding: var(--spacing-sm) var(--spacing-md);
    border-bottom: 1px solid var(--color-grey-100);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}
.opportunities-export-strip__text {
    margin: 0;
    flex: 1 1 12rem;
    min-width: 0;
}
.opportunities-export-strip__button {
    margin-left: auto;
}
.opportunities-export-strip__dot {
    flex: 0 0 auto;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: var(--radius-pill);
    background: var(--color-grey-300);
}
.opportunities-export-strip--queued .opportunities-export-strip__dot,
.opportunities-export-strip--exporting .opportunities-export-strip__dot {
    background: var(--color-warning);
}
.opportunities-export-strip--done .opportunities-export-strip__dot {
    background: var(--color-success);
}
.opportunities-export-strip--failed {
    color: var(--color-error);
}
.opportunities-export-strip--failed .opportunities-export-strip__dot {
    background: var(--color-error);
}
/* Every export so far, folded under the strip; rows are the shared
   global-run-history component. */
.opportunities-export-strip__history {
    padding: 0 var(--spacing-md) var(--spacing-sm);
    border-bottom: 1px solid var(--color-grey-100);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}
.opportunities-export-strip__history > summary {
    cursor: pointer;
    padding: var(--spacing-xs) 0;
}

/* ── Pipeline Milestones card — clickable stage band (PR4) ──────────────────
   Every stage is a real, keyboard-focusable button; clicking one shows that
   stage's milestone pane (Alpine x-show). Brand tokens only per ADR-124; no
   disabled state per ADR-043 — future stages stay clickable, just read-only. */
.opportunities-stageflow {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-xsm);
    padding: var(--spacing-sm) var(--spacing-smd);
    border-bottom: 1px solid var(--color-grey-100);
}
.opportunities-stageflow__stage {
    /* Reset the native button so it reads as a stage chip. */
    appearance: none;
    -webkit-appearance: none;
    border: 1px solid var(--color-border);
    background: var(--color-card-bg);
    border-radius: var(--radius-pill);
    /* 44px minimum tap target — accessible on touch. */
    min-height: 44px;
    padding: var(--spacing-xs) var(--spacing-smd);
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    cursor: pointer;
    color: var(--color-text);
    text-align: left;
    transition: background .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.opportunities-stageflow__stage:hover {
    border-color: var(--color-action);
}
.opportunities-stageflow__stage:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}
.opportunities-stageflow__stage--selected {
    background: var(--ms-active-soft);
    border-color: var(--ms-active);
    box-shadow: 0 0 0 3px var(--ms-active-ring);
}
.opportunities-stageflow__marker {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: var(--font-size-xs);
    font-weight: 700;
    border: 1.5px solid var(--ms-pending);
    background: var(--color-card-bg);
    color: var(--color-grey-600);
}
.opportunities-stageflow__stage--done .opportunities-stageflow__marker {
    background: var(--ms-done);
    border-color: var(--ms-done);
    color: var(--color-card-bg);
}
.opportunities-stageflow__stage--current .opportunities-stageflow__marker {
    border-color: var(--ms-active);
    background: var(--ms-active-soft);
}
.opportunities-stageflow__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ms-active);
    box-shadow: 0 0 0 3px var(--ms-active-ring);
}
.opportunities-stageflow__body {
    display: flex;
    flex-direction: column;
    line-height: 1.15;
    min-width: 0;
}
.opportunities-stageflow__name {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    letter-spacing: .01em;
}
.opportunities-stageflow__stage--future .opportunities-stageflow__name {
    color: var(--color-grey-500);
}
/* The current stage's pane holds full-bleed components (the advance band and
   the milestone list, which self-pad), so the pane itself stays flush. */
.opportunities-stage-pane {
    padding: 0;
}
.opportunities-stage-milestone-list {
    list-style: none;
    padding: var(--spacing-sm) var(--spacing-md);
    margin: 0;
}
.opportunities-stage-milestone-row {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-sm);
    padding: var(--spacing-xsm) 0;
}
.opportunities-stage-milestone-row--done {
    opacity: .6;
}
/* Read-only status circle — the canonical .global-status-dot atom (--sm);
   only its optical alignment with the first line of the name is local. */
.opportunities-stage-milestone-row__dot {
    margin-top: var(--spacing-2xs);
}
.opportunities-stage-milestone-body {
    flex: 1;
    min-width: 0;
}
.opportunities-stage-milestone-name {
    font-size: var(--font-size-sm);
    font-weight: 500;
    color: var(--color-text);
    display: flex;
    align-items: center;
    gap: var(--spacing-xsm);
    flex-wrap: wrap;
}
.opportunities-stage-milestone-row--done .opportunities-stage-milestone-name {
    color: var(--color-grey-600);
}
.opportunities-stage-milestone-row--skipped .opportunities-stage-milestone-name {
    text-decoration: line-through;
    text-decoration-color: var(--color-grey-500);
}
.opportunities-stage-milestone-meta {
    font-size: var(--font-size-2xs);
    margin-top: .0625rem;
}

.opportunities-stage-milestone-optional {
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--color-grey-500);
    background: var(--color-grey-100);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    padding: .0625rem var(--spacing-xsm);
    line-height: 1.4;
}

/* ── Event reschedule history timeline (Events card) ────────────────────────
   Moved out of the detail.html inline <style> block; hardcoded hex converted to
   brand tokens per ADR-124. A dotted vertical track of past/current bookings. */
.global-hist {
    margin-left: var(--spacing-2xs);
}
.global-hist__row {
    display: flex;
    gap: var(--spacing-sm);
    min-height: 1.375rem;
}
.global-hist__track {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 0.625rem;
    flex-shrink: 0;
}
.global-hist__dot {
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: var(--spacing-xs);
}
.global-hist__line {
    width: 1px;
    flex: 1;
    background: var(--color-grey-200);
    margin: 0.0625rem 0;
}
.global-hist__dot--ok {
    background: var(--color-success);
}
.global-hist__dot--moved {
    background: var(--color-warning);
}
.global-hist__dot--cancel {
    background: var(--color-error);
}
.global-hist__dot--pending {
    background: var(--event-color);
}
.global-hist__body {
    font-size: var(--font-size-xs);
    color: var(--color-text);
    padding-bottom: var(--spacing-2xs);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacing-xs);
}
.global-hist__date {
    color: var(--color-text);
    font-weight: 500;
}
.global-hist__time {
    color: var(--color-grey-600);
}
.global-hist__actor {
    font-size: var(--font-size-2xs);
    color: var(--color-text-muted);
}

/* ==========================================================================
   Sidebar cards — Contact · Events · Forms · Attachments (PR4 detail redesign)
   Restyled onto the roster's card + type-scale language: brand tokens only
   (ADR-124), the --font-size-* scale (no ad-hoc rem), and ≥44px tap targets
   for anything a finger touches.
   ========================================================================== */

/* Email + phone are tappable rows — 44px tall so a finger lands them cleanly. */
.opportunities-detail__contact-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: var(--font-size-lg);
    text-decoration: none;
}

.opportunities-detail__events {
    overflow: hidden;
}

/* Every finger control in the events list clears 44px; icon-only ones stay square. */
.opportunities-detail__events .btn {
    min-height: 44px;
}

.opportunities-detail__events .global-header-btn-icon {
    min-width: 44px;
    min-height: 44px;
}

/* ── Meeting card (round 7) ─────────────────────────────────────────────────
   A meeting row is now a proper card: a stacked month/day date block on the
   left, title · time · location in the middle, and tidy icon actions on the
   right. Replaces the old dense row (2xs mini-buttons + address-as-a-button). */
.opportunities-meeting {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-smd);
}

/* Left date block — brand-tinted, month abbreviation over the day number. */
.opportunities-meeting__date {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 3rem;
    padding: var(--spacing-xsm) 0;
    border-radius: var(--radius-md);
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
    line-height: 1.05;
}

.opportunities-meeting__date-mon {
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.opportunities-meeting__date-day {
    font-size: var(--font-size-lg);
    font-weight: 700;
}

.opportunities-meeting__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
}

.opportunities-meeting__title {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.opportunities-meeting__time {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xsm);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.opportunities-meeting__time i {
    font-size: 0.85em;
    opacity: 0.75;
}

/* Location — muted text a coordinator taps to edit (opens the address modal).
   Reads as a plain line, not a button; underlines on hover to signal it's editable.
   A reader who cannot edit it — a planner here on agenda:write — gets the same
   line as a <span> carrying --static, which takes the affordances back off. */
.opportunities-meeting__loc {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xsm);
    align-self: flex-start;
    min-height: 32px;
    max-width: 100%;
    padding: 0;
    border: 0;
    background: none;
    text-align: left;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    cursor: pointer;
}

.opportunities-meeting__loc i {
    font-size: 0.85em;
    opacity: 0.75;
    flex-shrink: 0;
}

.opportunities-meeting__loc span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Hover says "this is editable", so it is written to exclude the static line
   rather than to be undone below it — an override would be one rule move away
   from silently offering an edit that is not on the page. */
.opportunities-meeting__loc:not(.opportunities-meeting__loc--static):hover {
    color: var(--color-text);
}

.opportunities-meeting__loc:not(.opportunities-meeting__loc--static):hover span {
    text-decoration: underline;
}

.opportunities-meeting__loc--static {
    cursor: default;
}

/* Right-side actions — status pills plus square 44px icon buttons for Meet /
   Reschedule. Icon-only (no more 2xs text mini-buttons). */
.opportunities-meeting__actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-xsm);
    flex-shrink: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.opportunities-meeting__action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-grey-200);
    background: var(--color-card-bg);
    color: var(--color-text-muted);
    font-size: var(--font-size-md);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.opportunities-meeting__action:hover,
.opportunities-meeting__action:focus-visible {
    background: var(--color-action-tint-pressed);
    border-color: var(--color-action);
    color: var(--color-action);
}

/* Event + history pills reuse the shared .global-badge family, sized down and
   left in normal case to fit these dense rows. */
.opportunities-meeting__actions .global-badge,
.global-hist__body .global-badge {
    font-size: var(--font-size-2xs);
    padding: 0.0625rem var(--spacing-xsm);
    text-transform: none;
}

/* One card per assigned form, stacked full-width so each reads as its own
   surface — matches the role roster's card language. */
.opportunities-detail__form-cards {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--spacing-smd);
    padding: var(--spacing-md);
}

.opportunities-detail__form-card {
    padding: var(--spacing-smd);
    border: 1px solid var(--color-grey-200);
    border-radius: var(--radius-md);
    background: var(--color-card-bg);
}

.opportunities-detail__form-card-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--spacing-sm);
}

.opportunities-detail__form-name {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-md);
    color: var(--color-text);
}

/* The recipient line is a global-person-chip --micro (components/person-chip.css).
   Only the type scale is local: this card reads its recipient one step larger. */
.opportunities-detail__form-recipient {
    font-size: var(--font-size-sm);
}

/* The boxed "Goes to" recipient email on the assign form. This rule was
   co-deleted when the forms card absorbed the chip, but the assign-form markup
   still renders `.opportunities-detail__form-email` (its stray chip edit was reverted as
   out-of-scope), so the rule is restored to keep the email small + grey. */
.opportunities-detail__form-email {
    display: block;
    font-size: var(--font-size-xs);
    color: var(--color-grey-500);
}

.opportunities-detail__form-tag {
    display: inline-flex;
    align-items: center;
    padding: 0.0625rem var(--spacing-xsm);
    border-radius: var(--radius-pill);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
}

.opportunities-detail__form-tag--contact {
    background: color-mix(in srgb, var(--color-info) 12%, transparent);
    color: var(--color-info);
}

.opportunities-detail__form-tag--role {
    background: color-mix(in srgb, var(--color-success) 12%, transparent);
    color: var(--color-success);
}

.opportunities-detail__form-state {
    margin-top: var(--spacing-sm);
    font-size: var(--font-size-xs);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
}

.opportunities-detail__form-state-label {
    font-weight: 500;
}

.opportunities-detail__form-state-label--done {
    color: var(--color-success);
}

.opportunities-detail__form-state-label--progress {
    color: var(--color-action);
}

.opportunities-detail__form-state-label--waiting {
    color: var(--color-warning);
}

/* Progress track for the in-progress "N of M answered" bar. */
.opportunities-detail__form-progress {
    margin-top: var(--spacing-sm);
    height: 6px;
    border-radius: var(--radius-pill);
    background: var(--color-grey-200);
    overflow: hidden;
}

.opportunities-detail__form-progress-fill {
    display: block;
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--color-action);
}

.opportunities-detail__form-sub {
    margin-top: var(--spacing-xsm);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

.opportunities-detail__form-action {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    margin-top: var(--spacing-sm);
    font-size: var(--font-size-xs);
    font-weight: 500;
    color: var(--color-action);
    text-decoration: none;
}

.opportunities-detail__form-action:hover {
    text-decoration: underline;
}

/* Read-only "Goes to: client" row in the assign drawer. */
.opportunities-assign__goesto {
    display: flex;
    align-items: center;
    padding: var(--spacing-sm) var(--spacing-smd);
    border: 1px solid var(--color-grey-200);
    border-radius: var(--radius-md);
    background: var(--color-surface-subtle);
    font-size: var(--font-size-sm);
    color: var(--color-text);
}

.opportunities-detail__form-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-right: var(--spacing-xsm);
}

.opportunities-detail__form-dot--submitted {
    background: var(--color-success);
}

.opportunities-detail__form-dot--pending {
    background: var(--color-warning);
}

/* ── Files card (round 7) ───────────────────────────────────────────────────
   Nick: the files area is centered, with a generous drag-and-drop upload band at
   the top and the file cards centered below. The whole area is capped and centered
   inside the card. */
.opportunities-detail__files {
    max-width: 720px;
    margin: 0 auto;
    padding: var(--spacing-md);
}

/* Upload band — a big dashed dropzone that opens the existing add-files modal
   (click/drop both route to the same flow; no new upload backend). Generous height
   so it reads as a real drop target, not the old cramped add-card. */
.opportunities-detail__file-dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-xsm);
    width: 100%;
    min-height: 168px;
    padding: var(--spacing-lg);
    border: 2px dashed var(--color-grey-300);
    border-radius: var(--radius-card);
    background: var(--color-surface-subtle);
    color: var(--color-text-muted);
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.opportunities-detail__file-dropzone:hover,
.opportunities-detail__file-dropzone:focus-visible {
    border-color: var(--color-action);
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
}

.opportunities-detail__file-dropzone-icon {
    font-size: 2rem;
    line-height: 1;
}

.opportunities-detail__file-dropzone-label {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.opportunities-detail__file-dropzone:hover .opportunities-detail__file-dropzone-label,
.opportunities-detail__file-dropzone:focus-visible .opportunities-detail__file-dropzone-label {
    color: var(--color-action);
}

.opportunities-detail__file-dropzone-hint {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

/* Matches the Forms-tab card language (grid of bordered cards). Each file card
   leads with a type-tinted preview band whose glyph carries the meaning; the
   tint just groups by kind, mapped onto existing semantic tokens (no new hex
   per ADR-124). Base rule is shared with the workers files card, so it is left
   unchanged; the opportunity Files tab tweaks layout via the scoped rule below. */
.opportunities-detail__file-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: var(--spacing-smd);
    padding: var(--spacing-md);
}

/* On the opportunity Files tab the cards sit inside the centered .opportunities-detail__files
   wrapper (which supplies the padding), below the upload band. */
.opportunities-detail__files .opportunities-detail__file-cards {
    padding: 0;
    margin-top: var(--spacing-md);
}

.opportunities-detail__file-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--color-grey-200);
    border-radius: var(--radius-md);
    background: var(--color-card-bg);
    overflow: hidden;
}

.opportunities-detail__file-preview {
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-size-xl);
    background: var(--color-grey-100);
    color: var(--color-grey-600);
}

.opportunities-detail__file-preview--pdf {
    background: color-mix(in srgb, var(--color-error) 10%, transparent);
    color: var(--color-error);
}

.opportunities-detail__file-preview--image,
.opportunities-detail__file-preview--sheet {
    background: color-mix(in srgb, var(--color-success) 10%, transparent);
    color: var(--color-success);
}

.opportunities-detail__file-preview--doc {
    background: color-mix(in srgb, var(--color-info) 10%, transparent);
    color: var(--color-info);
}

.opportunities-detail__file-preview--link {
    background: color-mix(in srgb, var(--color-action) 10%, transparent);
    color: var(--color-action);
}

.opportunities-detail__file-preview--other {
    background: var(--color-grey-100);
    color: var(--color-grey-600);
}

.opportunities-detail__file-body {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
    padding: var(--spacing-smd);
}

.opportunities-detail__file-name {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

/* The uploader line is a global-person-chip --micro (components/person-chip.css);
   the atom carries the whole layout, so nothing is left to declare here. */

.opportunities-detail__file-sub {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

.opportunities-detail__file-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-xsm);
}

.opportunities-detail__file-action {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    font-size: var(--font-size-xs);
    font-weight: 500;
    color: var(--color-action);
    text-decoration: none;
}

.opportunities-detail__file-action:hover {
    text-decoration: underline;
}

/* Remove stays a full-color action here (unlike the old hover-reveal row) —
   the card already scopes it, and it never disables per ADR-043. */
.opportunities-detail__file-remove {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    border: none;
    background: none;
    padding: 0;
    font-size: var(--font-size-xs);
    font-weight: 500;
    color: var(--color-error);
    cursor: pointer;
}

.opportunities-detail__file-remove:hover {
    text-decoration: underline;
}

/* ── Event address modal — mobile-safe widths + tap targets ────────────────── */
.opportunities-detail__modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: var(--z-popover);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* No fixed px width — fill the viewport up to a cap, with a margin so the card
   never touches the screen edge on a phone. */
.opportunities-detail__modal-card {
    width: 100%;
    max-width: 460px;
    margin: var(--spacing-md);
    border-radius: var(--radius-lg);
}

/* ==========================================================================
   Tab shell (opportunity detail redesign)
   The old two-column card stack now sits behind six tabs in one column. The tab
   strip is the platform-wide `.global-tabs` component (components/page-tabs.css);
   the record header, the strip, and the active pane read as ONE bordered card.
   ========================================================================== */

/* The active pane is the content section of the one card — no frame of its own;
   the shell supplies the border and rounded bottom. Hidden panes are display:none,
   so only the open one draws. */
.opportunities-tab-pane {
    display: flex;
    flex-direction: column;
}

/* The first-level card inside a pane loses its own chrome so it sits flush on the
   shell surface — no box-in-a-box, no stray inner border. It keeps its background
   and its card-body padding, so content still breathes. Nested sub-cards (role
   tiles, etc.) keep theirs. */
.opportunities-tab-pane > .card {
    border: none;
    box-shadow: none;
    border-radius: 0;
    margin-bottom: 0;
}

/* Slim per-pane toolbar — the tab already names the pane, so this row drops the
   old card-header title and carries only the pane's actions, right-aligned. */
.opportunities-pane-toolbar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--spacing-xsm);
    padding: var(--spacing-sm) var(--spacing-smd);
}

/* Schedule toolbar runs switch / summary / actions. The summary takes the slack
   so the bar has no gutter to leave empty: `space-between` across two items on a
   wide pane pushed them to opposite walls with ~1000px of nothing between. */
.opportunities-pane-toolbar--split {
    justify-content: flex-start;
}

/* The line that fills that space. It yields to both neighbours — they are fixed
   controls and it is prose, so it is the one that should ellipsize on a narrow
   pane rather than wrapping the buttons onto a second row. */
.opportunities-pane-toolbar__summary {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    padding: 0 var(--spacing-sm);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.opportunities-pane-toolbar__actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-xsm);
    flex-shrink: 0;
}

/* Schedule view switch — a segmented control that flips the pane between
   Calendar (the default) and List. Built as a track that holds more than one
   pill; when the timeslot plugin is off the calendar payload is absent and List
   is the lone segment. */
.opportunities-segview {
    display: inline-flex;
    flex-shrink: 0;
    gap: var(--spacing-2xs);
    padding: var(--spacing-2xs);
    /* A track has to sit visibly below the thumb that rides in it. Against the
       card this was one step off white, so the raised segment had nothing to be
       raised out of and the control read as two plain words. */
    background: var(--color-grey-100);
    border-radius: var(--radius-md);
}

/* Full text colour, not muted: these are the two places you can go, and a
   greyed-out word reads as a door that is shut. Only the thumb says which one
   you are on. */
.opportunities-segview__btn {
    padding: var(--spacing-xs) var(--spacing-smd);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-family: var(--font-family);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-button);
    cursor: pointer;
}

.opportunities-segview__btn:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
    border-radius: 4px;
}

/* The thumb: lifted off the track by shadow alone. A border would draw a second
   edge inside the track's own and the control would read as a box in a box. */
.opportunities-segview__btn--active {
    background: var(--color-card-bg);
    color: var(--color-text);
    box-shadow: var(--shadow-sm);
}

/* Booking links: one list under the sessions, client links then team links.
   Each row is the link's name and a muted line, with Copy and remove on the
   right. The address is one muted line that cuts off rather than wraps. */
.opportunities-detail__links {
    padding: var(--spacing-smd);
}

.opportunities-detail__links-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.opportunities-detail__link {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) 0;
}

.opportunities-detail__link + .opportunities-detail__link {
    border-top: 1px solid var(--color-border-hairline);
}

.opportunities-detail__link-text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-width: 0;
}

.opportunities-detail__link-name {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.opportunities-detail__link-meta,
.opportunities-detail__link-url {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.opportunities-detail__link-url {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.opportunities-detail__link-actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
    flex-shrink: 0;
}

/* The Add menu opens under its button, lined up with the pane's right edge. */
.opportunities-schedule-add {
    flex-shrink: 0;
}

/* Details pane: fields on the left, the people and money on the right. */
.opportunities-detail__details {
    align-items: start;
}

/* A section head already pads itself, so the save status inside it does not. */
.global-section__head > .opportunities-pane-toolbar {
    padding: 0;
}

/* Field group title in sentence case: a quiet label over each group. */
.opportunities-detail__field-group + .opportunities-detail__field-group {
    margin-top: var(--spacing-md);
}

.opportunities-detail__field-group-title {
    margin: 0 0 var(--spacing-xs);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text-muted);
}

/* Schedule → Calendar view. The shared month grid (calendar.js) is loaded above
   this compact nav row; the page-wide toolbar and date-nav partials teleport to
   header slots this page doesn't have, so month nav is a small local ‹ Today ›
   group that reloads with cal_year/cal_month + the #schedule hash. */
.opportunities-schedule-calendar__nav {
    display: flex;
    align-items: center;
    gap: var(--spacing-smd);
    padding: var(--spacing-xs) var(--spacing-smd) var(--spacing-smd);
}

/* One merged segmented control: ‹ Today › (round 7 — was three loose buttons). */
.opportunities-calnav {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--color-grey-200);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--color-card-bg);
}

.opportunities-calnav__btn,
.opportunities-calnav__today {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 40px;
    padding: 0 var(--spacing-smd);
    background: transparent;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.opportunities-calnav__btn {
    padding: 0 var(--spacing-sm);
    font-size: var(--font-size-md);
}

.opportunities-calnav__today {
    border-left: 1px solid var(--color-grey-200);
    border-right: 1px solid var(--color-grey-200);
}

.opportunities-calnav__btn:hover,
.opportunities-calnav__today:hover,
.opportunities-calnav__btn:focus-visible,
.opportunities-calnav__today:focus-visible {
    background: var(--color-action-tint-pressed);
    color: var(--color-action);
}

/* Already on the current month. Marked as where you are, not switched off: this
   was grey-on-grey between two chevrons, and it took the chevrons down with it —
   readers stopped trying to move months at all. Full text colour, a tinted seat
   under the word, and hover still answers. */
.opportunities-calnav__today--current {
    background: var(--color-action-tint-pressed);
    color: var(--color-text);
    font-weight: var(--font-weight-medium);
}

.opportunities-schedule-calendar__label {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

/* The grid ships its own padding via the shared calendar CSS; drop the card's
   default gutter so the month fills the pane width (capped at 1000px upstream). */
.opportunities-schedule-calendar__body {
    padding: 0 var(--spacing-smd) var(--spacing-smd);
}

/* ==========================================================================
   Sessions view — the rail
   The job's sessions down the left, the selected one's card and its agenda on
   the right. Ported from the approved mockup (Nick 2026-08-03, picked over the
   horizontal strip: that strip is reserved for choosing workers inside an agenda).
   ========================================================================== */
.opportunities-schedule-split {
    display: grid;
    grid-template-columns: 13.5rem 1fr;
}

/* A tint and a hairline are the whole separation — the rail is navigation, and
   a second card frame inside the pane's card would read as a nested box. */
.opportunities-schedule-rail {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
    padding: var(--spacing-sm);
    border-right: 1px solid var(--color-border-hairline);
    background: var(--color-grey-50);
}

/* Under the breakpoint the two columns stack, so the divider that was on the
   rail's right edge has to move to its bottom — same hairline, the side that
   now faces the panel. */
/* The tabs stay a group of their own inside the column even though they are now
   the only thing in it: the wrapper is what bounds the tablist, and a tablist
   that swallows the column would read anything added beside it out as a tab.
   Same axis and gap as the column, so the split is only there for assistive
   tech. */
.opportunities-schedule-rail__tabs {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
}

.opportunities-schedule-rail__item {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
    padding: var(--spacing-sm) var(--spacing-sm);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: none;
    text-align: left;
    cursor: pointer;
}

.opportunities-schedule-rail__item b {
    font-size: 0.8125rem;
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.opportunities-schedule-rail__item span {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.6875rem;
    color: var(--color-grey-600);
}

.opportunities-schedule-rail__item:hover {
    background: var(--color-grey-100);
}

/* The selected session is lifted onto the panel's own surface, so the rail
   reads as one continuous shape with what it opened. */
.opportunities-schedule-rail__item--on {
    border-color: var(--color-border);
    background: var(--color-card-bg);
}

.opportunities-schedule-rail__foot {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
}

/* Grey until the plan is shared with the workers, green once it is — the same
   judgement the agenda's own header makes, small enough to read at a glance
   down the column. */
.opportunities-schedule-rail__dot {
    flex: 0 0 auto;
    width: 0.375rem;
    height: 0.375rem;
    border-radius: var(--radius-pill);
    background: var(--color-grey-400);
}

.opportunities-schedule-rail__dot--shared {
    background: var(--color-success);
}

.opportunities-schedule-panel {
    min-width: 0;
}

/* One session's card. It used to be a Bootstrap list item in a stack of them;
   the rail took the stack away, so the row keeps its gutter and loses the
   dividing line it no longer sits above. */
.opportunities-detail__session {
    position: relative;
    padding: var(--spacing-smd);
}

/* Planning Center card. One button does the send and, after it, the re-check,
   so there is one spinner rule to write. Hidden until HTMX marks the button
   in-flight: the send runs inside the request and takes a few seconds against
   Planning Center's rate limit, so something has to be moving. */
.opportunities-pco__spin {
    display: none;
    margin-left: var(--spacing-xsm);
}

.opportunities-pco__btn.htmx-request .opportunities-pco__spin {
    display: inline-block;
}

.opportunities-pco__btn.htmx-request {
    opacity: 0.7;
    pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Phone-first header and roster — the BASE (wide) half. The header's actions
   slot renders two branches at every width: --wide is the desktop cluster,
   --narrow is one primary and a "more" menu. This half shows the cluster and
   hides everything phone-only; the @media block at the end of the file flips
   them. Every phone-only class still gets a base rule here so a reader (and
   the template-class gate) finds its home without opening the media block.
   ═══════════════════════════════════════════════════════════════════════════ */
.opportunities-record-header__actions--wide {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--spacing-xsm);
}

.opportunities-record-header__actions--narrow {
    display: none;
    align-items: center;
    gap: var(--spacing-xs);
}

.opportunities-record-header__primary {
    min-height: var(--control-h-touch);
    white-space: nowrap;
}

.opportunities-record-header__menu {
    flex-shrink: 0;
}

.opportunities-record-header__menu-toggle {
    width: var(--control-h-touch);
    height: var(--control-h-touch);
}

/* Description: the text sits in its own box so a phone can clamp it; the
   More / Less toggle never shows on a wide screen. */
.opportunities-record-header__desc-text {
    min-width: 0;
}

.opportunities-record-header__desc-toggle {
    display: none;
}

/* Pay shows cents on a wide screen and whole dollars on a phone: two spans,
   one visible. */
.opportunities-detail__role-pay-exact {
    display: inline;
}

.opportunities-detail__role-pay-whole {
    display: none;
}

/* ── Details: contacts card — hooks the phone block sizes ─────────────────── */
.opportunities-contact-row__note {
    font-size: var(--font-size-2xs);
}

/* The row's layout lives here, not in utility classes on the markup: a
   utility is !important, so the phone block could only answer with !important. */
.opportunities-contact-row__actions {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    gap: var(--spacing-xs);
}

.opportunities-contact-row__action {
    padding: 0;
    text-align: right;
}

.opportunities-contact-row__actions,
.opportunities-contact-form__actions,
.opportunities-contact-form__input,
.opportunities-contact-add {
    min-width: 0;
}

/* ── Schedule: meeting card hooks ───────────────────────────────────────────
   The time is one span so a phone can wrap it as one line of text; the action
   chips carry a word that only the phone shows (an icon alone is a guess). */
.opportunities-meeting__time-text {
    min-width: 0;
}

.opportunities-meeting__action-label {
    display: none;
}

.opportunities-meeting__hist {
    margin-top: var(--spacing-xsm);
}

.opportunities-meeting__hist-toggle {
    font-size: var(--font-size-xs);
}

.opportunities-meeting__hist-chevron {
    font-size: var(--font-size-2xs);
}

/* ── Pipeline: phone-only progress line and the current stage's hook ──────── */
.opportunities-pipe-band__left {
    display: none;
    margin: 0;
    flex-basis: 100%;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.opportunities-stageflow__stage.is-current {
    scroll-snap-align: center;
}

/* ── Forms: hooks the phone block sizes ─────────────────────────────────────── */
.opportunities-detail__form-assign,
.opportunities-detail__form-button {
    white-space: nowrap;
}

.opportunities-detail__form-button--remove {
    padding: 0;
    text-decoration: none;
}

/* ── Files: dropzone label per width, actor picker hooks ─────────────────────── */
.opportunities-detail__file-dropzone-label--wide {
    display: inline;
}

.opportunities-detail__file-dropzone-label--narrow {
    display: none;
}

.opportunities-detail__file-actor-form {
    align-items: center;
    min-width: 0;
}

.opportunities-detail__file-actor {
    width: auto;
    min-width: 0;
}

/* ── The Money card (admin only) ────────────────────────────────────────────
   Revenue over cost over the bottom line, read top to bottom. Labels sit
   left, figures right and tabular, so a column of dollars lines up on the
   decimal point instead of wandering with the digit widths. */
.opportunities-finance__note {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    margin-bottom: var(--spacing-md);
}

.opportunities-finance__section {
    margin-bottom: var(--spacing-md);
}

/* The last section already has the bottom line's own rule under it. */
.opportunities-finance__section:last-of-type {
    margin-bottom: 0;
}

.opportunities-finance__heading {
    color: var(--color-text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    margin-bottom: var(--spacing-xs);
}

.opportunities-finance__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--spacing-md);
    padding: var(--spacing-2xs) 0;
    font-size: var(--font-size-sm);
}

/* The subtotal closes its section, so it carries the rule above it rather
   than a heavier weight — one line does the job of a border and a bold. */
.opportunities-finance__row--total {
    border-top: 1px solid var(--color-border-hairline);
    margin-top: var(--spacing-2xs);
    padding-top: var(--spacing-xs);
    font-weight: var(--font-weight-medium);
}

.opportunities-finance__label {
    color: var(--color-text-secondary);
}

.opportunities-finance__amount {
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The Cost total, when it opens the bills dashboard for this booking. Reads
   as the same figure as every other total — the underline only shows up on
   hover, which is what marks it as a link instead of a plain number. */
a.opportunities-finance__amount--link {
    color: inherit;
    font-weight: inherit;
    text-decoration: none;
}

a.opportunities-finance__amount--link:hover {
    text-decoration: underline;
}

.opportunities-finance__bottom {
    display: flex;
    gap: var(--spacing-lg);
    border-top: 1px solid var(--color-border);
    padding-top: var(--spacing-md);
}

.opportunities-finance__figure {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
}

.opportunities-finance__figure-label {
    color: var(--color-text-muted);
    font-size: var(--font-size-2xs);
    text-transform: uppercase;
}

.opportunities-finance__figure-value {
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-medium);
    font-variant-numeric: tabular-nums;
}

/* Money made, money lost, and neither. A booking that came out even, or one
   nobody priced, gets the plain ink colour — colour here means a direction,
   so it must not appear when there is no direction to report. */
.opportunities-finance__figure--good .opportunities-finance__figure-value {
    color: var(--color-success);
}

.opportunities-finance__figure--bad .opportunities-finance__figure-value {
    color: var(--color-error);
}

.opportunities-finance__figure--flat .opportunities-finance__figure-value {
    color: var(--color-text);
}

/* ═══════════════════════════════════════════════════════════════════════════
   PHONE (under 768px). Source order is the override: this block stays LAST
   in the file (tests/interface/test_phone_block_last.py), and every phone
   rule for this page — header, description, roster, schedule — lives inside
   it. Append here; never add a base rule below it.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {
    /* Header: one primary and a menu instead of the five-button cluster. */
    .opportunities-record-header__actions--wide {
        display: none;
    }

    /* The narrow branch is the only child the shared actions wrapper renders
       here, so it spans the wrapper's two columns and lays the row out itself:
       the primary takes the width, the ⋯ keeps its square on the right. */
    .opportunities-record-header__actions--narrow {
        display: grid;
        grid-template-columns: minmax(0, 1fr) var(--control-h-touch);
        grid-column: 1 / -1;
        gap: var(--spacing-sm);
    }

    .opportunities-record-header__primary {
        width: 100%;
    }

    .opportunities-record-header__menu {
        grid-column: 2;
    }

    /* The identity reads as running text on a phone so the title's two-line
       clamp can break between its parts instead of clipping the recipient. */
    .opportunities-detail__identity--inpage,
    .opportunities-detail__identity--inpage > span {
        display: inline;
        white-space: normal;
        overflow: visible;
    }

    .opportunities-detail__identity--inpage > span:not(:first-child)::before {
        margin-left: var(--spacing-xs);
    }

    /* Description: three lines, then More / Less. */
    .opportunities-record-header__desc-text {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        overflow: hidden;
    }

    .opportunities-record-header__desc.is-open .opportunities-record-header__desc-text {
        display: block;
        -webkit-line-clamp: none;
        overflow: visible;
    }

    .opportunities-record-header__desc-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: flex-start;
        min-width: var(--control-h-touch);
        min-height: var(--control-h-touch);
        padding: 0;
        border: 0;
        background: none;
        color: var(--color-action);
        font: inherit;
        font-weight: var(--font-weight-medium);
        cursor: pointer;
    }

    /* Roster: the "Manage roster" link is a thumb target on a phone. */
    .opportunities-detail__roster-manage {
        min-height: var(--control-h-touch);
    }

    /* Roster tile: identity and kebab on the first line, status on the second,
       pay on the third — nothing squeezed beside the name. */
    .opportunities-detail__role-tile {
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas:
            "identity menu"
            "meta meta"
            "actions actions";
        row-gap: var(--spacing-sm);
        padding: var(--spacing-smd) var(--page-gutter);
    }

    .opportunities-detail__role-identity {
        grid-area: identity;
    }

    .opportunities-detail__role-menu {
        grid-area: menu;
    }

    .opportunities-detail__role-due,
    .opportunities-detail__role-waiting,
    .opportunities-detail__role-status {
        grid-area: meta;
        justify-self: start;
    }

    .opportunities-detail__role-actions {
        grid-area: actions;
        justify-content: flex-start;
    }

    .opportunities-detail__role-warning {
        grid-column: 1 / -1;
    }

    /* Names read at body size and may wrap; the name link is a full tap target. */
    .opportunities-detail__role-worker-name {
        font-size: var(--font-size-base);
        white-space: normal;
    }

    .opportunities-detail__role-namelink {
        min-height: var(--control-h-touch);
        justify-content: center;
    }

    .opportunities-detail__role-menu-toggle {
        min-width: var(--control-h-touch);
        min-height: var(--control-h-touch);
    }

    /* Whole dollars only. */
    .opportunities-detail__role-pay-exact {
        display: none;
    }

    .opportunities-detail__role-pay-whole {
        display: inline;
    }

    /* Schedule tab: the rail stacks above the panel. */
    .opportunities-schedule-split {
        grid-template-columns: 1fr;
    }

    .opportunities-schedule-rail {
        border-right: none;
        border-bottom: 1px solid var(--color-border-hairline);
    }

    /* ── Details: contacts — thumb-height row actions, 16px inputs. ─────────── */
    .opportunities-contact-row {
        flex-wrap: wrap;
    }

    .opportunities-contact-row__actions {
        flex-direction: row;
        flex-wrap: wrap;
        width: 100%;
        gap: var(--spacing-sm);
        margin-top: var(--spacing-xs);
    }

    .opportunities-contact-row__action {
        min-height: var(--control-h-touch);
        display: inline-flex;
        align-items: center;
        padding: 0 var(--spacing-sm);
        font-size: var(--font-size-md);
        text-align: left;
    }

    .opportunities-contact-row .form-check-input {
        width: var(--spacing-2xl);
        height: var(--spacing-lg);
        margin-top: 0;
    }

    .opportunities-contact-row .form-check {
        display: flex;
        align-items: center;
        gap: var(--spacing-sm);
        min-height: var(--control-h-touch);
    }

    .opportunities-contact-add {
        min-height: var(--control-h-touch);
    }

    .opportunities-contact-form__input {
        min-height: var(--control-h-touch);
        font-size: var(--font-size-base);
    }

    .opportunities-contact-form__actions .btn {
        flex: 1 1 0;
        min-height: var(--control-h-touch);
    }

    /* ── Schedule: toolbar stacks, the view switch spans the width. ─────────── */
    .opportunities-pane-toolbar {
        flex-direction: column;
        align-items: stretch;
        gap: var(--spacing-sm);
        padding: var(--spacing-sm) var(--page-gutter);
    }

    .opportunities-pane-toolbar__summary {
        white-space: normal;
        padding: 0;
    }

    .opportunities-pane-toolbar__actions {
        flex-wrap: wrap;
    }

    .opportunities-pane-toolbar__actions .btn,
    .opportunities-detail__form-assign {
        flex: 1 1 auto;
        min-height: var(--control-h-touch);
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .opportunities-segview {
        display: flex;
        width: 100%;
    }

    .opportunities-segview__btn {
        flex: 1 1 0;
        min-height: var(--control-h-touch);
    }

    /* ── Schedule: the rail is one horizontal row of chips with a right fade. ── */
    .opportunities-schedule-rail {
        position: relative;
        padding: var(--spacing-sm) 0 var(--spacing-sm) var(--page-gutter);
    }

    .opportunities-schedule-rail::after {
        content: "";
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        width: var(--spacing-xl);
        pointer-events: none;
        background: linear-gradient(to right, transparent, var(--color-grey-50));
    }

    .opportunities-schedule-rail__tabs {
        flex-direction: row;
        gap: var(--spacing-sm);
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-right: var(--spacing-xl);
    }

    .opportunities-schedule-rail__tabs::-webkit-scrollbar {
        display: none;
    }

    .opportunities-schedule-rail__item {
        flex: 0 0 auto;
        scroll-snap-align: start;
        min-height: var(--control-h-touch);
        justify-content: center;
        border-color: var(--color-border-subtle);
        background: var(--color-card-bg);
    }

    .opportunities-schedule-rail__item b,
    .opportunities-schedule-rail__item span {
        font-size: var(--font-size-sm);
    }

    /* ── Schedule: meeting card — text wraps, chips are labelled and 44px. ──── */
    .opportunities-meeting {
        flex-wrap: wrap;
    }

    .opportunities-meeting__time {
        align-items: flex-start;
    }

    .opportunities-meeting__time-text {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .opportunities-meeting__loc {
        min-height: var(--control-h-touch);
        align-items: flex-start;
        padding-top: var(--spacing-xs);
    }

    .opportunities-meeting__loc span {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .opportunities-meeting__actions {
        width: 100%;
        justify-content: flex-start;
        gap: var(--spacing-sm);
    }

    .opportunities-meeting__action {
        width: auto;
        min-height: var(--control-h-touch);
        padding: 0 var(--spacing-smd);
        gap: var(--spacing-xs);
        color: var(--color-text);
    }

    .opportunities-meeting__action-label {
        display: inline;
        font-size: var(--font-size-sm);
    }

    .opportunities-meeting__hist-toggle {
        min-height: var(--control-h-touch);
        font-size: var(--font-size-sm);
    }

    .global-hist__body,
    .global-hist__date,
    .global-hist__time,
    .global-hist__actor {
        font-size: var(--font-size-sm);
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .opportunities-detail__session {
        padding: var(--spacing-smd) var(--page-gutter);
    }

    /* ── Pipeline: the stage band is a horizontal rail, current stage centred. ── */
    .opportunities-stageflow {
        position: relative;
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding: var(--spacing-sm) var(--page-gutter);
        gap: var(--spacing-sm);
    }

    .opportunities-stageflow::-webkit-scrollbar {
        display: none;
    }

    .opportunities-stageflow::after {
        content: "";
        position: sticky;
        right: 0;
        flex: 0 0 var(--spacing-xl);
        align-self: stretch;
        pointer-events: none;
        background: linear-gradient(to right, transparent, var(--color-card-bg));
    }

    .opportunities-stageflow__stage {
        flex: 0 0 72%;
        scroll-snap-align: center;
        min-height: var(--control-h-touch);
    }

    .opportunities-stageflow__name {
        white-space: normal;
    }

    /* ── Pipeline: band — full-width advance, one progress line, 44px rows. ──── */
    .opportunities-pipe-band {
        padding: var(--spacing-sm) var(--page-gutter);
    }

    .opportunities-pipe-band__left {
        display: block;
    }

    .opportunities-pipe-band__action {
        width: 100%;
        margin-left: 0;
    }

    .opportunities-pipe-band__advance {
        flex: 1 1 auto;
        min-height: var(--control-h-touch);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-size: var(--font-size-md);
    }

    .opportunities-pipe-band__overflow-toggle {
        min-width: var(--control-h-touch);
        min-height: var(--control-h-touch);
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .opportunities-pipe-band__blocked {
        font-size: var(--font-size-sm);
        padding: var(--spacing-sm) var(--spacing-smd);
    }

    .opportunities-pipe-band__blockers {
        gap: 0;
    }

    .opportunities-pipe-band__blocker {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--spacing-2xs);
        min-height: var(--control-h-touch);
        justify-content: center;
        padding: var(--spacing-xs) 0;
        border-top: 1px solid color-mix(in srgb, var(--color-warning) 20%, transparent);
    }

    .opportunities-pipe-band__blocker:first-child {
        border-top: none;
    }

    .opportunities-pipe-band__blocker-link {
        display: inline-flex;
        align-items: center;
        min-height: var(--control-h-touch);
        font-size: var(--font-size-md);
    }

    /* ── Forms: toolbar button spans the width; each card's actions are 44px. ── */
    .opportunities-detail__form-cards {
        grid-template-columns: 1fr;
        padding: var(--spacing-smd) var(--page-gutter);
    }

    .opportunities-detail__form-action,
    .opportunities-detail__form-button {
        display: inline-flex;
        align-items: center;
        min-height: var(--control-h-touch);
        font-size: var(--font-size-md);
    }

    .opportunities-detail__form-button {
        padding: 0 var(--spacing-smd);
    }

    .opportunities-detail__form-resend,
    .opportunities-detail__form-remove {
        display: flex;
    }

    .opportunities-detail__form-resend .opportunities-detail__form-button {
        flex: 1 1 auto;
        justify-content: center;
    }

    /* ── Files: the pane fills the width; one button, one column, 44px rows. ── */
    .opportunities-detail__files {
        width: 100%;
        max-width: none;
        min-width: 0;
        padding: var(--spacing-smd) var(--page-gutter);
    }

    .opportunities-detail__file-dropzone {
        flex-direction: row;
        min-height: calc(var(--control-h-touch) + var(--spacing-smd));
        padding: var(--spacing-sm) var(--spacing-md);
        gap: var(--spacing-sm);
        border-style: solid;
        border-width: 1px;
        border-radius: var(--radius-md);
    }

    .opportunities-detail__file-dropzone-icon {
        font-size: var(--font-size-xl);
    }

    .opportunities-detail__file-dropzone-label--wide,
    .opportunities-detail__file-dropzone-hint {
        display: none;
    }

    .opportunities-detail__file-dropzone-label--narrow {
        display: inline;
    }

    .opportunities-detail__file-actor-form {
        flex-direction: column;
        align-items: stretch;
    }

    .opportunities-detail__file-actor {
        width: 100%;
        min-height: var(--control-h-touch);
        font-size: var(--font-size-base);
    }

    .opportunities-detail__files .opportunities-detail__file-cards,
    .opportunities-detail__file-cards {
        grid-template-columns: 1fr;
    }

    .opportunities-detail__file-preview {
        height: calc(var(--control-h-touch) + var(--spacing-smd));
    }

    .opportunities-detail__file-actions {
        flex-wrap: wrap;
        gap: var(--spacing-sm);
    }

    .opportunities-detail__file-action,
    .opportunities-detail__file-remove {
        min-height: var(--control-h-touch);
        padding: 0 var(--spacing-sm);
        font-size: var(--font-size-md);
    }

    /* An admin's pay pill opens the bill drawer, so on a phone it is a 44px
       target like the row's other controls; the plain pay figure stays a pill. */
    .opportunities-detail__role-actions .global-value-pill--interactive {
        min-height: var(--control-h-touch);
        padding-inline: var(--spacing-md);
    }
}
