/*
 * Opportunities milestone matrix (Table view). Light weights, 1px hairlines,
 * tight radii, opt-in zebra; colour is reserved for meaning (the status hues)
 * and the selected saved-view tab stays greyscale, so the table reads as a
 * data grid, not a branded surface.
 */

/* The saved-view tab strip is the canonical .global-tabs--neutral component now,
   rendered in the page header's tabs slot (interface/templates/app/assignments/
   _view_tabs.html, mounted via shared/components/page_header.html). It carries no
   opportunities-specific CSS — the greyscale HubSpot skin lives entirely in the
   shared component (components/page-tabs.css). */

/* Page card: the house .global-card (components/card.css) — white surface,
   1px --color-border edge, --radius-card, --shadow-sm — the same container every
   other page puts its content in (Nick 2026-09-19). overflow:hidden clips the
   table's corners to the card radius.

   Viewport fit — the table scrolling inside its own box while the page never
   scrolls — is the shared .global-page-fill chain now (main.css), which this
   card also carries in _table_body.html. The card and its body are flex
   columns so that chain reaches .opportunities-milestone-table-wrap unbroken
   (.global-card is already a flex column; the body is re-asserted here). The card view keeps normal page scroll on purpose: its paginator is
   tied to the window — infinite-scroll.js builds its IntersectionObserver with
   no `root`, and its fallback listens on window 'scroll' — so it pins its
   header instead (pinned=True on the page_header include; rules in
   page-header.css). */
.opportunities-table-card {
    overflow: hidden;
}
.opportunities-table-card > .card-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
}

.opportunities-milestone-table-wrap {
    /* Both axes scroll inside the bounded wrap (HubSpot pattern), so the
       horizontal scrollbar rides the bottom of the box, not the bottom of the
       full table where it would sit below the fold. */
    overflow: auto;
    overscroll-behavior: contain;
    /* The editor popover measures from here; without it a scrolled table would
       position the popover off some other ancestor and miss the cell. */
    position: relative;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    background: var(--color-card-bg);
    /* Persistent scrollbars. macOS "overlay" scrollbars stay hidden until the
       user is actively scrolling, so the horizontal bar on this wide grid would
       vanish. Styling the
       track + thumb forces an always-visible bar on WebKit/Blink and a thin one on
       Firefox; on platforms that already show a bar this is just a cosmetic skin. */
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}
.opportunities-milestone-table-wrap::-webkit-scrollbar { width: 12px; height: 12px; }
.opportunities-milestone-table-wrap::-webkit-scrollbar-track { background: var(--color-surface-subtle); }
.opportunities-milestone-table-wrap::-webkit-scrollbar-thumb {
    background: var(--color-border);
    border-radius: var(--radius-md);
    border: 3px solid var(--color-surface-subtle); /* inset the thumb so it reads as a rounded pill */
}
.opportunities-milestone-table-wrap::-webkit-scrollbar-corner { background: var(--color-surface-subtle); }

/* Inside the page card the wrap is only the scroll viewport — the card is the
   visible surface, so drop the wrap's own border/radius to avoid a double edge,
   and let it flex to fill the card body. min-height:0 is what lets a flex child
   shrink below its content height so it scrolls instead of growing the page. */
.opportunities-table-card .opportunities-milestone-table-wrap {
    border: none;
    border-radius: 0;
    flex: 1 1 auto;
    min-height: 0;
}

.opportunities-milestone-table {
    border-collapse: separate;
    border-spacing: 0;
    width: max-content;
    min-width: 100%;
    font-size: var(--font-size-sm);
}

.opportunities-milestone-table th,
.opportunities-milestone-table td {
    border-bottom: 1px solid var(--color-border-subtle);
    border-right: 1px solid var(--color-surface-subtle);
    /* No blanket padding reset here: at (0,1,1) it outranks every per-cell
       padding class (.opportunities-milestone-td, .opportunities-milestone-propcell at (0,1,0)) and
       would clamp them to 0. Each cell class owns its own padding instead. */
}

