/* ==========================================================================
   Signup Groups — grouped role rows on the sign-up dashboard
   ========================================================================== */

:root {
    /* One height for the action buttons and the status badges that sit beside
       them, so a pill and a button read the same size. The tap-target rung of
       the shared control-height ladder — these are the page's primary actions. */
    --signup-role-control-h: var(--control-h-touch);
}

.assignments-groups {
    width: 100%;
    /* The first group's header carries 0.875rem of internal top padding, which
       stacks on the toolbar's standard 1.25rem margin and reads as a wider gap
       than every other dashboard. Cancel it here so the first visible ink lands
       at the platform's usual 1.25rem below the toolbar (Nick, 08-17). */
    margin: -0.875rem 0 0;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--spacing-smd);
    align-items: start;
}

/* Group container — white bg starts halfway up the pill */
.assignments-group {
    background: transparent;
    position: relative;
    margin: 0;
}

.assignments-group::before {
    content: '';
    position: absolute;
    top: 2.1rem;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--color-card-bg);
    border: 1px solid var(--color-border-hairline);
    border-radius: var(--radius-card);
    box-shadow: 0 2px 8px var(--ink-alpha-05);
    z-index: 0;
}

.assignments-group__header {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-smd) var(--spacing-md) var(--spacing-2xs);
}

.assignments-group__title-wrap {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--spacing-sm);
    min-width: 0;
}

/* Ink pill title — the group heading, on the neutral action ramp */
.assignments-group__pill {
    background: var(--color-action);
    color: #fff;
    font-weight: var(--font-weight-heading);
    border-radius: var(--radius-pill);
    padding: var(--spacing-xsm) var(--spacing-md);
    font-size: var(--font-size-lg);
    letter-spacing: 0.15px;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    transition: background 200ms ease, box-shadow 200ms ease;
}

.assignments-group__pill:hover {
    background: var(--color-action-hover);
    box-shadow: 0 2px 8px var(--ink-alpha-20);
    color: #fff;
    text-decoration: none;
}

.assignments-group__ref {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

/* Worker avatars — directly right of the black pill, no overlap, pill-height matched */
.assignments-group__workers {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    margin-left: var(--spacing-sm);
    gap: var(--spacing-xsm);
}

.assignments-group__worker-avatar {
    width: 2.25rem;
    height: 2.25rem;
    font-size: var(--font-size-2xs);
    border: 2px solid var(--color-card-bg);
    cursor: default;
    box-shadow: 0 1px 3px var(--ink-alpha-10);
}

.assignments-group__worker-avatar:first-child {
    margin-left: 0;
}

/* Referral avatar — worker-sized (shares .assignments-group__worker-avatar) but glows
   with brand color to draw attention. The scoped selector outranks the
   worker-avatar box-shadow so the glow ring wins. The clip and the `img` fill rule
   that used to live here are `.global-avatar`; only the glow is local. */
.assignments-group .assignments-group__referral-avatar {
    box-shadow: 0 0 0 3px var(--brand-primary),
                0 0 12px color-mix(in srgb, var(--brand-primary) 40%, transparent);
    animation: referral-pulse 2s ease-in-out infinite;
}

@keyframes referral-pulse {
    0%, 100% { box-shadow: 0 0 0 3px var(--brand-primary), 0 0 8px color-mix(in srgb, var(--brand-primary) 30%, transparent); }
    50% { box-shadow: 0 0 0 3px var(--brand-primary), 0 0 16px color-mix(in srgb, var(--brand-primary) 50%, transparent); }
}

/* Opportunity-level event info — date + booking time, shown once for the whole
   group. margin-left:auto claims the header's free space so this block floats to the
   far-right edge while the worker avatars stay packed next to the pill (their position
   is unchanged — the whole point of Nick's "all the way right, don't move the icons").
   Date and time sit INLINE (flex-direction:row) on one line, joined by a · separator,
   wrapped in a raised white pill — the small shadow floats it off the grey header band
   (grey-on-grey faded there; Nick picked the raised look over a brand tint) — so the
   block reads as a single standout chip rather than two stacked lines. */
.assignments-group__event {
    margin-left: auto;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--spacing-sm);
    flex-shrink: 0;
    line-height: 1.2;
    background: var(--color-card-bg);
    border: 1px solid var(--color-border-hairline);
    border-radius: var(--radius-pill);
    padding: var(--spacing-xsm) var(--spacing-smd);
    box-shadow: var(--shadow-sm);
}

/* Calendar glyph leading the pill — a quiet visual cue that this chip is the date/time.
   Slightly tighter gap to the date than the date·time gap so it reads as an icon, not a
   third value. Brand ink (the contrast-corrected on-card pair, safe on the white
   fill for any org palette) so the pill reads as "this is the when" at a glance. */
.assignments-group__event-icon {
    color: var(--brand-primary-on-card-bg);
    font-size: var(--font-size-sm);
    margin-right: -0.1rem;
}

/* The dot joining date and time inside the pill (only rendered when both exist). */
.assignments-group__event-sep {
    color: var(--color-text-muted);
    font-weight: var(--font-weight-heading);
}

.assignments-group__event-date {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-heading);
    color: var(--color-text);
    white-space: nowrap;
}

