/* ============================================================
   YBC Proofing — built on the YBC Scottsdale design system.

   This is an internal tool, so it is PORTAL territory: token values and
   component classes follow tokens/portal.css and components/portal.css from
   the design system, and where the product diverges from the brand guide
   (12px card radius, the 0.98 press, the card lift) the product wins.

   Outfit is self-hosted rather than pulled from Google Fonts. It is
   OFL-licensed so the binaries may be redistributed, and a login-gated
   internal tool should not make a third-party request on every page load.
   Only the four weights this app uses are bundled (400/500/600/700); the
   design system's full range is 300-800.
   ============================================================ */

@font-face {
  font-family: "Outfit";
  src: url("/static/fonts/Outfit-Regular.ttf") format("truetype");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Outfit";
  src: url("/static/fonts/Outfit-Medium.ttf") format("truetype");
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: "Outfit";
  src: url("/static/fonts/Outfit-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: "Outfit";
  src: url("/static/fonts/Outfit-Bold.ttf") format("truetype");
  font-weight: 700;
  font-display: swap;
}

/* ---------- Tokens: Sage & Linen (tokens/colors.css) ---------- */
:root {
  /* This app has exactly one theme and it is light. Declaring it opts out of
     Chrome's automatic dark theme, which would invert the status pills --
     keeping them distinguishable while making each one say the wrong thing. */
  color-scheme: only light;

  --ybc-sage: #384E41;
  --ybc-sage-deep: #23392C;
  --ybc-sage-ink: #081D12;
  --ybc-sage-mist: #98A69E;
  --ybc-sage-whisper: #EAEEEC;

  --ybc-linen: #F1F3F1;
  --ybc-linen-shade: #E6E8E6;
  --ybc-linen-darker: #D6D8D6;
  --ybc-hairline: #E1E6E2;
  --ybc-stone: #6A6F6A;
  --ybc-black: #000000;
  --ybc-white: #FFFFFF;

  /* Portal status tones (tokens/portal.css). Not decoration -- colour is how
     a row says where it is. `quiet` is a warm taupe on purpose: under a
     desaturated sage, a cool stone and the green read as near-identical. */
  --status-alert: #8C2F2F;
  --status-warn: #8A5A15;
  --status-quiet: #787066;

  /* Type (tokens/typography.css) */
  --font-core: "Outfit", "Gotham", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-reference: ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", Courier, monospace;

  --size-heading-lg: 28px;
  --size-heading-md: 22px;
  --size-heading-sm: 18px;
  --size-body: 15px;
  --size-body-sm: 13px;
  --size-label: 12px;
  --size-caps: 11px;

  --leading-body: 1.6;
  --leading-tight: 1.35;
  --tracking-caps: 0.1em;

  /* Shape. 12px on cards -- the product runs one step softer than the brand
     guide's 10px surface radius. */
  --radius-control: 6px;
  --radius-card: 12px;

  /* Spacing scale. There was a type scale and a radius scale and no scale for
     space, so spacing was set inline at the point of use -- the same element
     in the same situation carried 8px, 10px, 12px and 16px in different
     templates, and nothing said which was right. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;

  /* The page column. Every band that has to line up with it reads these rather
     than repeating the numbers, which is what let the header drift. */
  --content-max: 1040px;
  --content-gutter: 32px;

  /* How wide a line of prose is allowed to get. Comfortable reading is 45-75
     characters; measured, this page's subtext ran to 131 and its hints to 90.
     29rem holds about 73 characters at 13px.

     Deliberately not in `ch`. That reads as the honest unit for a measure and
     it is a trap: `ch` is the width of a zero in the *element's own* font, so
     one token means different widths on a 13px hint and a 15px subtext -- and a
     card sized to calc(measure + padding) came out 114px wider than the
     paragraph inside it instead of 42. A length that means one length. */
  --measure: 29rem;
  --shadow-card: 0 1px 3px rgba(11, 32, 21, 0.06), 0 8px 24px -12px rgba(11, 32, 21, 0.10);

  /* Motion: calm and short. No bounce, no overshoot, no spring. */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur-control: 140ms;

  /* Focus is never removed: a 2px Sage ring offset by a 2px white gap. */
  --ring-focus: 0 0 0 2px var(--ybc-white), 0 0 0 4px var(--ybc-sage);

  /* Destructive. The one place the app reaches past its own tokens --
     the palette has no red. Flagged in the design system, not endorsed. */
  --danger: #DC2626;
  /* The same red one step darker, for danger *text* on a light ground.
     --danger is sized for a fill with white on it; as 15px type on the danger
     card it measured 4.41:1, just under the 4.5 that small text needs. */
  --danger-ink: #B91C1C;
  --danger-bg: #FEF2F2;
  --danger-border: #FECACA;
}

/* ---------- Base ---------- */
* { box-sizing: border-box; }

/* A class that sets `display` (e.g. .btn) otherwise beats the browser's
   default [hidden] rule, leaving hidden elements visible. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: var(--font-core);
  font-size: var(--size-body);
  line-height: var(--leading-body);
  color: var(--ybc-black);
  background: var(--ybc-white);
  -webkit-font-smoothing: antialiased;
}

/* Outfit has no italic at any weight, and a synthesised slant looks wrong.
   Emphasis comes from weight instead. */
em, i { font-style: normal; font-weight: 500; }

:focus-visible { outline: none; box-shadow: var(--ring-focus); border-radius: 3px; }

a { color: var(--ybc-sage); }

/* Reference values -- filenames, hex builds, version codes. Monospace, never
   Outfit: in a geometric sans 0/O are both plain circles and 1/I/l are
   near-identical verticals, and these get read off a proof and typed back in. */
.ybc-reference, .mono {
  font-family: var(--font-reference);
  letter-spacing: 0.02em;
}

/* ---------- Shell ---------- */
/* The band runs edge to edge; what is in it lines up with the page. Those are
   two different jobs and they were one rule, so the header was inset 32px from
   the viewport while .content sits in a centred 1040px column -- measured at
   1421px wide, the mark was 191px to the left of the page heading under it and
   the nav 190px to the right of the content's edge. They only agreed below
   about 1100px, which is where the column stops growing. */
.site-header { background: var(--ybc-sage); }
.header-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 var(--content-gutter);
  height: 64px;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.brand { display: flex; align-items: center; gap: 14px; text-decoration: none; }
/* NavBar placement: 32px tall, set by the bar height rather than the mark scale. */
.brand-logo { height: 32px; display: block; }
.brand-sub {
  color: rgba(255, 255, 255, 0.72);
  font-size: var(--size-body-sm);
  font-weight: 500;
}

.content {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 40px var(--content-gutter) 96px;
}

.site-footer {
  border-top: 1px solid var(--ybc-hairline);
  padding: 24px var(--content-gutter);
  color: var(--ybc-stone);
  font-size: var(--size-body-sm);
  text-align: center;
}

/* ---------- Type ---------- */
/* ALL CAPS only for short labels, and always letterspaced -- Outfit's caps
   set tight. Sentence case everywhere else, including buttons and headings. */
.eyebrow {
  font-size: var(--size-caps);
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ybc-stone);
  margin: 0 0 var(--space-2);
}
.eyebrow a { color: var(--ybc-stone); text-decoration: none; }
.eyebrow a:hover { color: var(--ybc-sage); }

/* A breadcrumb states its own spacing rather than borrowing a word space.
   Tracked caps make a word space useless as a separator: at 11px and 0.1em the
   space came to 3.25px while the tracked letters inside each crumb sat about
   1.1px apart, so the separator was barely more separated than the letters
   beside it and the trail read as one run. The gap is on the flex container, so
   it is identical either side of every separator and survives being copied. */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
}
/* Punctuation, not content: the crumbs are 600 and this is not, which is what
   keeps a separator from reading as another step in the trail. */
.crumb-sep { font-weight: 400; opacity: 0.55; }

/* A filename in a trail keeps its own case and takes the monospace reference
   rule, like every other filename in the app. The trail is uppercase caps, and
   uppercasing a filename does not just restyle it -- it rewrites it, and this
   is a string somebody reads off the screen and types back in to find the
   file. Sized down a step so it still sits inside the trail. */