/* Header band. position: sticky pins the header to the top of the bounded
   .opportunities-milestone-table-wrap as rows scroll under it (HubSpot pattern) AND still anchors each
   column's absolutely-positioned resize handle — sticky is a positioned value, so
   it establishes the same containing block `relative` did. z-index 3 is the band's
   own stacking context — nothing in the body scrolls above it.
   The band paints the card's own white with a --color-border hairline under it, the
   house grid header (.global-grid__head) — a grey band read as one more zebra stripe
   (Nick 2026-09-19). No per-stage colours; the table is a neutral data grid. */
.opportunities-milestone-table thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--color-card-bg);
    color: var(--color-text);
    /* One header type for the WHOLE band — 12px / medium weight / UPPERCASE / 0.02em
       tracking (HubSpot's grid header) — inherited here since every header cell
       shares this one rule regardless of column kind. */
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    text-align: center;
    vertical-align: middle;
    border-bottom: 1px solid var(--color-border);
}

/* Stage context for a milestone lives in its tooltip (milestone · stage); the
   header itself carries no stage colour (the stage band row was removed for
   crowding the header, and per-org colours were dropped for a clean grid). */
.opportunities-milestone-table__milestone {
    position: relative; /* anchor for the resize handle */
    padding: var(--spacing-sm) var(--spacing-smd);
    min-width: 110px;
    max-width: 320px;
    height: 36px; /* HubSpot header band height */
}

/* HubSpot pattern: header labels WRAP (white-space: normal) instead of being
   truncated with an ellipsis, and the whole table scrolls horizontally. This is
   what stops the column headers from being cut off. The header type (12px /
   --font-weight-medium / uppercase / 0.02em / --color-text) is inherited from .opportunities-milestone-table thead th,
   which owns it for the whole band — only layout props live here. */
.opportunities-milestone-table__milestone-label {
    display: block;
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.3;
}

.opportunities-milestone-table__milestone--required .opportunities-milestone-table__milestone-label {
    font-weight: 700;
}

/* Drag-to-resize handle: a thin hit-area on the column's right edge. The visible
   1px rule only appears on hover, HubSpot-style. touch-action: none lets the
   pointer-events drag own touch input instead of fighting the wrap's scroll. */
.opportunities-milestone-resize {
    position: absolute;
    top: 0;
    right: -4px;
    width: 9px;
    height: 100%;
    cursor: col-resize;
    z-index: 4;
    user-select: none;
    touch-action: none;
}

.opportunities-milestone-resize::after {
    content: "";
    position: absolute;
    top: 6px;
    bottom: 6px;
    left: 4px;
    width: 1px;
    background: transparent;
    transition: background var(--transition-fast);
}

.opportunities-milestone-table thead th:hover .opportunities-milestone-resize::after,
.opportunities-milestone-resize:hover::after {
    background: var(--color-text-muted);
}

/* Reference's small style: font-size-sm / medium weight, no underline until
   hover. Kept regardless of which column lands first — the "row's only way in"
   link (.opportunities-milestone-row-link, below) wraps whichever cell that is,
   so Reference no longer needs to be a link itself to earn this look. */
.opportunities-milestone-ref-link {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    text-decoration: none;
    white-space: nowrap;
}

.opportunities-milestone-ref-link:hover {
    text-decoration: underline;
}

/* The record's only way into the detail page (object-grid.css: "the first cell
   is the row's only way into the record"). Whichever property column an org has
   picked first wraps its content in this link, so the anchor never depends on
   which key that turns out to be. */
.opportunities-milestone-row-link {
    display: block;
    color: inherit;
    text-decoration: none;
}

.opportunities-milestone-row-link:hover {
    text-decoration: underline;
}

/* Property columns: org-selectable facts rendered before the milestone grid —
   Booking, Reference, Stage, service date, status, location, team, etc. All
   centered alike; none is pinned or left-aligned as a fixed identity anchor. */