/* For a signed-in worker the date is the Day Preview trigger — a button styled to read
   as plain text (no chrome). font-family (NOT font shorthand) so weight/size stay ours. */
.assignments-group__event-date--action {
    padding: 0;
    border: 0;
    background: none;
    font-family: inherit;
    cursor: pointer;
    transition: color 0.15s ease;
}

.assignments-group__event-date--action:hover {
    color: var(--brand-primary);
}

.assignments-group__event-time {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-grey-600);
    white-space: nowrap;
}

/* Role row — desktop grid
   name+location | dates (150px) | properties (150px) | action
   NOTE: no overflow:hidden — would clip the global-split-button chevron menu.
   Inner text fields handle their own ellipsis truncation.
   NOTE: z-index:auto (not 1) so the row doesn't form a stacking context;
   otherwise an open .global-split-button__menu would be trapped below later rows. */
.assignments-role-row {
    position: relative;
    display: grid;
    /* Three columns: the main column (role name stacked over the muted meta chip line,
       FIXED width) | the scrolling property card (1fr) | the action, content-sized and
       pinned to the right edge. The main column is a FIXED track (--signup-name-col), not
       greedy, so the property card begins at the same x on every row no matter how wide a
       row's name or chips are — variable name content can't bump the card column sideways.
       The card is 1fr (fills the slack) and the action sizes to content. align-items:center
       keeps all three vertically centered against the stacked main block. Move the shared
       card start left/right with the single --signup-name-col knob. */
    --signup-name-col: 27.5rem;
    grid-template-columns: var(--signup-name-col) minmax(0, 1fr) max-content;
    align-items: center;
    gap: 0 var(--spacing-smd);
    padding: var(--spacing-smd) var(--spacing-md);
    line-height: 1.25;
}

/* Left main column — stacks the bold name line over the muted meta line. min-width:0 lets
   both children shrink and truncate instead of forcing the row wider than its grid track. */
.assignments-role-row__main {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    min-width: 0;
    gap: var(--spacing-xs);
}

/* Border between consecutive role rows only (not between header and first role) */
.assignments-role-row + .assignments-role-row {
    border-top: 1px solid var(--color-border-hairline);
}

/* The value pill base lives in components/value-pill.css (one shared numeric
   value badge). The signup meta row keeps only its own box-metric override below. */

/* Role name row — a flex row holding the name text plus the decorations that stay on
   the name line (the availability pill and the coordinator approve-chip). The fixed-width
   grid track is __main, not this row; because the name text is the only shrinkable child
   here, it ellipsizes while every decoration stays full-size (flex:0 0 auto). That's what
   lets a pending-approval chip appear without bumping the property column — the name gives
   up width, not the row. */
.assignments-role-row__name {
    display: flex;
    align-items: center;
    min-width: 0;
    max-width: 100%;
    text-align: left;
}

/* The role name itself — matched to the legacy .role-name. The lone shrinkable child; truncates
   with an ellipsis when name + decorations exceed the fixed name column. */
.assignments-role-row__name-text {
    font-size: var(--font-size-h4);
    font-weight: var(--font-weight-heading);
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 0 1 auto;
    min-width: 0;
}

/* Name-line chrome never shrinks — it keeps its size and pushes the name text to truncate.
   (Pay, location and the scope-of-work pill live on the meta line now; only the
   availability pill still sits on the name line.) */
.assignments-role-row__name > .assignments-role-row__available-pill {
    flex: 0 0 auto;
}

/* The approve-chip MAY shrink — 3× faster than the role name (flex-shrink 3 vs the name
   text's 1) — so a long signer name gives way before the role name does. It keeps its avatar
   and ellipsizes only the name label. This is the #2907 lesson: with the chip pinned at full
   size, a fixed name track truncated the *role* name to nothing on approval rows; letting the
   chip absorb most of the overflow keeps the role name readable while alignment still holds. */
.assignments-role-row__name > .assignments-role-row__approve-chip {
    flex: 0 3 auto;
    min-width: 0;
}
/* The chip's inner __current container already permits shrink-below-content
   (components/person-chip.css), so the name label ellipsizes here too. */

/* Location tag — the city/Remote tag on the meta line. Wears the outline-chip
   recipe (hairline --color-border, transparent fill, muted ink — the adoption
   sweep the styleguide names for this class) so it reads as the same family as
   the outlined View Full Details button and the pay pill beside it, instead of
   a borderless smudge between two bordered boxes.

   Ink is --color-text-muted-cool, not --color-text-muted: at this size the
   lighter muted grey measures 2.54:1 on white, so the city a worker travels to
   was the one thing on the row under the 4.5:1 floor.

   Corner is --radius-md because both neighbours on this line are: the pay pill
   and the View Full Details button. A capsule was tried here (Nick asked for
   the pay pill's roundness) and pulled straight back out — the pill only READS
   rounder because its corner is a filled gradient while this one is traced by a
   1px hairline, so a fully-rounded tag beside two 8px boxes was the odd shape
   on the row rather than the matching one. Match the neighbours, not the
   impression. */
.assignments-role-row__location-tag {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs) var(--spacing-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-text-muted-cool);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    vertical-align: middle;
    margin-left: var(--spacing-xsm);
    text-decoration: none;
    line-height: 1.2;
}

.assignments-role-row__location-tag .bi-geo-alt-fill {
    color: var(--color-error);
    font-size: var(--font-size-2xs);
}