.crumb-file {
  font-family: var(--font-reference);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}

h1 { font-size: var(--size-heading-lg); font-weight: 700; letter-spacing: -0.01em; margin: 0 0 6px; line-height: 1.2; }
/* A filename as the page title still takes the monospace reference rule, but
   one size down: mono at 28px sets much heavier than Outfit does, and the
   proof PDF puts the client name at display size with the filename quieter
   beneath it. The screen should not invert that hierarchy. */
h1.ybc-reference { font-size: var(--size-heading-md); font-weight: 600; }
h2 { font-size: var(--size-heading-sm); font-weight: 600; margin: 40px 0 14px; letter-spacing: -0.01em; }
.subtext { color: var(--ybc-stone); margin: 0; max-width: var(--measure); }
.hint { color: var(--ybc-stone); font-size: var(--size-body-sm); margin: var(--space-2) 0 0; max-width: var(--measure); }
/* Three things a hint does, named rather than set inline each time it does
   them. Anything not one of these three keeps the default above. */
/* Keeps .hint's own top margin -- the inline rule this replaces only ever set
   the bottom, so zeroing the top here silently pulled every intro hint 8px
   closer to its heading. */
.hint-intro { margin: var(--space-2) 0 var(--space-3); }  /* under a heading, introducing what follows */
.hint-flush { margin: 0; }                    /* the only text in its panel */
.hint-apart { margin-top: var(--space-4); }   /* set apart from the block above it */
.muted { color: var(--ybc-stone); }

.page-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; margin-bottom: 8px; }
.page-head-actions { display: flex; gap: 10px; align-items: center; }
/* An action that has to be a POST -- sending a proof, signing out -- is a form
   wrapping one button, and the row should not be able to tell. */
.page-head-actions form { margin: 0; display: flex; }

/* ---------- Cards ----------
   A card is white on a white page: the hairline border defines it, the
   shadow is only a lift. */
.form-card, .card {
  background: var(--ybc-white);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  margin: 24px 0;
  overflow: hidden;
}
/* The card's own heading becomes a flat Sage band, edge to edge, square
   inside the rounded card. */
.form-card > h2:first-child, .card > h2:first-child {
  background: var(--ybc-sage);
  color: var(--ybc-white);
  font-size: var(--size-label);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  padding: 10px 20px;
  margin: 0;
}
.form-card > *:not(h2:first-child) { margin-left: 20px; margin-right: 20px; }
.form-card > *:last-child { margin-bottom: 20px; }
/* And a top edge. The rules above gave a card's children left, right and
   bottom margins and no top one, so whatever came first sat 1px from the
   border -- measured on the client's disclaimer, whose text touched the box --
   and a grid following the heading bar pressed straight against it. The bar
   itself stays flush: it is full-bleed, which is why it is excluded above. */
.form-card > *:first-child:not(h2) { margin-top: 20px; }
.form-card > h2:first-child + * { margin-top: 16px; }
.form-card > h2:not(:first-child) {
  margin-top: 28px;
  font-size: var(--size-caps);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ybc-stone);
}
.form-card > form { margin: 20px; }
.form-card > form > *:last-child { margin-bottom: 0; }

/* ---------- Tables ---------- */
.data-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.data-table th {
  text-align: left;
  font-size: var(--size-caps);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ybc-stone);
  border-bottom: 1px solid var(--ybc-hairline);
  padding: 10px 12px;
}
/* Text cells are top-aligned so primary lines read as one line across the
   row; control cells are middle-aligned, because a button has no baseline
   to share. */
.data-table td {
  padding: 14px 12px;
  border-bottom: 1px solid var(--ybc-hairline);
  font-size: var(--size-body-sm);
  vertical-align: top;
}
.data-table td.align-right { vertical-align: middle; }
.data-table tbody tr:hover { background: rgba(241, 243, 241, 0.55); }
.data-table a { text-decoration: none; font-weight: 600; }
.align-right { text-align: right; }

/* An inline link that carries a Lucide chevron. Never a unicode arrow:
   Outfit has no arrow glyph, so it falls back to whatever the OS supplies
   and renders at the wrong weight. */
.row-link { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.row-link svg { width: 14px; height: 14px; }

.empty-state {
  padding: 48px;
  text-align: center;
  color: var(--ybc-stone);
  border: 1px dashed var(--ybc-hairline);
  border-radius: var(--radius-card);
  background: var(--ybc-linen);
}
.empty-state p { margin: 0 0 16px; max-width: var(--measure); }
.empty-state p:only-child { margin: 0; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* A button label is a name for an action, not a paragraph. Without this
     "New project" broke across two lines the moment the heading beside it
     grew long enough to squeeze the flex row. */
  white-space: nowrap;
  font-family: inherit;
  font-size: 14px;
  border-radius: var(--radius-control);
  border: 0;
  cursor: pointer;
  text-decoration: none;
  transition: background-color var(--dur-control) var(--ease),
              transform var(--dur-control) var(--ease);
}
.btn-primary {
  background: var(--ybc-sage);
  color: var(--ybc-white);
  font-weight: 700;
  letter-spacing: 0.025em;
  padding: 12px 24px;
}
/* Hover darkens rather than lightens. */
.btn-primary:hover:not(:disabled) { background: var(--ybc-sage-deep); }
/* The portal's one scale press -- 0.98, no bounce. */
.btn-primary:active:not(:disabled) { transform: scale(0.98); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }

.btn-secondary {
  background: var(--ybc-linen);
  color: var(--ybc-sage-ink);
  font-weight: 600;
  padding: 10px 20px;
}
.btn-secondary:hover:not(:disabled) { background: var(--ybc-linen-shade); }
.btn-secondary:disabled { opacity: 0.4; cursor: not-allowed; }

.btn-danger {
  background: var(--danger);
  color: var(--ybc-white);
  font-weight: 600;
  padding: 10px 20px;
}
.btn-danger:hover { background: #B91C1C; }

/* ---------- Forms ----------
   Fields are Linen-filled with a transparent 2px border that turns Sage on
   focus, so the field never changes size when it lights up. */
label { display: block; font-size: var(--size-body-sm); font-weight: 600; margin-bottom: 16px; }
.optional-tag {
  font-weight: 500;
  color: var(--ybc-stone);
  font-size: var(--size-caps);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  margin-left: 6px;
}

input[type="text"], input[type="number"], input[type="date"],
input[type="email"], input[type="password"], select, textarea {
  width: 100%;
  margin-top: 6px;
  background: var(--ybc-linen);
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  padding: 10px 12px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--ybc-black);
  transition: border-color var(--dur-control) var(--ease);
}
input::placeholder { color: var(--ybc-stone); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--ybc-sage);
  box-shadow: none;
}
/* The two controls the browser is allowed to draw, because `accent-color` tints
   a native box while leaving its behaviour and its accessibility alone -- and a
   hand-drawn radio is a worse trade than a tinted one. Declared once here: it
   was set on `.contour-confirm` only, so every other box in the app -- the
   bundle picker, "primary contact", and the contact picker -- drew in the
   operating system's blue in the middle of a sage page. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--ybc-sage); }

input[type="file"] {
  font-family: inherit;
  font-size: var(--size-body-sm);
}

.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 4px 20px; }
.field-note { display: block; font-weight: 500; font-size: var(--size-caps); color: var(--ybc-stone); margin-top: 4px; }
.field-grid.readonly-grid {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
/* A filename is a long unbroken monospace string, and narrowing the columns to
   pack the short facts made it wrap to four lines. Any cell holding a reference
   value takes two tracks instead -- it is the one kind of value here that is
   long by nature, and the whole point of setting it in monospace is that
   somebody reads it off and types it back in. */