.opportunities-milestone-table thead th.opportunities-milestone-table__propcol {
    padding: var(--spacing-sm) var(--spacing-smd);
    min-width: 120px;
    max-width: 240px;
}

.opportunities-milestone-propcell {
    height: var(--ms-row-h, 84px);
    padding: var(--spacing-sm) var(--spacing-smd);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-body);
    color: var(--color-text);
    text-align: center;
    vertical-align: middle;
    max-width: 240px;
    /* Clip when a column is dragged narrower than its nowrap content — without
       this the widest cell floors the resize and the drag appears to "stick". */
    overflow: hidden;
}

/* Frozen first column (HubSpot pattern), ported from the old booking-only rule.
   Whichever property column renders first is the row's own link
   (opportunities-milestone-propcell--first, _milestone_rows.html) — pin it to
   the left edge on both header and body so it never scrolls out from under a
   wide grid. Selectors are written at the same specificity as the base rules
   above (and placed after them) so the override actually lands. z-index 2
   keeps the body cell above the scrolling rows beneath it; the header corner
   (below) climbs to z-index 5 to also outrank the sticky header band
   (z-index 3, above) and the resize handle (z-index 4).
   Conditional styling: a saved view's rules stamp data-tone on a property cell
   and object-grid.css paints it with the roster's tokens. This fill, the zebra
   and the hover fill below all read the tone first and keep their own colour as
   the fallback — otherwise a rule paints only the rows the pointer is not on. */
.opportunities-milestone-propcell.opportunities-milestone-propcell--first {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--grid-tone-bg, var(--color-card-bg));
    min-width: 180px;
    text-align: left;
    padding: var(--spacing-smd) var(--spacing-md) var(--spacing-smd) var(--spacing-mdlg);
    vertical-align: middle;
    box-shadow: 1px 0 0 var(--color-border-subtle);
}

.opportunities-milestone-table thead th.opportunities-milestone-table__propcol--first {
    position: sticky;
    left: 0;
    z-index: 5;
    background: var(--color-card-bg);
    min-width: 180px;
    text-align: left;
    padding: var(--spacing-smd) var(--spacing-md) var(--spacing-smd) var(--spacing-mdlg);
    box-shadow: 1px 0 0 var(--color-border-subtle);
}

.opportunities-milestone-propcell--editable {
    cursor: text;
}

.opportunities-milestone-propcell--editable:hover .global-grid__pencil {
    opacity: 1;
}