/* Hover is scoped to the <a> renders (address present → maps link). The
   city-only <span> and the Remote tag are static labels and must not invite a
   click they can't honor.

   The hairline and ink wake to the brand color with the fill left transparent:
   a grey wash is the platform's pressed/disabled language, so filling the chip
   read as "this control is spent" rather than "this opens a map". Leaving the
   background alone also keeps the chip's weight steady beside the pay pill —
   the View on Map tooltip is what explains the target.

   The pin keeps its --color-error red through hover, per Nick: it is the row's
   one map marker, and a marker that changes color on approach stops reading as
   a marker. The rule above sets the icon's color explicitly, so the anchor's
   hover ink is never inherited by it — no hover declaration is needed to hold
   the red, and adding one back is what turned the pin brand. */
a.assignments-role-row__location-tag:hover {
    border-color: var(--brand-primary);
    text-decoration: none;
    color: var(--brand-primary);
}

/* Remote wears the same quiet chip as a city: where a role happens is
   metadata, not a status, so it takes no color of its own — the screen glyph
   is the distinction. */
.assignments-role-row__location-tag--virtual .bi-display {
    font-size: var(--font-size-2xs);
}

.assignments-role-row__map-pill {
    white-space: nowrap;
}

.assignments-role-row__map-pill .bi-geo-alt-fill {
    color: var(--color-error);
}

.assignments-role-row__map-pill:hover .bi-geo-alt-fill {
    color: var(--brand-primary);
}

/* ── Meta chip line ────────────────────────────────────────────────────────
   The second line of the main column: the View Full Details button leads,
   then the pay pill, then the location/Remote tag, on one flowing, wrapping
   line. Everything else that varies by role renders in the scrolling
   property card to the right. */
.assignments-role-row__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacing-xs) var(--spacing-xsm);
    min-width: 0;
    max-width: 100%;
    font-size: var(--font-size-md);
    line-height: 1.35;
    color: var(--color-text-muted);
}

/* The details button and the two meta chips share one height so their top and
   bottom edges line up; each keeps its own color, background, radius and
   shadow — only the box metrics unify here. */
.assignments-role-row__meta .global-value-pill,
.assignments-role-row__meta .assignments-role-row__location-tag,
.assignments-role-row__details-btn {
    display: inline-flex;
    align-items: center;
    margin-left: 0;
    height: 24px;
    padding: 0 var(--spacing-sm);
    font-size: var(--font-size-sm);
}

/* View Full Details — a second door into the terms modal Sign Up opens. A ghost
   outline in the brand color so it reads as the row's one obvious "read more"
   control without competing with the solid Sign Up button in the action column.

   Weight 400 next to the tag's 600 and the pay pill's 700 drew the one control
   on the line you are meant to click the most faintly of the three. */
.assignments-role-row__details-btn {
    border: 1px solid var(--brand-primary);
    border-radius: var(--radius-button);
    background: transparent;
    color: var(--brand-primary);
    font-weight: var(--font-weight-medium);
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
}

/* A tint, not the solid flip: filling this button made the row's smallest
   control its heaviest mark, for a door the whole card behind it already opens. */
.assignments-role-row__details-btn:hover {
    background: var(--brand-primary-alpha-10);
    color: var(--brand-primary);
}

/* ── Scrolling property card ───────────────────────────────────────────────
   The middle grid cell: one org-configurable list of {label, value} rows (system
   fields + custom props) that scrolls inside a fixed-height host. The cap is the
   whole point — the role row stays a constant height no matter how many properties
   a role template is configured to show; overflow scrolls, revealed by hover arrows.
   No icons: each row is a flat inline "Label: value" line. */
.assignments-role-row__card {
    position: relative;
    min-width: 0;
    /* Defensive: a grid child defaults to min-height:auto and would refuse to shrink
       below its content. Pinning it to 0 lets the inner scroll's max-height govern the
       cell height, so a long prop list can never stretch the row. */
    min-height: 0;
    /* Inline "Label: value" rows (~1.2rem each + 0.3rem gap). The default card's three
       fields fit here with no scroll; a longer org-configured list fades the 4th row out
       under the scroll mask, signalling "scroll for more". */
    --card-max-height: 4.4rem;
    /* Pull the property block in from the action button so the values don't crowd it. */
    margin-right: var(--spacing-smd);
}

/* When the viewer can still sign up, the whole card is a click target for the
   same terms modal as View Full Details. The cursor is the only affordance —
   the old hover ring + label underline read as noise and Nick killed them
   (08-17); the View Full Details button beside the card already names the
   click. */
.assignments-role-row__card--openable {
    cursor: pointer;
}

.assignments-role-row__card-scroll {
    display: flex;
    flex-direction: column;
    /* Left-justify each property row so the labels line up on a common left edge. */
    align-items: flex-start;
    /* Vertically center the rows in the fixed-height cell so a 1- or 2-property card lines
       up with the (vertically-centered) role name beside it; `safe` falls back to
       top-alignment when the content overflows so the first row is never scrolled out. */
    justify-content: safe center;
    gap: var(--spacing-xs);
    min-width: 0;
    /* FIXED height (not max-height): every role row is exactly --card-max-height tall no
       matter how many properties it carries. Overflow scrolls, revealed by the hover arrows. */
    height: var(--card-max-height);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Arrows are the affordance — hide the native scrollbar to keep the cell clean. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* Per-edge fade depth, raised by the --fade-* classes (bound to the same
       Alpine state as the arrows). Kept at 0 here so a fully-scrolled edge
       renders crisp to the card boundary. */
    --card-fade-top: 0rem;
    --card-fade-bottom: 0rem;
}