.readonly-grid > div:has(.ybc-reference) { grid-column: span 2; }
.readonly-grid > div { margin-bottom: 16px; }
.readonly-grid strong { display: block; font-size: 14px; font-weight: 600; }
.readonly-grid .field-note { margin: 0 0 2px; text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.readonly-line { font-size: var(--size-body-sm); color: var(--ybc-stone); }

.upload-form { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.upload-form label { margin: 0; }
.save-row { display: inline-flex; align-items: center; gap: 12px; }
.saved-indicator { color: var(--ybc-sage); font-size: var(--size-body-sm); font-weight: 600; display: inline-flex; align-items: center; gap: 5px; }
.saved-indicator svg { width: 15px; height: 15px; }

/* ---------- Status pills ----------
   Filled = "where is this now". The progression reads taupe -> cream -> sage:
   nothing done yet, in hand, finished. Adding a state means picking a tone
   from this set, never inventing a shape. */
.status-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--size-caps);
  font-weight: 600;
  line-height: 1;
  padding: 6px 10px;
  border-radius: 9999px;
  white-space: nowrap;
}
.status-uploaded { background: var(--status-quiet); color: var(--ybc-white); }
.status-reviewed { background: var(--ybc-linen); color: var(--ybc-sage-ink); box-shadow: inset 0 0 0 1px var(--ybc-linen-darker); }
.status-proofed { background: var(--ybc-sage); color: var(--ybc-white); }

/* Outlined + tinted = "what kind of record". Revision and side markers. */
.rev-badge, .side-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--size-caps);
  font-weight: 600;
  line-height: 1;
  padding: 5px 9px;
  border-radius: 9999px;
  border: 1px solid var(--ybc-sage-mist);
  color: var(--ybc-sage);
  background: var(--ybc-sage-whisper);
  white-space: nowrap;
}
.side-badge { margin-right: 6px; }
.rev-current { color: var(--ybc-sage); font-weight: 600; font-size: var(--size-caps); }

/* Confidence is a judgement about a detection, so it takes the status tones
   rather than a colour of its own. */
.conf-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--size-caps);
  font-weight: 600;
  line-height: 1;
  padding: 5px 9px;
  border-radius: 9999px;
  margin-left: 8px;
  white-space: nowrap;
}
.conf-high { background: var(--ybc-sage); color: var(--ybc-white); }
.conf-medium { background: var(--status-warn); color: var(--ybc-white); }
.conf-low { background: var(--status-alert); color: var(--ybc-white); }

/* ---------- Banners ----------
   Warnings flag things for a human decision; they never block. */
.banner {
  border-radius: var(--radius-control);
  padding: 12px 16px;
  margin: 12px 0;
  font-size: var(--size-body-sm);
  line-height: var(--leading-tight);
  border-left: 3px solid;
}
.banner-warning { background: #FDF6EC; border-color: var(--status-warn); color: #5C3C0E; }
.banner-warning strong { color: var(--status-warn); }
.banner-info { background: var(--ybc-sage-whisper); border-color: var(--ybc-sage); color: var(--ybc-sage-ink); }
/* Prose links only. A descendant type selector is specificity 0,1,1 and beats
   `.btn-primary` at 0,1,0, so without the `:not` a button dropped into a banner
   inherited the banner's ink -- the Email button rendered dark on sage. */
.banner a:not(.btn) { color: inherit; font-weight: 600; }

/* ---------- Callout ----------
   A banner states a fact; a callout asks for something to be done about one,
   so it takes the alert tone ("needs a human") rather than warn ("ended off
   the happy path"), and it gets room to say what and why. Still a hairline
   surface with a coloured edge — the same construction as .banner, not a
   louder box. */
.callout {
  border-radius: var(--radius-control);
  border: 1px solid var(--ybc-hairline);
  border-left: 3px solid;
  padding: 16px 18px;
  margin: 16px 0;
  font-size: var(--size-body-sm);
  line-height: var(--leading-body);
  max-width: 68ch;
}
.callout p { margin: 0 0 8px; }
.callout p:last-child { margin-bottom: 0; }
.callout a:not(.btn) { color: inherit; font-weight: 600; }
.callout-title {
  font-weight: 600;
  font-size: var(--size-body);
  line-height: var(--leading-tight);
}
.callout-note { color: var(--ybc-stone); font-size: var(--size-caps); }
.callout-alert {
  background: #FBF4F4;
  border-color: var(--status-alert);
  color: #4A1F1F;
}
.callout-alert .callout-title, .callout-alert strong { color: var(--status-alert); }

/* ---------- Dropzone ---------- */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 40px 24px;
  border: 1px dashed var(--ybc-sage-mist);
  border-radius: var(--radius-card);
  background: var(--ybc-linen);
  cursor: pointer;
  text-align: center;
  margin-bottom: 16px;
  transition: background-color var(--dur-control) var(--ease),
              border-color var(--dur-control) var(--ease);
}
.dropzone:hover { background: var(--ybc-linen-shade); border-color: var(--ybc-sage); }
.dropzone.is-dragging { background: var(--ybc-sage-whisper); border-color: var(--ybc-sage); }
.dropzone-icon { width: 28px; height: 28px; color: var(--ybc-sage); margin-bottom: 6px; }
.dropzone-primary { font-size: var(--size-body); font-weight: 600; }
.dropzone-secondary { font-size: var(--size-body-sm); color: var(--ybc-stone); font-weight: 500; }
.dropzone-link { color: var(--ybc-sage); text-decoration: underline; }
.dropzone input[type="file"] { display: none; }

.file-list { list-style: none; padding: 0; margin: 0 0 16px; }
.file-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-control);
  margin-bottom: 6px;
}
.file-name { flex: 1; font-family: var(--font-reference); font-size: var(--size-body-sm); overflow-wrap: anywhere; }
.file-size { color: var(--ybc-stone); font-size: var(--size-caps); font-variant-numeric: tabular-nums; }
/* The multiplication sign as a dismiss affordance is one of the two allowed
   non-icon glyphs. */
.file-remove {
  border: 0;
  background: none;
  color: var(--ybc-stone);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
}
.file-remove:hover { background: var(--ybc-linen); color: var(--ybc-black); }
.set-note {
  font-size: var(--size-body-sm);
  color: var(--ybc-sage-ink);
  background: var(--ybc-sage-whisper);
  padding: 10px 14px;
  border-radius: var(--radius-control);
  margin: 0 0 16px;
}

/* ---------- Colours used ---------- */
.colors-used { margin-top: 24px; }
.colors-used-label {
  font-size: var(--size-caps);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ybc-stone);
  margin: 0 0 10px;
}
.colors-used-row { display: flex; flex-wrap: wrap; gap: 20px; }
.color-chip { display: flex; align-items: center; gap: 10px; }
.chip-swatch {
  width: 32px; height: 32px;
  border-radius: var(--radius-control);
  border: 1px solid var(--ybc-hairline);
  flex-shrink: 0;
}
.chip-spot { background: var(--ybc-white); border: 1px dashed var(--ybc-stone); }
.chip-text { display: flex; flex-direction: column; gap: 1px; }
.chip-hex { font-family: var(--font-reference); font-size: var(--size-body-sm); font-weight: 600; }
.chip-sub { font-size: var(--size-caps); color: var(--ybc-stone); }