.opportunities-milestone-prop__main {
    display: block;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.opportunities-milestone-prop__sub {
    display: block;
    margin-top: 1px;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The Worker cost cell, when it opens that booking's bills dashboard. Reads as
   the same figure as every other money cell — the underline only shows up on
   hover, which is what marks it as a link instead of plain text. */
a.opportunities-milestone-prop__main--link {
    color: inherit;
    text-decoration: none;
}

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

/* Team column — one compact "Role · Worker" line per role; an unfilled role reads
   as a muted "Open" so staffing gaps surface in the same glance. */
.opportunities-milestone-team {
    margin: 0;
    padding: 0;
    list-style: none;
    /* A booking with more roles than fit the row would stretch the row taller than
       its neighbours (the team list is the only body cell that grows vertically).
       Cap it to the row's inner height and let the overflow scroll INSIDE the cell
       — same hidden-scrollbar pattern as the sidebar nav and the
       calendar day containers. 18px = the 16px
       of vertical padding on .opportunities-milestone-propcell (padding: 8px 12px → 8px top + 8px
       bottom), plus 2px of headroom: a scroll container otherwise picks up a 1px
       rounding artifact that would push the capped row a pixel taller than its
       neighbours (breaking uniformity). Keep this in step with .opportunities-milestone-propcell's
       padding. */
    max-height: calc(var(--ms-row-h, 84px) - 18px);
    overflow: hidden auto;
    overscroll-behavior: contain;
    scrollbar-width: none;            /* Firefox — wheel/trackpad scrolls, no bar */
}

.opportunities-milestone-team::-webkit-scrollbar { display: none; }   /* Chrome / Safari */

.opportunities-milestone-team__member {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: var(--spacing-xsm);
    line-height: 1.35;
    white-space: nowrap;
}

.opportunities-milestone-team__role {
    font-size: var(--font-size-2xs);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--color-text-muted);
}

.opportunities-milestone-team__worker {
    font-size: var(--font-size-sm);
    overflow: hidden;
    text-overflow: ellipsis;
}

.opportunities-milestone-team__member--open .opportunities-milestone-team__worker {
    color: var(--color-warning-text);
    font-style: italic;
}

/* Status pill — tinted per OpportunityStatus, a neutral tinted grid chip. */
.opportunities-milestone-status {
    display: inline-block;
    padding: var(--spacing-2xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-body);
    border-radius: var(--radius-sm);
    background: var(--color-surface-subtle);
    color: var(--color-text-secondary);
    white-space: nowrap;
}

.opportunities-milestone-status--draft {
    background: var(--color-surface-subtle);
    color: var(--color-text-muted);
}

.opportunities-milestone-status--open {
    background: #e5f5f8;
    color: #0091ae;
}

.opportunities-milestone-status--filled {
    background: #ece9fb;
    color: #6a78d1;
}

.opportunities-milestone-status--paused {
    background: var(--color-warning-bg);
    color: var(--color-warning-text);
}

.opportunities-milestone-status--in_progress {
    background: #d6eff4;
    color: #007a91;
}

.opportunities-milestone-status--completed {
    background: var(--color-availability-bg);
    color: var(--color-success);
}

.opportunities-milestone-status--cancelled {
    background: var(--color-error-subtle);
    color: var(--color-error-text);
}

/* Row-height tiers. The view picks a density in the settings drawer; the matching .ms-rh-* class on the scroll wrapper supplies --ms-row-h,
   so the choice paints server-side and Alpine never touches cell styles. Every
   tier is floored at the booking cell's ~73px three-line content so the identity
   (template / service / title) is never clipped at any density; the team list
   scrolls within the row when a booking has more roles than fit. The old 40/56/72
   values sat BELOW that floor, so the booking cell overrode them and 2-line vs
   3-line bookings rendered at different heights — the non-uniformity this guards against.
   There is deliberately no .ms-rh-standard rule: the standard tier IS the base
   value below, so the JS-added ms-rh-standard class just inherits it; compact and
   relaxed are the only overrides. */
.opportunities-milestone-table-wrap {
    --ms-row-h: 84px; /* standard tier — the default */
}

.opportunities-milestone-table-wrap.opportunities-milestone-rh-compact {
    --ms-row-h: 76px; /* a few px above the ~73px three-line booking floor */
}

.opportunities-milestone-table-wrap.opportunities-milestone-rh-relaxed {
    --ms-row-h: 104px;
}

.opportunities-milestone-td {
    height: var(--ms-row-h, 84px);
    padding: var(--spacing-sm) var(--spacing-smd);
    font-size: var(--font-size-md);
    font-weight: 300;
    color: var(--color-text);
    text-align: center;
    vertical-align: middle;
}

/* Zebra striping — opt-in via the drawer. The row's first
   cell is a <th>, not a <td> (see .opportunities-milestone-row-link), so it
   needs its own selector alongside td to pick up the stripe too. */
.opportunities-milestone-table-wrap.opportunities-milestone-zebra tbody tr:nth-child(even) td,
.opportunities-milestone-table-wrap.opportunities-milestone-zebra tbody tr:nth-child(even) th {
    background: var(--grid-tone-bg, var(--color-surface-subtle));
}

/* Row hover — HubSpot highlights the row under the cursor so the eye can track a
   booking straight across a wide grid. The row's <th> first cell gets the tint
   too; placed after .opportunities-milestone-zebra and matched at equal
   specificity so it wins on striped rows. */
.opportunities-milestone-table-wrap .opportunities-milestone-table tbody tr:hover > td,
.opportunities-milestone-table-wrap .opportunities-milestone-table tbody tr:hover > th {
    background: var(--grid-tone-bg, var(--color-grey-100));
}

.opportunities-milestone-blank {
    color: var(--color-border);
}

/* Conditional formatting: a cell whose milestone is past its due day and still
   not done or skipped. Nick, 2026-09-17: "use the table view in skillbridge
   along with conditional formatting to highlight things red that meet certain
   criteria like this" — so lateness is a property of the CELL, tinted in place,
   rather than a digest posted somewhere else.

   Tinted with the error token rather than a flat red so the zebra stripe and the
   row hover above still read through it; the solid left bar is what carries at a
   glance across a wide grid. The bar is the first stop of the background gradient
   rather than an inset box-shadow so the cell keeps its own shadow slot free.
   Placed after the zebra and hover rules and matched at higher specificity so a
   late cell stays red on a striped, hovered row. The fill reads the view's own
   styling tone first (object-grid.css, data-tone) and keeps the error tint as
   its fallback, so a rule on a late cell still shows; the red bar stays. */
.opportunities-milestone-table-wrap .opportunities-milestone-table tbody tr > .opportunities-milestone-td--late,
.opportunities-milestone-table-wrap .opportunities-milestone-table tbody tr:hover > .opportunities-milestone-td--late {
    background: linear-gradient(
        to right,
        var(--color-error) 0,
        var(--color-error) 3px,
        var(--grid-tone-bg, var(--color-error-subtle)) 3px
    );
}

/* The due day, under the complete/skip buttons. Only milestones that carry a
   due-date policy (or a stored per-booking date) render one at all, so this is a
   quiet line in two columns rather than noise in every one. */
.opportunities-milestone-due {
    display: block;
    margin-top: var(--spacing-2xs);
    color: var(--color-text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

.opportunities-milestone-due--late,
.opportunities-milestone-td--late .opportunities-milestone-pending {
    color: var(--color-error-text);
}

/* Read-only "pending" marker shown to non-coordinators in place of the
   complete/skip buttons (they may view the matrix but not mutate it). Reads as a
   state — the canonical pending dot plus the word — not a bare middot. */
.opportunities-milestone-pending {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: 500;
}

/* Editable cell wrapper — carries data-org/data-opp/data-instance for
   window.__sbMilestone, on both the not-yet-done buttons row and a
   done/skipped stamp that an editor can undo. */
.opportunities-milestone-cell {
    display: inline-flex;
}

/* Not-yet-done — a Done pill, and (optional milestones only) a quiet Skip
   text button beside it. */
.opportunities-milestone-act {
    align-items: center;
    gap: var(--spacing-xs);
}

.opportunities-milestone-do {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    height: 26px;
    padding: 0 var(--spacing-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-button);
    background: var(--color-card-bg);
    color: var(--color-text);
    cursor: pointer;
    font-size: var(--font-size-xs);
    font-weight: 500;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.opportunities-milestone-do:hover {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success-text);
}

.opportunities-milestone-do:disabled {
    opacity: 0.5;
    cursor: default;
}

.opportunities-milestone-skip {
    height: 26px;
    padding: 0 var(--spacing-xs);
    border: none;
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    font-size: var(--font-size-xs);
    transition: background 0.12s ease, color 0.12s ease;
}

.opportunities-milestone-skip:hover {
    background: var(--color-action-tint);
    color: var(--color-text);
}

.opportunities-milestone-skip:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Done/skipped stamp — an icon and a word, a muted when-line, and (an editor
   only, on hover or keyboard focus) an Undo pill below. The JS optimistic
   handler in my_assignments_table.html builds this same markup, so it must
   keep matching _milestone_stamp.html class-for-class. */
.opportunities-milestone-stamp {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    line-height: 1.25;
}

.opportunities-milestone-stamp__top {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

/* Done reads in the success colour; skipped stays neutral grey — a skip is a
   deliberate choice, not a failure, so it never reads as an error/red state. */
.opportunities-milestone-stamp--done .opportunities-milestone-stamp__top {
    color: var(--color-success);
}

.opportunities-milestone-stamp--skip .opportunities-milestone-stamp__top {
    color: var(--color-text-muted);
}

/* A deliverable cell's two lines sit the same distance apart wherever they
   happen to live. A button and the line under it are BOTH inside the stamp,
   while the who line is outside it, so __main's gap only ever reaches one of
   the two pairs — the stamp needs the same number for the other. One token, so
   there is still only one number to change. A milestone cell keeps its own
   tight stack: its word and its time read as one stamp, not as two lines. */
.opportunities-milestone-delivcell .opportunities-milestone-stamp {
    gap: var(--spacing-xsm);
}

/* A role-deliverable cell reuses this stacked shape and NOTHING else from here:
   its state is the platform's global-badge, so its colour comes from
   STATUS_VARIANTS (apps/core/templatetags/ui_tags.py) and this file gives it
   none. There is deliberately no --handed rule any more — it painted a
   waiting deliverable in warning ink, and amber is not a colour this product
   uses (Nick, 2026-09-22). See _milestone_delivcell_content.html. */

/* The state pill is a link into the role it names, which is the only way three
   of the four states lead anywhere: handing work in is the worker's own step
   and can never be a table button, so without this those cells were somewhere
   to read and nowhere to go. The pill keeps the state's own colour and gains
   none of its own — an arrow appears beside it once the cell is hovered or the
   link takes keyboard focus, the same opacity/pointer-events reveal the Undo
   pill uses, so Tab still reaches it. */
/* A role-deliverable cell is the stacked stamp with the way into the role
   BESIDE it, not tacked onto the end of its words: the state, the button and
   the day read down the left, and the link reads as its own control on the
   right. */
.opportunities-milestone-delivcell {
    display: inline-flex;
    align-items: stretch;
    gap: var(--spacing-sm);
}

/* Everything the way in stands beside: the stack, and the line saying who is
   on the seat. One column, so the link has a single thing to match.

   🚨 CAPPED, and it has to be. .opportunities-milestone-propcell's `height` is a
   MINIMUM in table layout, so a cell taller than the tier pushes its whole row
   taller and the row-height tiers stop meaning anything — compact would render
   as tall as relaxed, and only on rows that happen to carry a deliverable. The
   team list solved this first (see .opportunities-milestone-team above) and its
   comment used to call itself "the only body cell that grows vertically"; this
   is now the second, so it takes the same treatment and the same 18px. Keep
   both in step with .opportunities-milestone-propcell's padding.

   The cell shows two lines and never three, so its tallest state is the button
   (26px) over the gap over the day (18px) — 50px against the 68px a standard
   row leaves once the cell's own padding comes off. No state drags a row
   taller, and the cap is here for the row tiers a reader can shrink to. */
.opportunities-milestone-delivcell__main {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    /* The air between the two lines. Settled by eye against the shipped cell's
       2px hairline (Nick, 2026-09-22: 24px "way too much", 8px "still a little
       too much"). */
    gap: var(--spacing-xsm);
    max-height: calc(var(--ms-row-h, 84px) - 18px);
    overflow: hidden auto;
    overscroll-behavior: contain;
    scrollbar-width: none;            /* Firefox — wheel/trackpad scrolls, no bar */
}

.opportunities-milestone-delivcell__main::-webkit-scrollbar { display: none; }

/* Who is holding the role. A column, because two people on one role read as
   two lines rather than a wrapped run.

   🚨 It must stay OUTSIDE .opportunities-milestone-stamp: every optimistic
   repaint in my_assignments_table.html overwrites that stack's innerHTML, so a
   face moved inside would vanish the moment somebody ticked the cell done.

   No margin of its own: the two lines are spaced by __main's gap, so the cell
   has ONE number deciding how far apart they sit. */
.opportunities-milestone-who {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--spacing-2xs);
    /* Dragged into the first column the cell sits inside the row's own link,
       which underlines everything under the pointer — a person's name reading
       as a link to that person, when the row goes to the booking. */
    text-decoration: none;
}

/* A cell holding a button hides this line rather than dropping it, so the
   repaint that takes the button away has a name to put back (see
   my_assignments_table.html). `display` above would otherwise beat the
   browser's own rule for the attribute, and the line would show through. */
.opportunities-milestone-who[hidden] {
    display: none;
}

/* The way into the role, as tall as everything it sits beside. `stretch` gives
   it the column's OWN height rather than a guessed one, so it runs the two lines
   it sits beside end to end (Nick, 2026-09-22: "new tab icon
   needs be the same height as the button and due date combined") and a cell
   that gains or loses a line still lines up top and bottom, with no number here
   to keep in step. It holds its space whether or not it is showing, so the
   stack beside it never shifts under the pointer. */
.opportunities-milestone-open {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    padding: 0 var(--spacing-xs);
    border-radius: var(--radius-button);
    color: inherit;
    text-decoration: none;
    opacity: 0;
    transition: opacity 0.12s ease, background 0.12s ease;
}

/* Full height means the whole right edge of the cell is the click, so it says
   so under the pointer — without a tint a tall target reads as empty space. */
.opportunities-milestone-open:hover {
    background: var(--color-action-tint);
}

.opportunities-milestone-open:hover .opportunities-milestone-open__go {
    color: var(--color-text);
}

.opportunities-milestone-open__go {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Hovering ANYWHERE in the cell shows it — the propcell is the whole table
   cell, so the reader does not have to find the words first. */
.opportunities-milestone-propcell:hover .opportunities-milestone-open,
.opportunities-milestone-propcell:focus-within .opportunities-milestone-open,
.opportunities-milestone-open:focus-visible {
    opacity: 1;
}

/* A touch screen has no hover to reveal it with, so it is simply there. */
@media (hover: none) {
    .opportunities-milestone-open {
        opacity: 1;
    }
}

/* The buttons in a deliverable stamp sit UNDER the state rather than beside
   it, which is the only thing that differs from the pill's use in a
   not-yet-done milestone cell. */
.opportunities-milestone-do--stamped {
    margin-top: var(--spacing-2xs);
}

/* Anything that LEADS the stack has nothing above it to be pushed away from.
   A cell whose Mark done button stands in for its word starts with that
   button, and a cell with no button and no word starts with the day. */
.opportunities-milestone-stamp > :first-child.opportunities-milestone-do--stamped,
.opportunities-milestone-stamp > :first-child.opportunities-milestone-due {
    margin-top: 0;
}

.opportunities-milestone-stamp__when {
    font-size: var(--font-size-2xs);
    color: var(--color-text-muted);
    white-space: nowrap;
}

/* Hidden until the cell is hovered or the pill itself takes keyboard focus —
   opacity/pointer-events rather than display:none, so Tab can still reach it. */
.opportunities-milestone-undo {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-2xs);
    height: 20px;
    margin-top: var(--spacing-2xs);
    padding: 0 var(--spacing-xs);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-button);
    background: var(--color-card-bg);
    color: var(--color-text-muted);
    cursor: pointer;
    font-size: var(--font-size-2xs);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.opportunities-milestone-td:hover .opportunities-milestone-undo,
.opportunities-milestone-td:focus-within .opportunities-milestone-undo,
.opportunities-milestone-undo:focus-visible {
    opacity: 1;
    pointer-events: auto;
}

.opportunities-milestone-undo:hover {
    color: var(--color-text);
    border-color: var(--color-text-muted);
}

.opportunities-milestone-undo:disabled {
    opacity: 0.5;
    cursor: default;
    pointer-events: none;
}

/* Infinite-scroll sentinel: the table defaults to infinite scroll — a sentinel row at the tail fetches the
   next page when revealed (HTMX hx-trigger="revealed"). It spans every column and
   shows a quiet loading line; htmx swaps the next batch of rows in its place. */
.opportunities-milestone-scroll-sentinel td {
    padding: var(--spacing-smd) var(--spacing-smd);
    text-align: center;
    border-bottom: none;
}