/* Overflow fade — a mask on the scroll cell, not a wash painted over it. The
   old white scrim sat on top of clipped text, so however eased its ramp was,
   its outer edge always met full-strength letters in a visible line (Nick,
   08-17, twice). Masking makes the text itself dissolve into the card surface:
   there is no painted layer to end anywhere. The stops approximate a
   smoothstep so the alpha ramp arrives at full strength with a flat tangent.
   The -webkit- copy below is the same ramp verbatim (Safari before 15.4 knows
   only the prefixed property) — edit the two together or the mask layers
   drift. */
.assignments-role-row__card-scroll--fade-top,
.assignments-role-row__card-scroll--fade-bottom {
    -webkit-mask-image: linear-gradient(
        to bottom,
        transparent,
        rgba(0, 0, 0, 0.3) calc(var(--card-fade-top) * 0.35),
        rgba(0, 0, 0, 0.75) calc(var(--card-fade-top) * 0.7),
        rgba(0, 0, 0, 0.95) calc(var(--card-fade-top) * 0.88),
        black var(--card-fade-top),
        black calc(100% - var(--card-fade-bottom)),
        rgba(0, 0, 0, 0.95) calc(100% - var(--card-fade-bottom) * 0.88),
        rgba(0, 0, 0, 0.75) calc(100% - var(--card-fade-bottom) * 0.7),
        rgba(0, 0, 0, 0.3) calc(100% - var(--card-fade-bottom) * 0.35),
        transparent
    );
    mask-image: linear-gradient(
        to bottom,
        transparent,
        rgba(0, 0, 0, 0.3) calc(var(--card-fade-top) * 0.35),
        rgba(0, 0, 0, 0.75) calc(var(--card-fade-top) * 0.7),
        rgba(0, 0, 0, 0.95) calc(var(--card-fade-top) * 0.88),
        black var(--card-fade-top),
        black calc(100% - var(--card-fade-bottom)),
        rgba(0, 0, 0, 0.95) calc(100% - var(--card-fade-bottom) * 0.88),
        rgba(0, 0, 0, 0.75) calc(100% - var(--card-fade-bottom) * 0.7),
        rgba(0, 0, 0, 0.3) calc(100% - var(--card-fade-bottom) * 0.35),
        transparent
    );
}

.assignments-role-row__card-scroll--fade-top {
    --card-fade-top: 0.75rem;
}

.assignments-role-row__card-scroll--fade-bottom {
    --card-fade-bottom: 0.75rem;
}

.assignments-role-row__card-scroll::-webkit-scrollbar {
    display: none;
}

/* One flat row: "Label: value" on a single line, label muted, value emphasized. */
.assignments-role-row__card-item {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    justify-content: flex-start;
    gap: var(--spacing-xsm);
    min-width: 0;
    max-width: 100%;
    text-align: left;
    line-height: 1.25;
}

.assignments-role-row__card-label {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    color: var(--color-grey-600);
    white-space: nowrap;
    flex: 0 0 auto;
}

/* The colon that turns "Deliverable" + "Highlight film" into one legible line. */
.assignments-role-row__card-label::after {
    content: ":";
}

.assignments-role-row__card-value {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    min-width: 0;
    /* Never clip a full value — wrap as a last resort instead of an ellipsis. */
    overflow-wrap: anywhere;
}

/* The scope brief is prose, not a short value: side-by-side it wrapped in a
   narrow column to the right of the label, which read badly (Nick, 08-17).
   Stack it — label on its own line, the brief full-width under it. */
.assignments-role-row__card-item--scope {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-2xs);
}

/* Hover-scroll chevrons — only shown (via x-show) when the rows overflow. The
   "scroll for more" affordance itself is the mask fade on the scroll cell; the
   chevron just names the direction. */
.assignments-role-row__card-arrow {
    position: absolute;
    left: 0;
    right: 0;
    /* A sliver: the text should run almost to the card's top/bottom edges, with
       just enough wash left for the chevron to sit on (Nick, 08-17 — "reaching
       closer to the edge of the top/bottom of the card"). */
    height: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    cursor: pointer;
    color: var(--brand-primary);
    /* Below the type scale on purpose — the chevron is an ornament, not text,
       and must fit inside the 0.5rem sliver without spilling onto the rows. */
    font-size: 0.5rem;
    opacity: 0.75;
    z-index: 2;
    transition: opacity var(--transition-fast);
}

.assignments-role-row__card-arrow:hover {
    opacity: 1;
}

/* No background: the scroll cell's mask (above) already dissolves the text
   under the sliver, so the chevron sits directly on clean card surface. Any
   painted wash here would reintroduce the cutoff line the mask removed. */
.assignments-role-row__card-arrow--up {
    top: 0;
    background: none;
}

.assignments-role-row__card-arrow--down {
    bottom: 0;
    background: none;
}