/* ---------- Colour process + contour ---------- */
.colorspec-summary { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.notation-badge {
  font-family: var(--font-reference);
  font-size: var(--size-heading-md);
  font-weight: 700;
  color: var(--ybc-sage);
  letter-spacing: 0.02em;
}
.notation-label { font-size: var(--size-body); font-weight: 600; }

.contour-panel {
  margin-top: 24px;
  padding: 18px;
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  background: var(--ybc-linen);
}
.contour-confirm {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0 0;
  font-weight: 600;
  font-size: var(--size-body-sm);
}
.contour-confirm input { width: auto; margin: 0; accent-color: var(--ybc-sage); }

/* One row per fold path, so a reviewer can check each position against the
   file rather than trusting a summary. Tighter than a page-level table --
   it sits inside a panel that is already inside a form card. */
.fold-paths { margin-top: 14px; }
.fold-paths th { padding: 6px 10px 6px 0; }
.fold-paths td { padding: 8px 10px 8px 0; font-size: var(--size-caps); }
.fold-paths tbody tr:hover { background: transparent; }
.fold-method { margin: 18px 0 0; max-width: 420px; }

.scale-helper {
  margin-top: 24px;
  padding: 16px 18px;
  background: var(--ybc-linen);
  border-radius: var(--radius-card);
}
.scale-helper-label { font-size: var(--size-body-sm); font-weight: 600; margin-bottom: 10px; }
.scale-helper-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.scale-helper-row input { width: 140px; margin: 0; background: var(--ybc-white); }
.scale-helper-row .field-note { margin: 0; }

.proof-source { font-family: var(--font-reference); font-size: var(--size-caps); color: var(--ybc-stone); line-height: 1.5; }

/* ---------- Danger zone ----------
   Uses raw red rather than a palette token: Sage & Linen has no red. This is
   the one documented place the product reaches past its own tokens. */
.danger-zone {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  margin-top: 64px;
  padding: 20px;
  border: 1px solid var(--danger-border);
  background: var(--danger-bg);
  border-radius: var(--radius-card);
}
.danger-title { font-weight: 600; margin: 0 0 4px; color: var(--danger-ink); }
.danger-note { margin: 0; font-size: var(--size-body-sm); color: #7F1D1D; max-width: 60ch; }
.delete-confirm { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.delete-confirm-text { font-size: var(--size-body-sm); font-weight: 600; color: #7F1D1D; }

/* ---------- Sign in ---------- */
.signin-shell { min-height: 70vh; display: flex; align-items: center; justify-content: center; }
.signin-card {
  width: 100%;
  max-width: 400px;
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 32px;
  background: var(--ybc-white);
}
.signin-card h1 { font-size: var(--size-heading-md); margin-bottom: 4px; }
.signin-card .subtext { margin-bottom: 24px; }
.signin-card .btn-primary { width: 100%; justify-content: center; }
.signin-error {
  background: var(--danger-bg);
  border-left: 3px solid var(--danger);
  color: #7F1D1D;
  padding: 12px 14px;
  border-radius: var(--radius-control);
  font-size: var(--size-body-sm);
  margin-bottom: 16px;
}
/* The bar is brand, then everything else as one group pinned right. One
   margin-left:auto, on the group -- put it on each item instead and flexbox
   shares the free space out between them rather than pushing them over, which
   is what made the spacing look arbitrary: measured at 801px wide, Clients and
   Stock took 150.9px of margin each and then sat 20px from Sign out. */
.site-nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* Portal nav items, per the design system's NavBar: a tight row of small pills
   rather than spaced-out text. The gap is 4px because each item carries its own
   padding -- the hover and current states need a shape to fill, and that shape
   is what separates one item from the next. */
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: rgba(255, 255, 255, 0.82);
  font: inherit;
  font-size: var(--size-body-sm);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}
.nav-link:hover { background: rgba(255, 255, 255, 0.12); color: var(--ybc-white); }

/* Where you are, as a white pill. The bar carries no other cue -- every page
   heading names the record, not the section -- so without this the only way to
   tell Clients from Stock is to read the page. */
.nav-link.is-current {
  background: var(--ybc-white);
  color: var(--ybc-sage);
}
.nav-link.is-current:hover { background: var(--ybc-white); color: var(--ybc-sage); }

/* Navigating and signing out are different kinds of act, so they are told apart
   by a rule rather than by a wider gap -- a gap only reads as a group boundary
   once it is big enough to look like a mistake. */
.nav-divider {
  width: 1px;
  height: 20px;
  margin: 0 var(--space-2);
  background: rgba(255, 255, 255, 0.18);
}

/* A table is the one thing here wider than a phone, and a body that scrolls
   horizontally drags the header and every other block with it -- so the table
   scrolls inside its own box instead. The overflow goes on a wrapper, never on
   the table: `display: block` on a <table> makes it scroll and destroys its
   column model with it, so the header stops lining up with the body. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll > .data-table { margin: 0; }

@media (max-width: 640px) {
  /* Retuning the token retunes the header, the content and the footer together,
     which is the point of naming it. A custom property needs a selector even
     here -- a bare declaration inside a media query is dropped silently. */
  :root { --content-gutter: 20px; }

  .content { padding: 24px var(--content-gutter) 64px; }
  .page-head { flex-direction: column; align-items: flex-start; }
  .data-table { font-size: var(--size-caps); }
}

/* ---------- File transfer progress ----------
   One row per file, for uploads and downloads alike. State is never carried
   by colour alone: every row says what it is doing in words, and the bar
   carries a real aria-valuenow rather than a decorative width. */
.transfer-list { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 14px; }
.transfer-batch {
  font-size: var(--size-body-sm);
  font-weight: 600;
  color: var(--ybc-sage-ink);
  margin: 16px 0 0;
}
.transfer-row {
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-control);
  padding: 12px 14px;
  background: var(--ybc-white);
}
.transfer-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.transfer-name { font-size: var(--size-body-sm); overflow-wrap: anywhere; }
.transfer-pct {
  font-family: var(--font-reference);
  font-size: var(--size-body-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.transfer-track {
  position: relative;
  height: 6px;
  margin: 10px 0 8px;
  border-radius: 9999px;
  background: var(--ybc-linen);
  overflow: hidden;
}
.transfer-fill {
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--ybc-sage);
  transition: width var(--dur-control) var(--ease);
}
/* Work is happening and its extent is genuinely unknown. A slow sweep, no
   bounce -- the same motion rules as everything else. */
.transfer-track.is-indeterminate .transfer-fill {
  width: 35%;
  animation: transfer-sweep 1.6s var(--ease) infinite;
}
@keyframes transfer-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(300%); }
}
@media (prefers-reduced-motion: reduce) {
  .transfer-track.is-indeterminate .transfer-fill { animation: none; width: 100%; opacity: 0.35; }
  .transfer-fill { transition: none; }
}

.transfer-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--size-caps);
  color: var(--ybc-stone);
}
.transfer-status { font-weight: 600; color: var(--ybc-sage-ink); }
.transfer-bytes { font-family: var(--font-reference); font-variant-numeric: tabular-nums; }
.transfer-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.transfer-link { color: var(--ybc-sage); font-weight: 600; text-decoration: none; }

.transfer-row[data-state="ready"] { border-color: var(--ybc-sage-mist); }
.transfer-row[data-state="ready"] .transfer-status { color: var(--ybc-sage); }
.transfer-row[data-state="failed"] { border-color: var(--status-alert); }
.transfer-row[data-state="failed"] .transfer-status { color: var(--status-alert); }
.transfer-row[data-state="failed"] .transfer-fill { background: var(--status-alert); }
.transfer-row[data-state="cancelled"] { opacity: 0.6; }
.transfer-row[data-state="cancelled"] .transfer-fill { background: var(--status-quiet); }

.btn-quiet {
  background: transparent;
  color: var(--ybc-sage-ink);
  font-weight: 600;
  /* Its own, because `.btn` sets none and every other variant supplies one.
     Missing here, the label sat flush against the inset border wherever this
     is used without `.btn-small` -- "Back to the proof" on the client's
     confirmation page. `.btn-small` still wins, being the later rule. */
  padding: 10px 20px;
  box-shadow: inset 0 0 0 1px var(--ybc-hairline);
}
.btn-quiet:hover:not(:disabled) { background: var(--ybc-linen); }
.btn-small { padding: 5px 12px; font-size: var(--size-caps); }

.dropzone.is-locked { opacity: 0.5; pointer-events: none; }
.transfer-detail {
  margin: 8px 0 0;
  font-size: var(--size-caps);
  line-height: var(--leading-tight);
  color: var(--status-alert);
}
.transfer-row[data-state="failed"] .transfer-status,
.transfer-row[data-state="failed_processing"] .transfer-status { color: var(--status-alert); }
.transfer-row[data-state="failed_processing"] { border-color: var(--status-alert); }
.transfer-row[data-state="failed_processing"] .transfer-fill { background: var(--status-alert); }

/* ---------- Brand colour variance ----------
   One block per family: the colour the design uses, then the ones that look
   like it. Severity is named in words as well as toned, like every other
   status in the app. */
