@charset "utf-8";
/* ==========================================================================
   bpr-primitives.css — cross-surface UI primitives (.bpr-*)

   One definition, loaded in every operational shell (content/base.html,
   work/_base.html, manage/base.html) alongside toasts.css. These are shared
   atoms that render identically for customer, writer, and admin; per-surface
   chrome is layered as thin skins in account.css / staff.css / admin.css only
   where a surface genuinely diverges.

   Governs the *atoms*, not the page-level systems — see docs/UI.md
   "Cross-surface primitives". The six page-level style systems
   (checkout / blog / KB / public / account / admin) stay separate.

   Consumes the brand design tokens (design-tokens.css, loaded in all shells).
   ========================================================================== */

/* ---------- Status pill ------------------------------------------------- */
/* Semantic variants (not status-keyed) so audience relabelling is free.
   The status -> variant mapping lives in work/status_display.py. */
.bpr-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.18rem 0.65rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.bpr-pill--neutral  { background: #F1F3F5;                color: #495057;              border-color: #DEE2E6; }
.bpr-pill--warning  { background: #FFF4D9;                color: #806308;              border-color: #F2D976; }
.bpr-pill--info     { background: #EEE9FB;                color: #4F3BA6;              border-color: #D6CBF1; }
.bpr-pill--progress { background: #E8F0FF;                color: #1F3F8A;              border-color: #BBD0F4; }
.bpr-pill--review   { background: #FBF6EE;                color: #B05D00;              border-color: #F0DCB8; }
.bpr-pill--success  { background: var(--brand-green-700); color: #fff;                border-color: var(--brand-green-700); }
/* done = a TERMINAL delivered state — deliberately quieter than the actionable
   washes (progress/review) and the solid --success, so a completed item recedes
   and in-flight work reads hotter on the scan. A calm sage tint with forest ink,
   NOT the vivid --color-success semantic. See UI.md § Delivered-pill demote. */
.bpr-pill--done     { background: #EAEEEA;                color: var(--brand-green-700); border-color: #D3DBD3; }
.bpr-pill--paid     { background: var(--brand-green-100); color: var(--brand-green-700); border-color: rgba(0, 141, 54, 0.18); }
.bpr-pill--danger   { background: #FBE9E8;                color: #8E2421;              border-color: #F2C7C3; }
/* Deadline-urgency taxonomy (status_display.urgency_band). slate = quiet/far
   out; warning = approaching (<=48h); warning-strong = imminent (<=24h, deeper
   amber than plain warning); danger = breached. See UI.md § Deadline urgency. */
.bpr-pill--slate          { background: #EEF1F4;                color: #51606E;              border-color: #D7DEE5; }
.bpr-pill--warning-strong { background: #FCE9C6;                color: #8A4B00;              border-color: #EFC178; }

/* ---------- Deadline pill ---------------------------------------------- */
/* The ONE cross-surface deadline chip (customer / writer / admin), rendered by
   the {% deadline_pill %} inclusion tag from a computed delivery/due datetime.
   Two states only (deadline-pill-unification): amber = the default, red = the
   deadline is within 2 days (<=48h) or overdue. Colour resolved in Python
   (status_display.deadline_pill); this is the visual. Mixed-case + a clock icon
   (NOT the uppercase status .bpr-pill — a date must not shout); reuses the
   amber/red wash of .bpr-pill--warning / --danger. Replaces the retired
   .acc-deadline-pill and .stf-due--* families. See UI.md § Deadline pill. */
.bpr-deadline-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.18rem 0.6rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}
.bpr-deadline-pill > i { font-size: 0.6875rem; }
.bpr-deadline-pill--amber { background: #FFF4D9; color: #806308; border-color: #F2D976; }
.bpr-deadline-pill--red   { background: #FBE9E8; color: #8E2421; border-color: #F2C7C3; }
/* Mobile: let the full "<label> <date> · in <rel>" string wrap rather than clip
   a narrow card (mirrors the old .stf-due mobile rule). */
@media (max-width: 768px) {
  .bpr-deadline-pill { white-space: normal; max-width: 100%; }
}

/* ---------- Breadcrumbs ------------------------------------------------- */
.bpr-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-body);
  font-size: 0.875rem;
}
.bpr-crumbs__link { color: var(--brand-charcoal-500); text-decoration: none; }
a.bpr-crumbs__link:hover { color: var(--brand-green-700); text-decoration: underline; }
.bpr-crumbs__sep { font-size: 0.625rem; color: var(--brand-paper-200); }
.bpr-crumbs__current { color: var(--brand-ink-900); font-weight: 600; }
/* Skins: account pages give the trail breathing room; admin sits in the header. */
.bpr-crumbs--acc { margin-bottom: var(--sp-5); }
.bpr-crumbs--adm { min-width: 0; }

/* ---------- Modal ------------------------------------------------------- */
/* One canonical modal look across every operational shell. `.adm-modal` and
   `.stf-modal` are retained as aliases so existing markup + E2E selectors keep
   working; new code uses `.bpr-modal`. The old per-shell rule blocks (which had
   drifted on backdrop colour, radius, borders, z-index) are removed. */
.bpr-modal-backdrop, .adm-modal-backdrop, .stf-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  z-index: 1050;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.bpr-modal, .adm-modal, .stf-modal {
  background: #fff;
  border-radius: var(--radius-md);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
  max-width: 460px;
  width: 100%;
  overflow: hidden;
  /* Anchor for the absolutely-positioned × close button. */
  position: relative;
  /* The modal is a fixed overlay but stays a DOM descendant of its trigger, so
     it must reset any inherited text-layout property from that trigger's context:
     `white-space` (a confirm whose trigger sits in a `white-space:nowrap` cell
     would render the body on one clipped line) and `text-align` (a trigger in a
     right-aligned actions cell would render the body copy ragged-left). Reset
     both so the modal reads the same wherever it's anchored. */
  white-space: normal;
  text-align: left;
}
/* Explicit × close affordance — every branded modal carries one (pinned by
   ModalCloseAffordanceSweepTests). Absolutely positioned so it works with
   both the div-head (.stf-modal__head) and header-element (.adm-modal__head)
   variants without reflowing them. Escape / outside-click / Cancel remain. */
.bpr-modal__close {
  position: absolute;
  top: 0.55rem;
  right: 0.6rem;
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font-size: 1.35rem;
  line-height: 1;
  color: var(--brand-charcoal-500);
  cursor: pointer;
}
.bpr-modal__close:hover,
.bpr-modal__close:focus {
  color: var(--brand-ink-900);
  background: var(--brand-paper-100);
}
.bpr-modal__head, .adm-modal__head, .stf-modal__head {
  /* Right padding clears the absolutely-positioned × close button. */
  padding: 1rem 2.75rem 1rem 1.25rem;
  border-bottom: 1px solid var(--brand-paper-200);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 1rem;
  color: var(--brand-ink-900);
}
.bpr-modal__body, .adm-modal__body, .stf-modal__body {
  padding: 1.25rem;
  color: var(--brand-charcoal-700);
}
.bpr-modal__foot, .adm-modal__foot, .stf-modal__foot {
  padding: 0.75rem 1.25rem;
  border-top: 1px solid var(--brand-paper-200);
  background: var(--brand-paper-50);
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}
.bpr-modal--form, .stf-modal--form { max-width: 540px; }
.bpr-modal__body--scroll, .stf-modal__body--scroll { max-height: 70vh; overflow-y: auto; }

/* ---------- Card (base) ------------------------------------------------- */
/* The account + writer/staff card base is one identical rule — unify it.
   `.acc-card` / `.stf-card` are retained as aliases; their per-surface
   sub-elements (__head/__title/__chevron) and modifiers stay in their own
   stylesheets. The admin `.adm-card` is a deliberately different *sectioned*
   pattern (head/body/foot, no card padding) and keeps its own base. */
.bpr-card, .acc-card, .stf-card {
  background: #fff;
  border: 1px solid var(--brand-paper-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-1);
  padding: var(--sp-5);
  margin-bottom: var(--sp-4);
}

/* ---------- Avatar + person chip ---------------------------------------- */
/* One person-identity atom for every surface (admin header, meganav, rosters,
   work cards, order detail, applicant detail, blog author bio). Photo → initials
   → icon; rendered only via {% avatar %} / {% person_chip %} (users app) so the
   markup never drifts. Forest circle to match the two former one-off avatars. */
.bpr-avatar {
  --bpr-avatar-size: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--bpr-avatar-size);
  height: var(--bpr-avatar-size);
  border-radius: 999px;
  object-fit: cover;
  background: var(--brand-green-700);
  color: #fff;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: calc(var(--bpr-avatar-size) * 0.4);
  line-height: 1;
  text-transform: uppercase;
  overflow: hidden;
  user-select: none;
}
.bpr-avatar--sm { --bpr-avatar-size: 1.75rem; }
.bpr-avatar--md { --bpr-avatar-size: 2.25rem; }
.bpr-avatar--lg { --bpr-avatar-size: 3rem; }
.bpr-avatar--xl { --bpr-avatar-size: 4.5rem; }
.bpr-avatar--icon { color: rgba(255, 255, 255, 0.92); }

.bpr-person {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}
.bpr-person__body { display: flex; flex-direction: column; min-width: 0; }
.bpr-person__name {
  font-weight: 600;
  color: var(--brand-charcoal-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bpr-person__email {
  font-size: 0.75rem;
  color: var(--brand-charcoal-500, #6b6b6b);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Tooltip ------------------------------------------------------ */
/* CSS-only (i) tooltip, ported from checkout's .chk-tip (the house tooltip
   convention — see docs/UI.md "Tooltips"). A relatively-positioned wrapper is
   the hover/focus target; the bubble is an absolutely-positioned
   role="tooltip" span revealed on :hover / :focus-within (keyboard users get
   it too). Markup contract:

     <span class="bpr-tip" tabindex="0" aria-describedby="tip-id">
       <i class="fas fa-info-circle bpr-tip__icon" aria-hidden="true"></i>
       <span id="tip-id" role="tooltip" class="bpr-tip__bubble">Copy.</span>
     </span>

   Dark ink bubble + CSS caret, opens upward; add .bpr-tip__bubble--down when
   the trigger sits too close to the top of its container. Triggers use
   cursor: pointer (never cursor: help — house convention). */
.bpr-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

.bpr-tip__icon {
  font-size: 0.8125rem;
  color: var(--brand-charcoal-500, #6b6b6b);
  transition: color 120ms ease;
}

.bpr-tip:hover .bpr-tip__icon,
.bpr-tip:focus-within .bpr-tip__icon {
  color: var(--brand-green-700);
}

.bpr-tip__bubble {
  position: absolute;
  bottom: calc(100% + var(--sp-2));
  left: 50%;
  /* Translate split into per-axis vars so the JS edge-detect flip (tip-flip.js)
     can re-anchor one axis without clobbering the other: --down flips Y,
     --align-start/--align-end flip X. */
  --tip-tx: -50%;
  --tip-ty: 4px;
  transform: translateX(var(--tip-tx)) translateY(var(--tip-ty));
  z-index: 30;
  width: max-content;
  max-width: 18rem;
  padding: var(--sp-2) var(--sp-3);
  background: var(--brand-ink-900);
  color: #fff;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease, visibility 120ms ease;
}

.bpr-tip__bubble::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--brand-ink-900);
}

.bpr-tip:hover > .bpr-tip__bubble,
.bpr-tip:focus-within > .bpr-tip__bubble {
  opacity: 1;
  visibility: visible;
  --tip-ty: 0;
}

/* Downward variant — for triggers near the top edge of a card/container.
   CSS-only fallback; also toggled by tip-flip.js when a bubble would clip the
   top of the viewport. (Hover clears --tip-ty via the general rule above.) */
.bpr-tip__bubble--down {
  bottom: auto;
  top: calc(100% + var(--sp-2));
  --tip-ty: -4px;
}

.bpr-tip__bubble--down::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--brand-ink-900);
}

/* Horizontal re-anchor variants (JS edge-detect flip, cluster 5). When a
   near-edge trigger would push the centred bubble past the left/right viewport
   edge, tip-flip.js anchors the bubble to the trigger side instead and moves
   the caret to match. */
.bpr-tip__bubble--align-start {
  left: 0;
  right: auto;
  --tip-tx: 0;
}
.bpr-tip__bubble--align-start::after {
  left: var(--sp-4);
}
.bpr-tip__bubble--align-end {
  left: auto;
  right: 0;
  --tip-tx: 0;
}
.bpr-tip__bubble--align-end::after {
  left: auto;
  right: var(--sp-4);
}

/* Narrow-viewport containment (Stage 1 quick-fix, cluster 5). The bubble is
   centred on its trigger (left: 50%; translateX(-50%)) and stays in layout
   while hidden, so a trigger right-of-centre pushed the 18rem bubble past the
   viewport edge — 24px of horizontal PAGE overflow at 390px on /work/claim/.
   Cap the bubble width on small screens (and never exceed the viewport) so a
   near-edge bubble stays contained. This is deliberately the QUICK fix; the
   full JS edge-detect + flip positioning utility is Stage 3. */
@media (max-width: 600px) {
  .bpr-tip__bubble {
    /* border-box so the cap includes padding (predictable total width). */
    box-sizing: border-box;
    max-width: min(12rem, calc(100vw - 2rem));
    /* Let long tokens wrap so the width cap actually binds (a long unbreakable
       word otherwise sets min-content wider than max-width and still spills). */
    overflow-wrap: anywhere;
  }
}