/* Extra left margin keeps the buttons off the card edge */
.assignments-role-row__action {
    margin-left: var(--spacing-smd);
}

/* Action buttons — matched to the legacy .btn-admin. The height comes from the
   min-height floor, not from an explicit line-height (the button never sets
   one). Button and status badge both read --signup-role-control-h, so a pill
   and a button stand the same height side by side and grow together as the
   fluid font scales up on wide screens. */
.assignments-role-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--signup-role-control-h, 2.55rem);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--radius-button);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-button);
    text-decoration: none;
    text-align: center;
    white-space: nowrap;
    cursor: default;
}

.assignments-role-btn--open {
    background: var(--color-card-bg);
    color: var(--brand-primary);
    /* Soft lift, not a glow. One small, low-opacity brand shadow; no white
       halo. Neutral fallback line first for browsers without color-mix() —
       literal, not var(--ink-alpha-15), for the reason the split-button pair
       below spells out. */
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
    box-shadow: 0 1px 4px color-mix(in srgb, var(--brand-primary) 18%, transparent);
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.assignments-role-btn--open:hover {
    transform: scale(1.05);
    /* Keep the slight grow on hover; deepen the shadow a little instead of spreading it wide. */
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--brand-primary) 28%, transparent);
    color: var(--brand-primary);
    text-decoration: none;
}

/* Yay button — same soft lift as assignments-role-btn--open, success tint instead of brand-primary */
.assignments-role-btn--yay {
    background: var(--color-card-bg);
    color: var(--color-success);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
    box-shadow: 0 1px 4px color-mix(in srgb, var(--color-success) 18%, transparent);
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.assignments-role-btn--yay:hover {
    transform: scale(1.05);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--color-success) 28%, transparent);
    color: var(--color-success);
}

.assignments-role-btn--nay {
    background: transparent;
    color: var(--color-error);
    border: 2px solid var(--color-error);
    cursor: pointer;
    transition: background 0.2s ease;
}

.assignments-role-btn--nay:hover {
    background: color-mix(in srgb, var(--color-error) 6%, transparent);
}

.assignments-role-row--locked {
    opacity: 0.45;
    pointer-events: none;
}

/* Skill gate lock button. flex-wrap serves the two-line level-shortfall
   label: the icon and "Needs Established" share the first flex line, and
   __line2's full basis pushes the skill name onto its own centered row
   (justify-content is already center on .assignments-role-btn). One-line labels — a
   missing skill, the bare "Locked", the approve row, and the coordinator
   global-split-button menu item — have no __line2 and keep their single line.
   __line2 is also display: block for the ≤480px rule below that turns
   .assignments-role-btn into a plain block: flex-basis means nothing there, and a block
   child is what still forces the break under .assignments-role-btn's nowrap. */
.assignments-role-btn--locked {
    background: transparent;
    color: var(--color-text-muted);
    border: 2px dashed var(--color-border);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-button);
    cursor: not-allowed;
    flex-wrap: wrap;
}

.assignments-role-btn--locked .bi-lock-fill { margin-right: var(--spacing-xs); }

.assignments-role-btn--locked .assignments-role-btn__line2 {
    display: block;
    flex-basis: 100%;
}

/* Signup role badge — LAYOUT ONLY. The pill itself is the shared
   .global-badge (components/badge.css); the old .badge-role clone and its
   hand-typed amber/green/red hex pairs are gone, and the state colour now
   comes from the shared tone modifiers (--warning / --success / --neutral)
   like every other status pill on the platform. Declined/Denied are --neutral,
   not --danger: badge.css reserves danger for genuine failures and puts
   declines and cancellations in the neutral group.
   This modifier keeps just the two things the signup row genuinely needs:
   it reads the shared --signup-role-control-h min-height so a status pill
   stands as tall as the button beside it at every viewport, and it drops the
   uppercase treatment because these labels are human sentences ("Waiting for
   Approval"), not one-word states.
   Doubled class on purpose: this file loads BEFORE components/badge.css in
   base.html, so a single-class .global-badge--role ties the .global-badge base
   on specificity and loses on source order — the base would win back uppercase,
   letter-spacing, line-height, padding and the pill radius. .global-badge
   .global-badge--role is (0,2,0) and beats the base regardless of load order. */
.global-badge.global-badge--role {
    justify-content: center;
    min-height: var(--signup-role-control-h, 2.55rem);
    line-height: 1.1;
    text-align: center;
    text-transform: none;
    letter-spacing: normal;
    padding: var(--spacing-xsm) var(--spacing-smd);
    border-radius: var(--radius-lg);
    cursor: default;
}

/* Two-step Cancel confirm (dashboard). confirm-action.js adds .is-confirming on
   the first click; the button turns a solid red "Confirm Cancel" so the armed
   state is unmistakable. It reverts only on outside click / Escape, never on a
   silent timer. Scoped to .assignments-role-btn so it never touches other confirm buttons. */
.assignments-role-btn.is-confirming {
    background: var(--color-error);
    color: #fff;
    border-color: var(--color-error);
}

/* Waiting-for-Approval pill and its Cancel button in one horizontal row,
   vertically centered with a small gap. Replaces the old stacked column so the
   pill and the button read as a matched pair. */
.assignments-waiting-cancel {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
}