.variance-group { margin-top: 14px; }
.variance-group + .variance-group { border-top: 1px solid var(--ybc-hairline); padding-top: 14px; }
.variance-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: var(--size-body-sm);
  font-weight: 600;
}
.variance-ref {
  font-family: var(--font-reference);
  font-size: var(--size-caps);
  font-weight: 700;
  color: var(--status-warn);
}
.variance-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; }
.variance-row .chip-swatch { width: 26px; height: 26px; }
.variance-severity {
  margin-left: auto;
  font-size: var(--size-caps);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.severity-info { color: var(--ybc-stone); }
.severity-warning { color: var(--status-warn); }
.severity-critical { color: var(--status-alert); }

/* Acting on a colour finding: quiet buttons under the row they belong to. */
.variance-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 10px 36px; }
.brand-color-form { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.brand-color-form label { margin: 0; font-size: var(--size-caps); }
.brand-color-form input[type="number"] { width: 70px; }
.brand-color-form input[type="text"] { width: 200px; }
.color-chip form { margin-left: 8px; }

/* --------------------------------------------------- house stock picker
   Chips rather than a dropdown: the catalogue is short and stable, so it is
   shown rather than searched. Names are monospace because they are reference
   values -- read off a proof and typed back in. */
.stock-groups { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
.stock-group-name {
  font-size: var(--size-caps);
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ybc-stone);
  margin: 0 0 6px;
}
.stock-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.stock-chip {
  /* Same face as every other reference value on the page, via the same token
     -- 0/O and 1/I/l have to stay apart in a name someone reads off a proof. */
  font-family: var(--font-reference);
  letter-spacing: 0.02em;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ybc-black);
  background: var(--ybc-linen);
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  padding: 6px 11px;
  cursor: pointer;
  transition: border-color var(--dur-control) var(--ease),
              background var(--dur-control) var(--ease),
              transform var(--dur-control) var(--ease);
}
.stock-chip:hover { border-color: var(--ybc-sage); }
.stock-chip:active { transform: scale(0.98); }
.stock-chip[aria-pressed="true"] {
  background: var(--ybc-sage);
  border-color: var(--ybc-sage);
  color: var(--ybc-white);
}

/* The proof just generated. Named as well as tinted -- colour alone is not
   how a row says which one it is, and the tint has to survive a printout. */
.data-table tr.row-new > td { background: var(--ybc-sage-whisper); }

/* A readonly grid that follows a paragraph inside a panel. Set inline in three
   places with the same value, which is a class wearing a disguise. */
.readonly-grid-inset { margin-top: var(--space-2); }

/* Header search. Sits with the right-hand group, so it carries the auto
   margin that pushes that group over and the rest follow it. */
/* Sits with the nav rather than floating between the brand and it. Its own
   margin holds it off the links: the pills need to read as one row, and a
   filled control butting against them would join that row. */
.header-search { margin-left: var(--space-3); }
.header-search input {
  width: 240px;
  margin: 0;
  padding: 7px 12px;
  background: rgba(255, 255, 255, 0.12);
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  color: var(--ybc-white);
  font-size: var(--size-body-sm);
}
.header-search input::placeholder { color: rgba(255, 255, 255, 0.6); }
.header-search input:focus-visible {
  outline: none;
  border-color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.18);
}
@media (max-width: 860px) { .header-search { display: none; } }
/* The bar's own breakpoint, tighter than the page's: at this width the four
   items still fit, but only without the tagline beside the mark. */
@media (max-width: 700px) { .brand-sub { display: none; } }

/* A checkbox and its label on one line, rather than the label block above it. */
.checkbox-row { display: flex; align-items: center; gap: 8px; font-weight: 500; }
.checkbox-row input { width: auto; margin: 0; }

/* ---------- Date picker ----------
   `<input type="date">` draws the operating system's calendar: grey in Chrome,
   different in Safari, different again in Firefox, and none of them take a
   colour, a radius or a typeface from this file. It was the one control here
   whose appearance YBC did not choose. */
.dp-field { position: relative; }
/* The field keeps room for the trigger rather than running under it. */
.dp-field input { padding-right: 42px; font-family: var(--font-reference); }

.dp-trigger {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 0 var(--radius-control) var(--radius-control) 0;
  background: none;
  color: var(--ybc-stone);
  cursor: pointer;
  transition: color var(--dur-control) var(--ease);
}
.dp-trigger:hover { color: var(--ybc-sage); }
.dp-trigger:focus-visible { outline: 2px solid var(--ybc-sage); outline-offset: -2px; }

/* Fixed, not absolute, and placed by script. Every form on this app sits in a
   .form-card, and a card carries `overflow: hidden` so its dark heading bar is
   clipped by its own rounded corners. An absolutely positioned popover inside
   one is clipped by the same rule -- measured, the calendar was cut off mid-grid
   at the card's bottom edge. A fixed element escapes an ancestor's overflow, so
   the card keeps its corners and the calendar keeps its last week. */
.dp-pop {
  position: fixed;
  z-index: 30;
  width: 268px;
  padding: var(--space-3);
  background: var(--ybc-white);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.dp-pop[hidden] { display: none; }

.dp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.dp-title { font-weight: 600; font-size: var(--size-body-sm); }
.dp-step {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--ybc-stone);
  cursor: pointer;
  transition: background-color var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}
.dp-step:hover { background: var(--ybc-linen); color: var(--ybc-sage); }

/* Caps, letterspaced, per the label rule -- the same treatment every other
   short label in the app takes. */
.dp-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin-bottom: var(--space-1);
  color: var(--ybc-stone);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  text-align: center;
}

.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
/* The number is a reference value in the same sense a filename is: it is read
   off and written down. Monospace also keeps every cell the same width, so the
   grid does not shuffle between a month of single and double digits. */
.dp-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--ybc-sage-ink);
  font-family: var(--font-reference);
  font-size: 12px;
  cursor: pointer;
  transition: background-color var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}
.dp-day:hover { background: var(--ybc-linen); }
.dp-day:focus-visible { outline: 2px solid var(--ybc-sage); outline-offset: -2px; }
/* The days either side of the month are shown rather than blanked: a week that
   stops mid-row hides which weekday a month starts on, which is the thing
   somebody scheduling a print run is looking for. */
.dp-day.is-outside { color: var(--ybc-sage-mist); }
/* Today is outlined and the selection is filled, so a day that is both still
   reads as both. */
.dp-day.is-today { box-shadow: inset 0 0 0 1px var(--ybc-sage-mist); }
.dp-day.is-selected,
.dp-day.is-selected:hover { background: var(--ybc-sage); color: var(--ybc-white); }

.dp-foot {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--ybc-hairline);
}
.dp-action {
  padding: 4px 10px;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--ybc-sage);
  font: inherit;
  font-size: var(--size-body-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--dur-control) var(--ease);
}
.dp-action:hover { background: var(--ybc-linen); }
.dp-clear { margin-left: auto; color: var(--ybc-stone); }

@media (prefers-reduced-motion: reduce) {
  .dp-trigger, .dp-step, .dp-day, .dp-action { transition: none; }
}

/* ---------- Client proof review ----------
   The public page. It reuses everything above it -- cards, tables, buttons,
   the type scale -- because a client seeing YBC's proof should see the same
   YBC the staff screens do. Only what has no counterpart internally is new. */

/* Sheets of a proof, in a grid that becomes one column on a phone. Each is a
   link to its own full-size image: on a small screen the readable move is to
   open one sheet and pinch it, not to scroll a wall of them. */
.review-sheets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-4);
  margin: var(--space-3) 0 var(--space-5);
}
/* A figure per sheet: the image and its caption are one cell of the grid. */
.review-figure { margin: 0; }
/* The summary sheet is what the page above already says in words. Named and
   quieted so the artwork -- the thing a client is here to check -- is the
   thing that reads first. */
.review-sheet.is-summary { opacity: 0.72; }
.review-sheet.is-summary:hover { opacity: 1; }
.review-sheet-label {
  margin: var(--space-2) 0 0;
  color: var(--ybc-stone);
  font-size: var(--size-body-sm);
  font-weight: 600;
  text-align: center;
}

.review-sheet {
  display: block;
  position: relative;
  background: var(--ybc-white);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  text-decoration: none;
  transition: box-shadow var(--dur-control) var(--ease),
              transform var(--dur-control) var(--ease);
}
.review-sheet:hover { box-shadow: var(--shadow-card-lift, var(--shadow-card)); transform: translateY(-1px); }
/* A proof sheet has its own aspect and they are not all the same one -- a
   landscape banner sheet sits among portrait ones. Letting each image keep its
   own shape is what stops the grid cropping artwork the client is checking. */
.review-sheet img { display: block; width: 100%; height: auto; }
.review-sheet-num {
  position: absolute;
  right: var(--space-2);
  bottom: var(--space-2);
  padding: 2px 8px;
  border-radius: var(--radius-control);
  background: rgba(11, 32, 21, 0.72);
  color: var(--ybc-white);
  font-family: var(--font-reference);
  font-size: 10px;
}

/* Approve and Request changes, side by side and never the same weight: one of
   these sends a job to press. */
.review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-4);
}
@media (max-width: 560px) {
  .review-actions { flex-direction: column; align-items: stretch; }
  .review-actions .btn { width: 100%; justify-content: center; }
}

/* The share URL, shown once. Monospace and breakable: it is a reference value,
   and a 43-character token must wrap rather than widen its card. */
.share-url {
  display: block;
  margin: var(--space-2) 0;
  padding: var(--space-2);
  background: var(--ybc-white);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-control);
  font-size: var(--size-body-sm);
  word-break: break-all;
}

/* ---------- Touch targets ----------
   A link in a table row is 16-21px tall, which is the height of its text. That
   is fine for a cursor and small for a finger -- WCAG asks for 24px. Rather
   than grow the rows for everyone, the hit area is expanded behind the text,
   and only where the pointer is coarse: an invisible box that is part of the
   link, so nothing moves and nothing looks different.

   Scoped to coarse pointers for a second reason -- on a dense desktop table
   these boxes would come close enough to each other to start stealing one
   another's clicks. */
@media (pointer: coarse) {
  .data-table a,
  .row-link,
  .crumbs a { position: relative; }
  .data-table a::after,
  .row-link::after,
  .crumbs a::after {
    content: "";
    position: absolute;
    inset: -8px -6px;
  }
  /* Vertically only. Crumbs sit 8px apart, so 6px of hit area on each side
     would leave them overlapping by 4px and stealing each other's taps --
     which is worse than the small target it set out to fix. */
  .crumbs a::after { inset: -8px 0; }
}

/* The client's approval receipt. Print gets the record and nothing else: a
   client saving this as a PDF for their own file does not need YBC's navigation
   or a link back to a page they have left. */
.receipt { border-color: var(--ybc-sage); }
/* The job line sat 2px above the first row of labels, so "Test6 · 2026-08-29"
   read as part of "VERSION APPROVED" rather than as the heading it belongs to.
   `.subtext` carries no bottom margin by design; the space belongs to whatever
   follows it. */
.receipt .field-grid { margin-top: var(--space-3); }

/* The client's own words, quoted back on the version that answers them. Set as
   a quotation rather than as body copy: it is the one block on the page that
   YBC did not write, and it should not read as if we did. */
.asked-for { border-left: 3px solid var(--ybc-sage); }
.asked-quote {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--ybc-hairline);
}
.asked-quote p:first-child { margin: 0; font-weight: 500; }

/* Sits apart from the hint above it: one is about the button, the other is
   what to do when neither button is the answer. */
.contact-line { margin-top: var(--space-3); }
@media print {
  .site-header, .site-footer, .page-head-actions, .hint-apart { display: none; }
  .content { padding: 0; }
  .form-card { box-shadow: none; break-inside: avoid; }
}

/* ---------- The approvals worklist ----------
   Four figures across the top: the ones that decide what to do this morning.
   Deliberately not cards -- a card says "read me", and these are meant to be
   taken in at a glance on the way to the table. */
.tally {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4);
  margin: var(--space-5) 0;
}
.tally-cell {
  display: block;
  padding: var(--space-3) var(--space-4);
  background: var(--ybc-white);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  color: inherit;
  text-decoration: none;
  transition: box-shadow var(--dur-control) var(--ease),
              transform var(--dur-control) var(--ease),
              border-color var(--dur-control) var(--ease);
}
/* A cell with a number in it opens the worklist filtered to that number, so it
   carries the lift the rest of the portal's cards do. A zero is a `div` and
   gets none of this -- an empty list is a worse answer than a number you can
   read where it is. */
a.tally-cell:hover {
  border-color: var(--ybc-sage);
  box-shadow: var(--shadow-card-lift, var(--shadow-card));
  transform: translateY(-1px);
}
a.tally-cell:active { transform: scale(0.98); }
a.tally-cell:focus-visible { outline: 2px solid var(--ybc-sage); outline-offset: 2px; }
.tally-cell.is-current {
  border-color: var(--ybc-sage);
  box-shadow: inset 0 0 0 1px var(--ybc-sage);
}
.tally-cell.is-empty .tally-figure { color: var(--ybc-stone); }
.tally-figure {
  display: block;
  margin: var(--space-1) 0 0;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.tally-cell .eyebrow { display: block; }
/* A count that means somebody has to do something is coloured; a zero is not.
   The template only adds these when the figure is non-zero, so "nothing
   overdue" reads as calm rather than as a green tick nobody asked for. */
.tally-cell.is-alert { border-color: var(--status-alert); }
.tally-cell.is-alert .tally-figure { color: var(--status-alert); }
.tally-cell.is-flag { border-color: var(--status-warn); }
.tally-cell.is-flag .tally-figure { color: var(--status-warn); }

/* A named place to land rather than a visible element: "#send" means the
   sharing panel, whose own id carries a proof id nobody can predict. */
.anchor-only { display: block; height: 0; scroll-margin-top: 90px; }

/* The review screen's own action bar, pinned to the bottom of the viewport.
   The form runs to about 3,000px on a file with findings, and a clean file
   should not have to be scrolled to its end to be confirmed. */
.review-bar {
  /* Fixed rather than sticky, and for the reason the date picker is fixed: the
     bar lives inside a `.form-card`, a card clips to its own rounded corners
     with `overflow: hidden`, and that makes the card the sticky scrollport --
     so a sticky bar simply sat at the end of the form. Measured: 1,424px down a
     545px viewport. `overflow: hidden` does not clip a fixed descendant. */
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  justify-content: space-between;
  padding: var(--space-3) max(var(--content-gutter),
                             calc((100vw - var(--content-max)) / 2 + var(--space-4)));
  background: var(--ybc-white);
  border-top: 1px solid var(--ybc-hairline);
  box-shadow: 0 -6px 18px rgba(56, 78, 65, 0.06);
}
/* Room for it, so the last field is never underneath the thing that saves it,
   and so a field scrolled into view by focus does not land behind the bar. */
form.form-card:has(.review-bar) { padding-bottom: 96px; }
html:has(.review-bar) { scroll-padding-bottom: 110px; }
.review-bar-count { margin: 0; font-size: var(--size-body-sm); color: var(--ybc-stone); }
.review-bar-count strong { color: var(--ybc-ink, inherit); }
.review-bar-actions { display: flex; align-items: center; gap: var(--space-2); }
@media (max-width: 620px) {
  .review-bar { flex-direction: column; align-items: stretch; }
  .review-bar-actions { justify-content: stretch; }
  .review-bar-actions .btn { flex: 1; }
}

/* An explanation folded away behind the instruction. The instruction is what
   a reviewer needs every time; the reasoning is what they need once. */
.callout-why { margin-top: var(--space-2); }
.callout-why > summary {
  cursor: pointer;
  font-size: var(--size-body-sm);
  font-weight: 600;
  color: inherit;
  opacity: 0.85;
}
.callout-why > summary:hover { opacity: 1; }
.callout-why[open] > summary { margin-bottom: var(--space-2); }

/* Sortable column heads. The link carries the whole cell so the target is the
   width of the label rather than the width of the word. */
.sort-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: inherit;
  text-decoration: none;
  padding: 2px 0;
}
.sort-link:hover { color: var(--ybc-sage); text-decoration: underline; }
th.is-sorted .sort-link { color: var(--ybc-sage); }
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* What the worklist is filtered to, and the ways back out of it. */
.filter-line {
  margin: calc(-1 * var(--space-3)) 0 var(--space-4);
  color: var(--ybc-stone);
  font-size: var(--size-body-sm);
}
.filter-line a { margin-left: var(--space-2); }