/* "N need review" count chip in the group header — same amber, sits after the
   title pill so a coordinator sees at a glance which events have work waiting. */
.assignments-group__review-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    flex-shrink: 0;
    margin-left: var(--spacing-sm);
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--radius-pill);
    background: var(--status-warning-bg);
    color: var(--status-warning-fg);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-heading);
    white-space: nowrap;
}

/* The muted "Invited · waiting Nd" pill on an invited (locked) row now renders
   as .global-badge--role.global-badge--neutral — no local rule needed. */

/* "Invited by" attribution — a quiet label + micro person chip naming the
   inviter. On the invited worker's row it sits on the name line next to the
   role name (mirroring where the invitee's chip sits for a coordinator); on a
   locked row it is the whole action column. */
.assignments-role-row__invited-by {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xsm);
    margin-left: var(--spacing-sm);
    vertical-align: middle;
}

.assignments-role-row__invited-by-label {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

/* Cancel-invite × on the invitee's chip (coordinator view). Quiet until
   hovered. confirm-action.js swaps the glyph for the data-confirm-text on the
   first click, so .is-confirming must restyle the grown text button as the
   armed state — the shared .assignments-role-btn.is-confirming rule can't reach this
   button because it isn't a .assignments-role-btn. */
.assignments-role-row__chip-cancel {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    margin-left: var(--spacing-2xs);
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    line-height: 1;
    cursor: pointer;
}

.assignments-role-row__chip-cancel:hover {
    background: var(--color-surface-hover);
    color: var(--color-error);
}

.assignments-role-row__chip-cancel.is-confirming {
    padding: 0 var(--spacing-sm);
    background: var(--color-error);
    color: #fff;
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

/* Availability row highlight (worker view, plugin-gated): a soft teal tint on
   rows the worker IS showing available for, plus an outlined "Available" pill.
   Teal (--color-availability), deliberately distinct from the success-green
   "Signed Up" pill. Keyed off role.availability_match (singular); absent = no
   highlight. */
.assignments-role-row--available {
    background: var(--color-availability-bg);
}

.assignments-role-row__available-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    margin-left: var(--spacing-sm);
    padding: var(--spacing-2xs) var(--spacing-sm);
    border: 1px solid var(--color-availability);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-availability);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
    vertical-align: middle;
}

/* Split-button: primary action + chevron menu of alternates.
   Sibling layout via inline-flex; menu is absolutely positioned
   below the row. Chevron only renders when 2+ actions exist. */
.global-split-button {
    position: relative;
    display: inline-flex;
    align-items: stretch;
    gap: var(--spacing-xsm);
}

/* Chevron is a square toggle styled like global-sidebar-org-toggle: a tinted square
   with a subtle border, hover deepens both, padding:0 so the inner SVG centers.
   align-self: stretch grows its height to the Sign Up button next to it, so
   their top and bottom edges always line up; the fixed width is set to about
   that same height so it reads as a square. Colors are literal black-alpha for
   the light card, not the sidebar's dark-surface tokens. */
.global-split-button__chevron {
    align-self: stretch;
    width: 2.5rem;
    padding: 0;
    background: var(--ink-alpha-05);
    border: 1px solid var(--ink-alpha-15);
    border-radius: var(--radius-button);
    cursor: pointer;
    color: var(--color-grey-600);
    transition: background-color 120ms ease-in-out, border-color 120ms ease-in-out, color 120ms ease-in-out;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.global-split-button__chevron:hover,
.global-split-button__chevron:focus {
    background: var(--ink-alpha-10);
    border-color: var(--ink-alpha-20);
    color: var(--color-text);
    outline: none;
}

/* Sized here (not inline) so the glyph is one source of truth, the same way
   global-sidebar-org-toggle-svg sizes the matching sidebar toggle. */
.global-split-button__chevron svg {
    width: 14px;
    height: 22px;
    opacity: 0.85;
}

.global-split-button__menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 20;
    background: var(--color-card-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-menu);
    padding: var(--spacing-xsm);
    min-width: 9rem;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
}

/* Each item mirrors the action button it switches to: full-width, bold, and
   tinted to the action's color (green = yes, red = no, brand = open). The base
   color is overridden by the --yay/--nay/--open modifiers below. */
.global-split-button__menu-item {
    display: block;
    width: 100%;
    background: transparent;
    border: none;
    text-align: left;
    padding: var(--spacing-sm) var(--spacing-smd);
    border-radius: var(--radius-md);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-button);
    color: var(--color-text);
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 120ms ease-in-out;
}

.global-split-button__menu-item:hover,
.global-split-button__menu-item:focus {
    background: var(--ink-alpha-05);
    outline: none;
}

.global-split-button__menu-item--yay {
    color: var(--color-success);
}

.global-split-button__menu-item--yay:hover,
.global-split-button__menu-item--yay:focus {
    background: var(--status-success-bg);
}

.global-split-button__menu-item--nay {
    color: var(--color-error);
}

.global-split-button__menu-item--nay:hover,
.global-split-button__menu-item--nay:focus {
    background: var(--status-danger-bg);
}

/* Brand-tinted; color-mix gives a 10%-brand hover where supported, with a
   neutral fallback for older engines (no brand color hardcoded). */
.global-split-button__menu-item--open {
    color: var(--brand-primary);
}