/* A row that has been waiting past the chase threshold. A tint, not a border:
   it has to be legible down a column of forty without turning the table into
   a warning. */
.row-attention > td { background: #FDF6EC; }

/* Four nav items plus a sign-out label is wider than a phone: measured at
   386px, the bar wanted 419 and took the whole page sideways with it. The
   label goes rather than a destination -- hiding somewhere you can navigate to
   is worse than hiding a word next to an icon that already means it, and the
   button keeps its accessible name through aria-label. */
@media (max-width: 520px) {
  .nav-signout .nav-label { display: none; }
  .nav-signout { padding-left: 8px; padding-right: 8px; }
  .nav-divider { margin: 0 var(--space-1); }
  .site-nav { gap: 0; }
  .nav-link { padding-left: 8px; padding-right: 8px; }
}

/* ---------- The proof-sheet viewer ----------
   Opened over the review page rather than in a tab of its own. A client on a
   phone is reading 8pt body copy on a letter sheet: they need to get in close
   and then get back to the proof, which a bare image in a new tab does not
   allow. */
.sheet-viewer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Near-black rather than YBC Sage: the sheet is the artwork, and a green
     surround would sit inside the client's judgement of its colour. */
  background: rgba(8, 12, 10, 0.92);
}
.sheet-viewer[hidden] { display: none; }
/* The page behind cannot scroll while a sheet is open, or closing the viewer
   returns you somewhere else on the proof. */
body.sheet-open { overflow: hidden; }

.sheet-stage {
  max-width: 100%;
  max-height: 100%;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  padding: 56px 12px 12px;
}
.sheet-stage img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 68px);
  margin: 0 auto;
  transform-origin: top center;
  transition: transform var(--dur-control) var(--ease);
  background: var(--ybc-white);
}
/* Zoomed, the image is allowed past the stage so it can be panned. */
.sheet-stage.is-zoomed img { max-width: none; max-height: none; }

.sheet-bar {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
}
.sheet-count {
  margin-right: auto;
  color: rgba(255, 255, 255, 0.82);
  font-family: var(--font-reference);
  font-size: var(--size-body-sm);
}
.sheet-btn {
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-control);
  background: rgba(255, 255, 255, 0.14);
  color: var(--ybc-white);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.sheet-btn:hover { background: rgba(255, 255, 255, 0.26); }
.sheet-btn:focus-visible { outline: 2px solid var(--ybc-white); outline-offset: 2px }

/* Previous and next as full-height edges: a target you can hit without looking
   on a phone, and out of the way of the artwork itself. */
.sheet-nav {
  position: absolute;
  top: 56px;
  bottom: 0;
  width: 15%;
  min-width: 44px;
  border: 0;
  background: none;
  cursor: pointer;
}
.sheet-nav[hidden] { display: none; }
.sheet-prev { left: 0; }
.sheet-next { right: 0; }
.sheet-nav:focus-visible { outline: 2px solid var(--ybc-white); outline-offset: -4px; }

@media (prefers-reduced-motion: reduce) {
  .sheet-stage img { transition: none; }
}

/* ---------- Client review page refinements ----------
   Measured on a live share link. The page is read once, by somebody outside
   the company, usually on a phone -- so it gets tuned as prose rather than as
   the dense reference the staff screens are. */

/* Prose ran to about 90 characters a line. The cap was 68ch, and `ch` in
   Outfit is narrow enough that 68 of them hold ninety letters -- comfortable
   reading is 45 to 75. Shortened here rather than everywhere: the staff pages
   are scanned, this one is read. */
.review-body .hint,
.review-body .subtext { max-width: var(--measure); }

/* The job's facts are a size, a colour notation, a material and a count --
   short values, not the long text fields the 220px minimum was set for. At the
   old minimum four of them fell 4px short of fitting one row, so the fourth
   sat alone under two empty columns. */
.review-body .field-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
/* Same rule as `.readonly-grid` above, for the same reason and on the surface
   where it matters most. `minmax(140px, …)` replaces the automatic min-content
   floor, so a track can end up narrower than an unbreakable monospace string --
   and an email address in a 160px track ran straight across the cell beside it,
   colliding with the date on the client's own approval receipt. Two tracks for
   any cell holding a reference value; the internal grids fit one because their
   minimum is 220px. */
.review-body .field-grid > div:has(.ybc-reference) { grid-column: span 2; }
/* And a floor under all of it: where even two tracks are not enough -- a long
   address on a narrow phone -- the value wraps rather than overlapping its
   neighbour. Ugly beats unreadable, and this is a record somebody keeps. */
.field-grid .ybc-reference { overflow-wrap: anywhere; }

/* Approve and Request changes sat 4px different in height -- the primary
   button carries more padding, which is right on its own and reads as
   misalignment the moment the two sit in a row. Matched here, where they do. */
.review-actions .btn { min-height: 44px; }

/* 16px on a touch device, because Safari zooms the whole page in when a field
   smaller than that takes focus -- and the field it happened on is a client
   typing their own name to approve a job. Keyed to the pointer rather than to
   the width: an iPad reports 768px and zooms just the same. The fields get a
   little taller and the page stops jumping under them. */
@media (pointer: coarse) {
  input[type="text"], input[type="number"], input[type="date"],
  input[type="email"], input[type="password"], input[type="search"],
  select, textarea { font-size: 16px; }
}

/* On a phone the same grid measured 293px inside its card and two columns
   wanted 300, so five short facts -- a version, a size, an ink notation, a
   stock and a count -- stacked one per row and took 300px of scrolling. Seven
   pixels. Asked for explicitly at this width rather than nudging the minimum
   again, because the minimum is what the desktop layout is tuned to. */
@media (max-width: 520px) {
  .review-body .form-card .field-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-3);
  }
}

/* The two fields a client must fill in keep the full width -- an email address
   in half a phone is a bad trade -- and the two optional ones pair up under
   them. */
@media (max-width: 640px) {
  .field-grid-pairs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .field-grid-pairs > label:nth-child(-n+2) { grid-column: 1 / -1; }
}

/* Four fields that belong together in twos. auto-fit lays four out as 3 + 1,
   which leaves the last one alone under two empty columns and gives an email
   address the same width as a phone extension. */
@media (min-width: 641px) {
  .field-grid-pairs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A card whose only content is a paragraph. Prose is capped at --measure for
   readability, and a full-width box around it left half the box empty --
   which reads as something broken rather than as a deliberate line length.
   So the card takes the measure too, and the text fills it.

   The 42px is the card's own 20px margins on the text plus its 1px borders --
   which only adds up because --measure is a real length and not a `ch`. */
.note-card { max-width: calc(var(--measure) + 42px); }

/* ---------- Editing a name in place ----------
   A pencil beside the thing it changes, and the form folded behind it. A
   correction is not something anybody does twice, so it should be within reach
   of the heading without taking up room next to it. */
.titled { display: flex; align-items: center; gap: var(--space-2); }
/* The heading keeps its own bottom margin; the row should not inherit it, or
   the pencil sits high. */
.titled > h1, .titled > .subtext { margin-bottom: 0; }

.inline-edit { position: relative; flex-shrink: 0; }
.inline-edit > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-control);
  color: var(--ybc-sage-mist);
  cursor: pointer;
  list-style: none;
  transition: background-color var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}
/* The disclosure triangle, in both dialects. */
.inline-edit > summary::-webkit-details-marker { display: none; }
.inline-edit > summary::marker { content: ""; }
.inline-edit > summary:hover { background: var(--ybc-linen); color: var(--ybc-sage); }
.inline-edit > summary:focus-visible {
  outline: 2px solid var(--ybc-sage);
  outline-offset: 1px;
}
.inline-edit[open] > summary { background: var(--ybc-linen); color: var(--ybc-sage); }

/* Positioned rather than in flow: `<details>` puts its content after the
   summary, and the summary has to come first -- so the only way the pencil
   shares a line with its heading is for the panel to leave the flow. Safe
   here, unlike inside a .form-card, because nothing between this and the page
   clips its overflow. */
.inline-edit-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  width: min(24rem, calc(100vw - 2 * var(--content-gutter)));
  padding: var(--space-4);
  background: var(--ybc-white);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.inline-edit-panel .btn { margin-top: var(--space-3); }
/* Right-hand edge of the column: the panel would run off the page opening
   leftward from a pencil that sits near it. */
@media (max-width: 520px) {
  .inline-edit-panel { left: auto; right: 0; }
}

/* An edit form folded into a card. In flow rather than positioned, because a
   card clips to its own rounded corners and anything absolute inside one gets
   cut off -- the trap the date picker had to escape. The card grows instead. */
.field-edit { margin-top: var(--space-4); }
.field-edit > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ybc-stone);
  font-size: var(--size-body-sm);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.field-edit > summary::-webkit-details-marker { display: none; }
.field-edit > summary::marker { content: ""; }
.field-edit > summary:hover { color: var(--ybc-sage); }
.field-edit[open] > summary { margin-bottom: var(--space-3); }
.field-edit .btn { margin-top: var(--space-3); }

/* ---------- Several items on one review link ----------
   An event's elements on one page: signage, amenity cards, banners. Each is a
   section with its own decision, because each goes to press separately. What
   they share is the page and the one place the client types who they are. */
.piece + .piece {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--ybc-hairline);
}
.piece-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}
/* Which of how many, so a client can tell where they are without counting. */
.piece-count {
  color: var(--ybc-stone);
  font-family: var(--font-reference);
  font-size: var(--size-caps);
  font-weight: 500;
}
.piece-status {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: 9999px;
  background: var(--ybc-linen);
  color: var(--ybc-stone);
  font-size: var(--size-caps);
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  /* A state is a name, the same way a button label is. Left to wrap, "SENT FOR
     APPROVAL" broke across two lines in the approvals table and both lines sat
     left inside a pill sized to the column, which reads as a badge printed
     off-centre rather than as a label that ran out of room. The other three
     9999px badges already say this. */
  white-space: nowrap;
}
/* The tick rides with the word rather than sitting on the baseline under it. */
.piece-status svg { flex: none; }
/* The rest of what a row has to say, kept out of the badge: a status and "on a
   link with one other" in one pill made a sage lozenge the width of a sentence,
   and the state is the part worth seeing at a glance. */
.piece-note {
  color: var(--ybc-stone);
  font-size: var(--size-body-sm);
  font-weight: 500;
}
/* Only the two that mean somebody has acted are coloured. "Sent" and "viewed"
   are the resting state and should not read as a result. */
/* What is on the link, named at the top. A count alone left a client scrolling
   four thousand pixels to find out what they had been sent. */
.review-toc {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
}
.review-toc li {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.review-toc a { font-weight: 600; }

.piece-status.is-approved { background: var(--ybc-sage); color: var(--ybc-white); }
.piece-status.is-changes_requested { background: #FDF6EC; color: #5C3C0E; }

/* Not a pill in the contents list. At 375px a pill after "Paradise Salon 3 -
   Nano Doors" wraps to a line of its own and the entry reads as two broken
   rows; the badges on the items themselves are where a status carries weight.

   After the state rules, not before them. `.review-toc .piece-status` and
   `.piece-status.is-approved` both weigh two classes, so the later one won the
   background -- and an approved item came out as a sage word on a sage pill,
   which is to say a green blob where the status should be. Both state colours
   are restated here for the same reason. */
.review-toc .piece-status {
  padding: 0;
  background: none;
  color: var(--ybc-stone);
  font-size: var(--size-caps);
}
.review-toc .piece-status.is-approved {
  background: none;
  color: var(--ybc-sage);
}
.review-toc .piece-status.is-changes_requested {
  background: none;
  color: #5C3C0E;
}

/* Choosing what goes on one client link, and what is already on it. */
/* The picker for what goes on a link, and the one for who it is emailed to.
   Same control, same shape -- listed together rather than described twice. */
.bundle-pick, .recipient-pick {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
}
.bundle-pick > legend, .recipient-pick > legend {
  padding: 0 var(--space-2);
  color: var(--ybc-stone);
  font-size: var(--size-caps);
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.bundle-pick .checkbox-row, .recipient-pick .checkbox-row { align-items: flex-start; padding: var(--space-1) 0; }
/* The file the element came from, under its project name: two projects can be
   called the same thing, and the filename is what tells them apart. */
.bundle-piece { display: block; margin: 0; font-family: var(--font-reference); }
.bundle-row { display: flex; justify-content: space-between; gap: var(--space-3); }
/* Send sits above the row of buttons rather than in it, so it needs its own
   space below when there is no contact picker to provide any. */
.send-form { margin: 0 0 var(--space-2); }
/* Writing down an answer that arrived by phone or email. Folded away because it
   is the exception -- most clients press the button -- but on the panel, which
   is where somebody is standing when the email arrives. */
.record-answer,
.send-confirm {
  /* Both sides, not just the top. `.page-head-actions` is a bare flex row with
     no vertical margin of its own, and one follows each of these disclosures --
     so with a top-only margin the Regenerate and Copy link buttons sat flush
     against the border above them. A bottom margin here rather than a top
     margin on the row because these are the two elements that know they are
     bordered boxes in a stack; and in a block container adjacent margins
     collapse, so this guarantees the gap without ever adding to a sibling that
     already has one. */
  margin: var(--space-3) 0;
  border: 1px solid var(--ybc-hairline);
  border-radius: var(--radius-card);
  padding: var(--space-2) var(--space-3);
}
.record-answer[open],
.send-confirm[open] { padding-bottom: var(--space-3); }
.record-answer > summary,
.send-confirm > summary {
  cursor: pointer;
  font-size: var(--size-body-sm);
  font-weight: 600;
  color: var(--ybc-sage-ink);
  /* The native disclosure triangle is the browser drawing a control, and it
     comes from the OS font at whatever weight that is. Replaced with the same
     chevron the sortable columns use. */
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
}
.record-answer > summary::-webkit-details-marker,
.send-confirm > summary::-webkit-details-marker { display: none; }
.record-answer > summary svg,
.send-confirm > summary svg { transition: transform var(--dur-control) var(--ease); }
.record-answer[open] > summary svg,
.send-confirm[open] > summary svg { transform: rotate(180deg); }
.record-answer > *:not(summary),
.send-confirm > *:not(summary) { margin-top: var(--space-3); }
.bundle-row + .bundle-row { margin-top: var(--space-2); }
/* The check shown before a proof is emailed. A description list, because every
   row is a label and the value a person reads it against -- and the values are
   reference values (an address, a version, a URL), so they are monospace for
   the reason every reference value here is: `0`/`O` and `1`/`l` collide in
   Outfit, and these are exactly what somebody is checking character by
   character. */
.send-check {
  margin: 0;
  display: grid;
  /* Named rather than auto: a long address in the value column must not be
     allowed to squeeze the labels into two lines each. `minmax` replaces the
     grid's automatic min-content floor so an unbreakable string wraps inside
     its own cell instead of widening the track. */
  grid-template-columns: 8.5rem minmax(0, 1fr);
  gap: 6px var(--space-3);
  font-size: var(--size-body-sm);
}
/* The form is the disclosure's only child, so the rule that spaces a
   disclosure's contents never reaches these. Without it the button that sends
   sat flush against the last row of the check. */
.send-confirm .send-form > * + * { margin-top: var(--space-3); }
.send-check dt { color: var(--ybc-stone); }
.send-check dd { margin: 0; min-width: 0; }
.send-check dd .ybc-reference { overflow-wrap: anywhere; }
/* One element per line. A bundle's five proofs run together on one line read
   as a sentence rather than as a list to check off. */
.send-check-job { display: block; }
@media (max-width: 640px) {
  /* The label above its value: 8.5rem of label leaves an address nowhere to go
     on a phone. */
  /* `minmax(0, ...)` here too, not a bare `1fr`: a bare one keeps the grid's
     automatic min-content floor, so the longest address would set the track
     width and push the card past the viewport. Same trap as the receipt grid. */
  .send-check { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .send-check dt { margin-top: var(--space-2); }
  .send-check dt:first-child { margin-top: 0; }
}

/* The filename sits under the piece name rather than beside it: two projects
   can share a name, and the file is what tells them apart. */
.bundle-piece { display: block; font-size: 12px; color: var(--stone); }