.global-split-button__menu-item--open:hover,
.global-split-button__menu-item--open:focus {
    /* Spelled out, not `var(--ink-alpha-05)`, though that token holds exactly
       this value. This is the fallback half of a pair, read by an engine that
       has already shown it cannot resolve the line below it; a literal asks
       nothing of the engine on the way to being painted. #6119 folded it into
       the token along with 30 genuine duplicates — the fold is right
       everywhere the value stands alone, and wrong here. */
    background: rgba(0, 0, 0, 0.05);
    background: color-mix(in srgb, var(--brand-primary) 10%, transparent);
}

/* The skill gate, shown to a coordinator who can staff the role but not take it.
   Muted like .assignments-role-btn--locked so it reads as the reason Sign Up is absent rather
   than as a third action. It stays clickable on purpose (ADR-043): the click is
   what raises the toast naming the missing skill. */
.global-split-button__menu-item--locked {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    cursor: not-allowed;
}

.global-split-button__menu-item--locked .bi-lock-fill { margin-right: var(--spacing-xs); }

.assignments-role-row__locked-info {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-xsm);
    flex-wrap: wrap;
}

/* Coordinator approve chip — sits inside .assignments-role-row__name immediately
   after the location pill. Single pending = static chip showing avatar+full name.
   Multi-pending = same chip wired as an Alpine.js dropdown that lets the
   coordinator pick which signup to approve; the visible Approve button in
   .assignments-role-row__action retargets to match. */
/* Layout hook only — the chip's skin (pill fill, 20px avatar, name, chevron)
   is the global-person-chip atom in components/person-chip.css. This rule keeps
   just the spacing that belongs to the signup role row. */
.assignments-role-row__approve-chip {
    margin-left: var(--spacing-sm);
}

/* Menu rows show a 24px face rather than the trigger's 20px.
   The panel ancestor is in the selector on purpose: this file loads BEFORE
   components/person-chip.css, so a two-class selector would tie with
   `.global-person-chip--dropdown .global-person-chip__avatar` (0,2,0) and
   lose on source order. Three classes (0,3,0) wins regardless of load order. */
.assignments-role-row__approve-panel .assignments-role-row__approve-panel-row .global-person-chip__avatar {
    width: 24px;
    height: 24px;
}

.assignments-role-row__approve-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 20;
    min-width: 220px;
    padding: var(--spacing-xs);
    border-radius: var(--radius-card);
    background: var(--color-card-bg);
    box-shadow: var(--shadow-menu);
    border: 1px solid var(--ink-alpha-05);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
}

.assignments-role-row__approve-panel-row {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-xsm) var(--spacing-sm);
    border-radius: var(--radius-sm);
    border: 0;
    background: transparent;
    color: var(--color-text);
    text-align: left;
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: background-color 100ms ease-in-out;
}

.assignments-role-row__approve-panel-row:hover {
    background: var(--ink-alpha-05);
}

.assignments-role-row__approve-panel-name {
    flex: 1;
    white-space: nowrap;
}

.assignments-role-row__approve-panel-check {
    color: var(--color-success);
    font-size: var(--font-size-lg);
}

@keyframes signup-success-flash {
    0%   { background-color: color-mix(in srgb, var(--color-success) 12%, var(--color-card-bg)); }
    100% { background-color: transparent; }
}
/* Assign panel header — uses org brand color instead of impersonate red.
   Override the inherited hardcoded `color: #fff` from .core-impersonate-modal-header
   so the text honors the per-color text override (Auto/Light/Dark). */
.assignments-assign-panel .core-impersonate-modal-header {
    background: var(--brand-primary);
    color: var(--brand-primary-fg);
}

/* A row's action slot once that worker already has an unanswered assignment
   here. Deliberately a <span>, not a disabled <button>: there is no action left
   on the row, and a button shape invites a click that could only be refused.
   Muted + dashed to match .assignments-role-btn--locked, the platform's other "this is why,
   not what next". Padded like the Bootstrap .btn-sm it replaces so rows do not
   shift. */
.assignments-assign-panel__pending-pill {
    display: inline-block;
    padding: var(--spacing-xs) var(--spacing-sm);
    line-height: 1.5;
    background: transparent;
    color: var(--color-text-muted);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-button);
}

/* Every other row while an unanswered invite holds the role: still a button,
   because the lock lifts (INVITE_LOCK_DURATION) and the row goes live again,
   but it must not read as one. A number here would rot the next time that
   constant moves — it already did once.
   Bootstrap's :disabled only drops opacity to .65, which on an
   outline button still comes out blue and clickable — this takes the pill's
   colours instead so the whole panel says one thing.

   The button is never `disabled` (a locked tap explains itself in a toast), so
   the lock is the aria-disabled attribute. Wins on load order, this file being
   the later one. That is a real dependency and not an
   assumption: tests/browser/test_invite_pill_swap_proof.py reads the painted
   colour off a locked button and compares it to the pill beside it. */
.assignments-assign-panel__invite-btn[aria-disabled="true"] {
    opacity: 1;
    background: transparent;
    color: var(--color-text-muted);
    border-color: var(--color-border);
}

@media (max-width: 768px) {
    /* Phone: every row and its Invite button is a 44px target, and the search
       box is 16px so iOS does not zoom into it. */
    .assignments-assign-panel__row,
    .assignments-assign-panel__invite-btn {
        min-height: var(--control-h-touch);
    }

    .assignments-assign-panel .global-filter-control {
        font-size: var(--font-size-base);
        min-height: var(--control-h-touch);
    }

    .assignments-groups {
        grid-template-columns: 1fr;
    }

    /* Mobile role rows: drop the fixed name track. The main block (name + meta) spans the
       full width on its own row; the property card and the action share the row below it. */
    .assignments-role-row {
        grid-template-columns: 1fr auto;
        gap: var(--spacing-xs) var(--spacing-sm);
        padding: var(--spacing-sm) var(--spacing-md);
    }

    .assignments-role-row__main {
        grid-column: 1 / -1;
    }

    /* Property card drops under the main block, in the left column; reset the desktop
       margin that pulled it off the action button (irrelevant when it's on its own row). */
    .assignments-role-row__card {
        grid-column: 1;
        margin-right: 0;
    }

    /* On narrow widths let a long role name wrap to a second line instead of
       ellipsizing against its decorations — there's no fixed name track, so wrapping is
       safe and keeps the whole name readable (mockup A6). */
    .assignments-role-row__name {
        flex-wrap: wrap;
    }
    .assignments-role-row__name-text {
        font-size: var(--font-size-h3);
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }

    .assignments-role-row__action {
        grid-column: 2;
        justify-self: end;
        align-self: center;
        margin-left: 0;
    }

    /* Let the header wrap on mobile so the event pill drops to its own right-aligned line
       (margin-left:auto pushes it to the right edge) instead of crowding pill + avatars. */
    .assignments-group__header {
        flex-wrap: wrap;
    }

    /* The header wraps here, so the pill has the first line to itself; a
       60% cap left it a stub ("Wedding · Full day …") beside empty space. */
    .assignments-group__pill {
        max-width: 100%;
    }

    /* Rows stack on a phone, so the desktop's constant row height buys
       nothing: a role with no properties showed 4.4rem of blank card. The
       cell sizes to its rows and the cap still scrolls a long list. */
    .assignments-role-row__card-scroll {
        height: auto;
        max-height: var(--card-max-height);
    }

    /* The pill stays compact and right-floated on its wrapped line — no full-width stretch
       (that was the stacked-column era; it's now an inline date·time chip). It gives up
       the desktop's flex-shrink: 0 here: a long date and time ran the pill off the
       right edge of a 393px screen, so it shrinks and wraps instead
       (test_phone_list_pages). */
    .assignments-group__event {
        margin-left: auto;
        flex-shrink: 1;
        flex-wrap: wrap;
        min-width: 0;
        max-width: 100%;
    }

    .assignments-group__event-date,
    .assignments-group__event-time {
        white-space: normal;
    }

    /* The row and its main column never grow past the card: the meta line's
       chips ran under the right edge when the grid sized to their content. */
    .assignments-role-row {
        min-width: 0;
        max-width: 100%;
    }

    .assignments-role-row__main {
        max-width: 100%;
    }

    .assignments-group__worker-avatar {
        margin-left: -8px;
    }

    .assignments-group__worker-avatar:first-child {
        margin-left: 0;
    }

    /* Height needs no override here: button and badge keep reading the shared
       --signup-role-control-h, which is already the tap-target rung. */
    .assignments-role-btn {
        padding: var(--spacing-smd) var(--spacing-mdlg);
        font-size: var(--font-size-lg);
    }
}

@media (max-width: 480px) {
    /* Very narrow: drop to a single column so main, card and action each stack full-width. */
    .assignments-role-row {
        grid-template-columns: 1fr;
    }
    /* Reset the two-column action placement from the 768px rule — in one column the action
       spans the full width below the property card. */
    .assignments-role-row__action {
        grid-column: 1 / -1;
        justify-self: stretch;
    }

    .assignments-role-btn {
        display: block;
        text-align: center;
    }

    .assignments-group__ref {
        display: none;
    }
}

/* Phone: the Invite panel is a bottom sheet like every drawer (drawer.css),
   not a full-screen modal with a blank lower half. It is not a
   .global-drawer-panel, so drawer.js never adds the --sheet class; the shape
   lives here on the panel's own class. Same handle as .global-drawer-head. */
@media (max-width: 767.98px) {
    .core-impersonate-modal.assignments-assign-panel,
    .core-impersonate-modal.assignments-assign-panel.is-open {
        /* Standing on the bottom bar (tokens.css --bottom-bar-h). */
        inset: auto 0 var(--bottom-bar-h) 0;
        width: 100%;
        max-width: none;
        height: auto;
        max-height: calc(100dvh - var(--header-bottom) - var(--bottom-bar-h));
        border-radius: var(--radius-lg) var(--radius-lg) var(--radius-none) var(--radius-none);
        transform: none;
    }

    .assignments-assign-panel .core-impersonate-modal-header {
        position: relative;
        padding-top: var(--spacing-mdlg);
    }

    .assignments-assign-panel .core-impersonate-modal-header::before {
        content: "";
        position: absolute;
        top: var(--spacing-sm);
        left: 50%;
        width: var(--sheet-handle-w);
        height: var(--sheet-handle-h);
        translate: -50%;
        border-radius: var(--radius-pill);
        background: var(--color-grey-300);
    }
}
